javaGrowing

  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  92 随笔 :: 33 文章 :: 49 评论 :: 0 Trackbacks

#

JDBC基础(一)

本来不想写这部份入门级的内容,但既然栏目定为JDBC专栏,还是简单写一些吧.
JDBC基础(一)

    来,我们认识一下!
    JDBC,JAVA平台的DATABASE的连通性.白话一句,什么意思啊?
    就是JAVA平台上和数据库进行连结的"工具".

    还是先一起来回顾一下接口吧:从下向上,接口是对"案例"的抽象,由一个案例抽象出一些规则.
反过来,从上向下,被抽象出来的接口是对案例的一种承诺和约束.
    也就是说,只要你实现我规定的接口,你的类就已经具有了接口对外承诺的方法,只要"客户"会
操作接口,不需要重新学习就会操作实现了该接口的新类!
    好了,用行话来说:
    1.通过接口可以实现不相关的类的相同行为.
    2.通过接口可以指明多个类需要实现的方法.
    3.通过接口可以了解对象的交互方法而不需要了解对象所对应的类蓝本.
    这几句话很明白吧?好象有一本什么模式的书把这段话用了30多页写出来,结果别人看了还不如
我这几句话明白,不过我明白了为什么有些人要写书了.

    搞懂了以上这东西,JDBC就好明白了.
    为了通用,JAVA中要求有一种机制,在操作不同厂商数据库时有相同的方法去操作,而不是每接
触一种数据库就要学习新的方法.完成这种机制的"东西"就叫"JDBC"了.
    简单地分,JDBC有两部分组成,JDBC API和JDBC Driver Interface.
    JDBC API就是提供给"客户"(就是象你我这种菜鸟级程序员来用的,如果是高手都自己写JDBC了,
哈哈)的一组独立于数据库的API,对任何数据库的操作,都可以用这组API来进行.那么要把这些通用的API
翻译成特定数据库能懂的"指令",就要由JDBC Driver Interface来实现了,所以这部份是面向JDBC驱动程
序开发商的编程接口,它会把我们通过JDBC API发给数据库的通用指令翻译给他们自己的数据库.


    还是通过实际操作来看看JDBC如何工作的吧.

    因为JDBC API是通用接口,那么程序是如何知道我要连结的是哪种数据库呢?所以在和数据库连
结时先要加载(或注册可用的Driver),其实就是JDBC签名.加载驱动程序和好多方法,最常用的就是先把驱
动程序类溶解到内存中,作为"当前"驱动程序.注意"当前"是说内存中可以有多个驱动程序,但只有现在加
载的这个作为首选连结的驱动程序.
    Class.forName("org.gjt.mm.mysql.Driver");
    Class.forName方法是先在内存中溶解签名为"org.gjt.mm.mysql.Driver"的Driver类,Driver类
就会把相应的实现类对应到JDBC API的接口中.比如把org.gjt.mm.mysql.Connection的实例对象赋给
java.sql.Connection接口句柄,以便"客户"能通过操作java.sql.Connection句柄来调用实际的
org.gjt.mm.mysql.Connection中的方法.之于它们是如果映射的,这是厂商编程的,"客户"只要调用
Class.forName("org.gjt.mm.mysql.Driver");方法就可以顺利地操作JDBC API了.

    一个普通数据库的连结过程为:
    1.加载驱动程序.
    2.通过DriverManager到得一个与数据库连结的句柄.
    3.通过连结句柄绑定要执行的语句.
    4.接收执行结果.
    5.可选的对结果的处理.
    6.必要的关闭和数据库的连结.
JDBC基础(二)

因为是基础篇,所以还是对每一步骤简单说明一下吧:

    前面说是,注册驱动程序有多方法,Class.forName();是一种显式地加载.当一个驱
动程序类被Classloader装载后,在溶解的过程中,DriverManager会注册这个驱动类的实例.
这个调用是自动发生的,也就是说DriverManager.registerDriver()方法被自动调用了,当然
我们也可以直接调用DriverManager.registerDriver()来注册驱动程序,但是,以我的经验.
MS的浏览中APPLET在调用这个方法时不能成功,也就是说MS在浏览器中内置的JVM对该方法的
实现是无效的.
    另外我们还可以利用系统属性jdbc.drivers来加载多个驱动程序:
System.setProperty("jdbc.drivers","driver1:driver2:.....:drivern");多个驱动程序之
间用":"隔开,这样在连结时JDBC会按顺序搜索,直到找到第一个能成功连结指定的URL的驱动
程序.
    在基础篇里我们先不介绍DataSource这些高级特性.

    在成功注册驱动程序后,我们就可以用DriverManager的静态方法getConnection来得
到和数据库连结的引用:
    Connection conn = DriverManager.getConnection(url);
    如果连结是成功的,则返回Connection对象conn,如果为null或抛出异常,则说明没有
和数据库建立连结.
    对于getConnection()方法有三个重载的方法,一种是最简单的只给出数据源即:
getConnection(url),另一种是同时给出一些数据源信息即getConnection(url,Properties),
另外一种就是给出数据源,用户名和密码:getConnection(url,user,passwod),对于数据源信息.
如果我们想在连结时给出更多的信息可以把这些信息压入到一个Properties,当然可以直接压
入用户名密码,别外还可以压入指定字符集,编码方式或默认操作等一些其它信息.
    
    在得到一个连结后,也就是有了和数据库找交道的通道.我们就可以做我们想要的操
作了.
    还是先来介绍一些一般性的操作:
    如果我们要对数据库中的表进行操作,要先缘故绑定一个语句:
    Statement stmt = conn.createStatement();
    然后利用这个语句来执行操作.根本操作目的,可以有两种结果返回,如果执行的查询
操作,返回为结果集ResultSet,如果执行更新操作,则返回操作的记录数int.
    注意,SQL操作严格区分只有两个,一种就是读操作(查询操作),另一种就是写操作(更
新操作),所以,create,insert,update,drop,delete等对数据有改写行为的操作都是更新操作.

    ResultSet rs = stmt.executeQuery("select * from table where xxxxx");
    int x = stmt.executeUpdate("delete from table where ......");
    如果你硬要用executeQuery执行一个更新操作是可以的,但不要把它赋给一个句柄,
当然稍微有些经验的程序员是不会这么做的.
    至于对结果集的处理,我们放在下一节讨论,因为它是可操作的可选项,只有查询操作
才返回结果集,对于一次操作过程的完成,一个非常必要的步骤是关闭数据库连结,在你没有了
解更多的JDBC知识这前,你先把这一步骤作为JDBC操作中最最重要的一步,在以后的介绍中我会
不断地提醒你去关闭数据库连结!!!!!!!!!!!

    按上面介绍的步骤,一个完成的例子是这样的:(注意,为了按上面的步骤介绍,这个例
子不是最好的)
    try{
        Class.forName("org.gjt.mm.mysql.Driver");
    }catch(Exception e){
        System.out.println("没有成功加载驱动程序:"+e.toString());
        return;
    }//对于象我这样的经验,可以直接从e.toString()的简单的几个字判断出异常原因,
     //如果你是一个新手应该选捕获它的子类,如何知道要捕获哪几个异常呢?一个简单
     //的方法就是先不加try{},直接Class.forName("org.gjt.mm.mysql.Driver");,编
     //译器就会告诉你要你捕获哪几个异常了,当然这是偷机取巧的方法,最好还是自己
     //去看JDK文档,它会告诉你每个方法有哪些异常要你捕获.
    Connection conn = null;
    try{
        conn = DriverManager.getConnection(
                        "jdbc:mysql://host:3306/mysql",
                        "user",
                        "passwd");
        Statement stmt = conn.createStatement();
        ResultSet rs = stmt.executeQuery("select * from table");
        //rs 处理
        [rs.close();]
        [stmt.close();]
    }
    catch(Exception e){
        System.out.println("数据库操作出现异常:"+e.toString());
    }
    finally{
        try{conn.close();}catch(Exception){}
    }//不管你以前是学习到的关于数据库流程是如何操作的,如果你相信我,从现在开始,
     //请你一定要把数据库关闭的代码写到finally块中,切切!
JDBC基础(三)

关于Statement对象:
    前面说过,Statement对象是用来绑定要执行的操作的,在它上面有三种执行方法:
即用来执行查询操作的executeQuery(),用来执行更新操作的executeUpdate()和用来执行
动态的未知的操作的execute().
    JDBC在编译时并不对要执行的SQL语句检测,只是把它看着一个String,只有在驱动
程序执行SQL语句时才知道正确与否.
    一个Statement对象同时只能有一个结果集在活动.这是宽容性的,就是说即使没有
调用ResultSet的close()方法,只要打开第二个结果集就隐含着对上一个结果集的关闭.所以
如果你想同时对多个结果集操作,就要创建多个Statement对象,如果不需要同时操作,那么可
以在一个Statement对象上须序操作多个结果集.
    
    这里我不得不特别说明一下,很多人会用一个Statement进行嵌套查询,然后就来问
我说为什么不能循环?道理上面已经说清楚了.我们来详细分析一下嵌套查询:
    Connection conn = null;
    Statement stmt = null;
    conn = .......;
    stmt = conm.createStatement(xxxxxx);
    ResultSet rs = stmt.executeQuery(sql1);
    while(rs.next()){
        str = rs.getString(xxxxx);
        ResultSet rs1 = stmt.executeQuery("select * from 表 where 字段=str");
    }
当stmt.executeQuery("select * from 表 where 字段=str");赋给rs1时,这时隐含的操作
是已经关闭了rs,你还能循环下去吗?
所以如果要同时操作多个结果集一定要让它他绑定到不同的Statement对象上.好在一个connection
对象可以创建任意多个Statement对象,而不需要你重新获取连结.

    关于获取和设置Statement的选项:只要看看它的getXXX方法和setXXX方法就明白了,这儿
作为基础知识只提一下以下几个:
    setQueryTimeout,设置一个SQL执行的超时限制.
    setMaxRows,设置结果集能容纳的行数.
    setEscapeProcessing,如果参数为true,则驱动程序在把SQL语句发给数据库前进行转义替
换,否则让数据库自己处理,当然这些默认值都可以通过get方法查询.

    Statement的两个子类:
    PreparedStatement:对于同一条语句的多次执行,Statement每次都要把SQL语句发送给数据
库,这样做效率明显不高,而如果数据库支持预编译,PreparedStatement可以先把要执行的语句一次发
给它,然后每次执行而不必发送相同的语句,效率当然提高,当然如果数据库不支持预编译,
PreparedStatement会象Statement一样工作,只是效率不高而不需要用户工手干预.
    另外PreparedStatement还支持接收参数.在预编译后只要传输不同的参数就可以执行,大大
提高了性能.
        
    PreparedStatement ps = conn.prepareStatement("select * from 表 where 字段=?");
    ps.setString(1,参数);
    ResultSet rs = ps.executeQuery();
    
    CallableStatement:是PreparedStatement的子类,它只是用来执行存储过程的.
    CallableStatement sc = conn.prepareCall("{call query()}");
    ResultSet rs = cs.executeQuery();
    
    关于更高级的知识我们在JDBC高级应用中介绍.
JDBC基础(四)

    作为基础知识的最后部分,我们来说一说结果集的处理,当然是说对一般结果集的处理.
至于存储过程返回的多结果集,我们仍然放在高级应用中介绍.
    SQL语句如何执行的是查询操作,那就要返回一个ResultSet对象,要想把查询结果最后
明白地显示给用户,必须对ResultSet进行处理.ResultSet返回的是一个表中符合条件的记录,对
ResultSet的处理要逐行处理,而对于每一行的列的处理,则可以按任意顺序(注意,这只是JDBC规
范的要求,有些JDBC实现时对于列的处理仍然要求用户按顺序处理,但这是极少数的).事实上,虽
然你可以在处理列的时候可以按任意顺序,但如果你按从左到右的顺序则可以得到较高的性能.

    这儿从底层来讲解一下ResultSet对象,在任何介绍JDBC的书上你是不会获得这样的知
识的,因为那是数据库厂商的事.ResultSet对象实际维护的是一个二维指针,第一维是指向当前
行,最初它指向的是结果集的第一行之前,所以如果要访问第一行,就要先next(),以后每一行都
要先next()才能访问,然后第二维的指针指向列,只要当你去rs.getXXX(列)时,才通过
Connection再去数据库把真实的数据取出来,否则没有什么机器能真的把要取的数据都放在内
存中.
    所以,千万要记住,如果Connection已经关闭,那是不可能再从ResultSet中取到数据的.
有很多人问我,我可不可以取到一个ResultSet把它写到Session中然后关闭Connection,这样就
不要每次都连结了.我只能告诉你,你的想法非常好,但,是错误的!当然在javax.sql包中JDBC高
级应用中有CacheRow和WebCacheRow可以把结果集缓存下来,但那和我们自己开一个数据结构把
ResultSet的行集中所有值一次取出来保存起来没有什么两样.
    访问行中的列,可以按字段名或索引来访问.下面是一个简单的检索结果的程序:

    ResultSet rs = stmt.executeQuery("select a1,a2,a3 from table");
    while(rs.next()){
        int i = rs.getInt(1);
        String a = rs.getString("a2");
        ..............
    }

    对于用来显示的结果集,用while来进行next()是最普通的,如果next()返回false,则
说明已经没有可用的行了.但有时我们可能连一行都没有,而如果有记录又不知道是多少行,这时
如果要对有记录和没有记录进行不同的处理,应该用以下流程进行判断:

    if(rs.next()){
        //因为已经先next()了,所经对记录应该用do{}while();来处理
        do{
            int i = rs.getInt(1);
            String a = rs.getString("a2");
        }while(rs.next());
    }
    esle{
        System.out.println("没有取得符合条件的记录!");
    }

    类型转换:
    ResultSet的getXXX方法将努力把结果集中的SQL数据类型转换为JAVA的数据类型,事实
大多数类型是可以转换的,但仍然有不少糊弄是不能转换的,如你不能将一个SQL的float转换成
JAVA的DATE,你无法将 VARCHAR "我们"转换成JAVA的Int.

    较大的值:
    对于大于Statement中getMaxFieldSize返回值的值,用普通的getBytes()或getString()
是不能读取的,好在JAVA提供了读取输入浪的方法,对于大对象,我们可以通过rs.getXXXStream()
来得到一个InputStream,XXX的类型包括Ascii,Binay,Unicode.根据你存储的字段类型来使用不
同的流类型,一般来说,二进制文件用getBinayStream(),文本文件用getAsciiStyream(),对于
Unicode字符的文本文件用getUnicodeStream(),相对应的数据库字段类型应该为:Blob,Clob和
Nlob.

    获取结果集的信息:
    大多数情况下编程人员对数据库结构是了解的,可以知道结果集中各列的情况,但有时并
不知道结果集中有哪些列,是什么类型.这时可以通过getMetaData()来获取结果集的情况:

    ResulSetMetaData rsmd = rs.getMetaData();
    rsmd.getColumnCount()返回列的个数.
    getColumnLabel(int)返回该int所对应的列的显示标题
    getColumnName(int)返回该int所对应的列的在数据库中的名称.
    getColumnType(int)返回该int所对应的列的在数据库中的数据类型.
    getColumnTypeName(int)返回该int所对应的列的数据类型在数据源中的名称.
    isReadOnly(int)返回该int所对应的列是否只读.
    isNullable(int)返回该int所对应的列是否可以为空
posted @ 2006-01-09 10:04 javaGrowing 阅读(334) | 评论 (0)编辑 收藏

一、什么是嵌套类及内部类?
    可以在一个类的内部定义另一个类,这种类称为嵌套类(nested classes),它有两种类型: 静态嵌套类和非静态嵌套类。静态嵌套类使用很少,最重要的是非静态嵌套类,也即是被称作为内部类(inner)。嵌套类从JDK1.1开始引入。其中inner类又可分为三种:
    其一、在一个类(外部类)中直接定义的内部类;
    其二、在一个方法(外部类的方法)中定义的内部类;
    其三、匿名内部类。
下面,我将说明这几种嵌套类的使用及注意事项。


 
二、静态嵌套类
    如下所示代码为定义一个静态嵌套类,

public class StaticTest {
  private static String name = "javaJohn";
  private String id = "X001";

  static class Person{
    private String address = "swjtu,chenDu,China";
    public String mail = "josserchai@yahoo.com";//内部类公有成员
    public void display(){
      //System.out.println(id);//不能直接访问外部类的非静态成员
      System.out.println(name);//只能直接访问外部类的静态成员
      System.out.println("Inner "+address);//访问本内部类成员。
    }
  }

   public void printInfo(){
     Person person = new Person();
     person.display();

     //System.out.println(mail);//不可访问
     //System.out.println(address);//不可访问

     System.out.println(person.address);//可以访问内部类的私有成员
     System.out.println(person.mail);//可以访问内部类的公有成员

   }

   public static void main(String[] args) {
       StaticTest staticTest = new StaticTest();
       staticTest.printInfo();
   }
}

     在静态嵌套类内部,不能访问外部类的非静态成员,这是由Java语法中"静态方法不能直接访问非静态成员"所限定。
若想访问外部类的变量,必须通过其它方法解决,由于这个原因,静态嵌套类使用很少。注意,外部类访问内
部类的的成员有些特别,不能直接访问,但可以通过内部类实例来访问,这是因为静态嵌套内的所有成员和方法默认为
静态的了。同时注意,内部静态类Person只在类StaticTest 范围内可见,若在其它类中引用或初始化,均是错误的。

三、在外部类中定义内部类
    如下所示代码为在外部类中定义两个内部类及它们的调用关系:

class Outer{
  int outer_x = 100;

  private class Inner{//私有的内部类
   public int y = 10;
   private int z = 9;
   int m = 5;
   public void display(){
     System.out.println("display outer_x:"+ outer_x);
   }

   private void display2(){
    System.out.println("display outer_x:"+ outer_x);
   }

}

 public Inner getInner(){//即使是对外公开的方法,外部类也无法调用
   return new Inner();
 }

 void test(){ 
   Inner inner = new Inner(); //可以访问
   inner.display();
   inner.display2();
   //System.out.println("Inner y:" + y);//不能访问内部内变量
   System.out.println("Inner y:" + inner.y);//可以访问
   System.out.println("Inner z:" + inner.z);//可以访问
   System.out.println("Inner m:" + inner.m);//可以访问

   InnerTwo innerTwo = new InnerTwo();
   innerTwo.show();
 }

 class InnerTwo{
   Inner innerx = getInner();//可以访问
   public void show(){
    //System.out.println(y);//不可访问Innter的y成员
    //System.out.println(Inner.y);//不可直接访问Inner的任何成员和方法
    innerx.display();//可以访问
    innerx.display2();//可以访问
    System.out.println(innerx.y);//可以访问
    System.out.println(innerx.z);//可以访问
    System.out.println(innerx.m);//可以访问
   }
 }

}

public class Test
{
 public static void main(String args[]){
  
  Outer outer = new Outer();
 // Outer.Inner a=outer.getInner();//Inner类是私有的,外部类不能访问,如果Inner类是public ,则可以。
  outer.test();
 }
}

    内部类Inner及InnterTwo只在类Outer的作用域内是可知的,如果类Outer外的任何代码尝试初始化类Inner或使用它,编译就不会通过。同时,内部类的变量成员只在内部内内部可见,若外部类或同层次的内部类需要访问,需采用示例程序
中的方法,不可直接访问内部类的变量。

四、在方法中定义内部类
    如下所示代码为在方法内部定义一个内部类:

public class FunOuter { 
 int out_x = 100;

 public void test(){
  class Inner{
    String x = "x";
    void display(){
       System.out.println(out_x);
    }
  }
 Inner inner = new Inner();
 inner.display();
}

public void showStr(String str){
  //public String str1 = "test Inner";//不可定义,只允许final修饰
  //static String str4 = "static Str";//不可定义,只允许final修饰
  String str2 = "test Inner";
  final String str3 = "final Str";
  class InnerTwo{
    public void testPrint(){
    System.out.println(out_x);//可直接访问外部类的变量
    //System.out.println(str);//不可访问本方法内部的非final变量
    //System.out.println(str2);//不可访问本方法内部的非final变量
    System.out.println(str3);//只可访问本方法的final型变量成员
    }
  }
  InnerTwo innerTwo = new InnerTwo();
  innerTwo.testPrint();
}

public void use(){
   //Inner innerObj = new Inner();//此时Inner己不可见了。
   //System.out.println(Inner.x);//此时Inner己不可见了。
}


 public static void main(String[] args) {
  FunOuter outer = new FunOuter();
  outer.test();
 }
}


    从上面的例程我们可以看出定义在方法内部的内部类的可见性更小,它只在方法内部 可见,在外部类(及外部类的其它方法中)中都不可见了。同时,它有一个特点,就是方法内的内部类连本方法的成员变量都不可访问,它只能访问本方法的final型成员。同时另一个需引起注意的是方法内部定义成员,只允许final修饰或不加修饰符,其它像static等均不可用。

五、匿名内部类
    如下所示代码为定义一个匿名内部类:匿名内部类通常用在Java的事件处理上


import java.applet.*;
import java.awt.event.*;

    public class AnonymousInnerClassDemo extends Applet{
    public void init(){
        addMouseListener(new MouseAdapter(){
            public void mousePressed(MouseEvent me){
             showStatus("Mouse Pressed!");
        }
        });
    }
    public void showStatus(String str){
        System.out.println(str);
    }
    }


在上面的例子中,方法addMouseListener接受一个对象型的参数表达式,于是,在参数里,我们定义了一个匿名内部类,这个类是一个MouseAdapter类型的类,同时在这个类中定义了一个继承的方法mousePressed,整个类做为一个参数。这个类没有名称,但是当执行这个表达式时它被自动实例化。同时因为,这个匿名内部类是定义在AnonymousInnerClassDemo 类内部的,所以它可以访问它的方法showStatus。这同前面的内部类是一致的。

六、内部类使用的其它的问题

     通过以上,我们可以清楚地看出内部类的一些使用方法,同时,在许多时候,内部类是在如Java的事件处理、或做为值对象来使用的。同时,我们需注意最后一个问题,那就是,内部类同其它类一样被定义,同样它也可以继承外部其它包的类和实现外部其它地方的接口。同样它也可以继承同一层次的其它的内部类,甚至可以继承外部类本身。下面我们给出最后一个例子做为结束:


public class Layer {
  //Layer类的成员变量
  private String testStr = "testStr";

  //Person类,基类
  class Person{
    String name;
    Email email;
    public void setName(String nameStr){
     this.name = nameStr;
    }

    public String getName(){
      return this.name;
    } 

    public void setEmail(Email emailObj){
      this.email = emailObj;
    }

    public String getEmail(){
      return this.email.getMailStr();
    }

    //内部类的内部类,多层内部类
    class Email{ 
     String mailID;
     String mailNetAddress;

     Email(String mailId,String mailNetAddress){
     this.mailID = mailId;
     this.mailNetAddress = mailNetAddress;
     }

     String getMailStr(){
        return this.mailID +"@"+this.mailNetAddress;
     }
    } 
  }

   //另一个内部类继承外部类本身
   class ChildLayer extends Layer{
     void print(){
       System.out.println(super.testStr);//访问父类的成员变量
     }
   }

   //另个内部类继承内部类Person
    class OfficePerson extends Person{ 
     void show(){
       System.out.println(name);
       System.out.println(getEmail()); 
     }
    }

    //外部类的测试方法 
    public void testFunction(){
      //测试第一个内部类
      ChildLayer childLayer = new ChildLayer();
      childLayer.print();

      //测试第二个内部类
      OfficePerson officePerson = new OfficePerson();
      officePerson.setName("abner chai");
      //注意此处,必须用对象.new 出来对象的子类对象
      //而不是Person.new Email(...)
      //也不是new Person.Email(...)
      officePerson.setEmail(officePerson.new Email("josserchai","yahoo.com"));

       officePerson.show();
    }

     public static void main(String[] args) {
      Layer layer = new Layer();
      layer.testFunction();
     }
}


posted @ 2006-01-07 16:45 javaGrowing 阅读(330) | 评论 (0)编辑 收藏

用缺省设置创建时,ResultSet 是一种只能访问一次(one-time-through)、只能向前访问(forward-only)和只读的对象。您只能访问数据一次,如果再次需要该 数据,必须重新查询数据库。

然而,并不只有这一种方式。通过设置 Statement 对象上的参数,您可以控制它产生的 ResultSet。例如:

...
         Class.forName(driverName);
         db = DriverManager.getConnection(connectURL);
         Statement statement = db.createStatement(
                            ResultSet.TYPE_SCROLL_SENSITIVE,
                                                  ResultSet.CONCUR_UPDATABLE
                        );         
         
         String orderElName = xmlfileEl.getElementsByTagName("order").item(0)
                                       .getFirstChild().getNodeValue();
...

这个 Statement 现在将产生可以更新并将应用其他数据库用户所作更改的 ResultSet。您还可以在这个 ResultSet 中向前和向后移动。

第一个参数指定 ResultSet 的类型。其选项有:

TYPE_FORWARD_ONLY:缺省类型。只允许向前访问一次,并且不会受到其他用户对该数据库所作更改的影响。
TYPE_SCROLL_INSENSITIVE:允许在列表中向前或向后移动,甚至可以进行特定定位,例如移至列表中的第四个记录或者从当前位置向后移动两个记录。不会受到其他用户对该数据库所作更改的影响。
TYPE_SCROLL_SENSITIVE:象 TYPE_SCROLL_INSENSITIVE 一样,允许在记录中定位。这种类型受到其他用户所作更改的影响。如果用户在执行完查询之后删除一个记录,那个记录将从 ResultSet 中消失。类似的,对数据值的更改也将反映在 ResultSet 中。
第二个参数设置 ResultSet 的并发性,该参数确定是否可以更新 ResultSet。其选项有:

CONCUR_READ_ONLY:这是缺省值,指定不可以更新 ResultSet
CONCUR_UPDATABLE:指定可以更新 ResultSet
posted @ 2006-01-06 16:31 javaGrowing 阅读(4420) | 评论 (1)编辑 收藏

     摘要: 作者:Rod Johnson 译者:yanger,taowen 校对:taowen 关于Spring Framework,今年夏天你可能已经听见很多的议论。在本文中,我将试图解释Spring能完成什么,和我怎么会认为它能帮助你开发J2EE应用程序。 又来一个framework? 你可能正在想“不过是另外一个的framework”。当已经有许多...  阅读全文
posted @ 2006-01-06 09:24 javaGrowing 阅读(330) | 评论 (0)编辑 收藏

从 ASP.NET 服务器控件插入客户端脚本

Scott Mitchell

2003 年 8 月

适用于:
    Microsoft® ASP.NET

前提条件:本文假设读者熟悉 ASP.NET。

难度: 2

摘要:尽管从技术角度讲,ASP.NET 服务器控件的所有功能都可以在服务器端执行,但通常情况下通过添加客户端脚本可以大大增强服务器控件的可用性。本文将探讨服务器控件发送客户端脚本的两种方法,还将构建两个使用这些技术的服务器控件:PopupGreeting,一个在首次加载的 Web 页面上显示带有特定消息的客户端模式对话框的服务器控件;ConfirmButton,一个增强的 Button Web 控件,如果用户点击此按钮,则在发回 Web 窗体前向用户显示一个 JavaScript confirm() 的对话框。(本文包含一些指向英文站点的链接。)

下载 InjectingClientSideScript.msi

目录

简介
使用 RegisterStartupScript() 和 RegisterClientScriptBlock() 添加客户端脚本块
探讨 IsStartupScriptRegistered() 和 IsClientScriptBlockRegistered()
从 ASP.NET 服务器控件发送客户端脚本块
发送 ASP.NET 服务器 Web 控件的 HTML 属性
小结

简介

尽管从技术角度讲,Microsoft® ASP.NET 服务器控件的所有功能都可以在服务器端执行,但通常情况下通过添加客户端脚本可以大大增强服务器控件的可用性。例如,ASP.NET 验证 Web 控件可以在服务器端执行所有的验证检查。但是,对于高版本浏览器,验证 Web 控件也会发送客户端脚本,以在客户端进行验证。这就是说,这些浏览器的用户可以获得响应效果更好的动态体验。

在开发 ASP.NET 服务器控件时,您不妨问问自己,如何才能通过使用客户端脚本来增强可用性。一旦找到可行的方案,其他要做的就是增强服务器控件的功能,以使其发送合适的客户端脚本。

ASP.NET 服务器控件可以发送两种客户端脚本:

  • 客户端脚本块
  • 客户端 HTML 属性

客户端脚本块通常是用 JavaScript 编写的,其中通常包含在发生特定的客户端事件时执行的函数。客户端 HTML 属性提供将客户端事件与客户端脚本联系在一起的方法。例如,以下的 HTML 页面中包含了客户端脚本块,脚本块中包含了名为 doClick() 的函数。该页面同时还包含一个按钮(通过 <input> HTML 元素创建),这个按钮的 onclick 属性与 doClick() 函数绑定。也就是说,只要用户单击该按钮,就开始执行 doClick() 函数中的客户端代码。在本示例中,将显示一个弹出式对话框(图 1)。

<html>
<body>
<form>
<script language="JavaScript">
<!--
function doClick() {
alert("You clicked me!");
}
// -->
</script>

<input type="button" onclick="doClick()" value="Click Me!" />
</form>
</body>
</html>

图 1 是单击“Click Me!”按钮时 HTML 页面的屏幕快照。

图 1:单击“Click Me!”按钮时显示的弹出式对话框

对于以上 HTML 页面中的客户端脚本,有几点值得注意。首先,客户端脚本块包含在 HTML 注释(<!---->)中。之所以这样,是因为如果不将脚本块放入 HTML 注释中,那些不能识别脚本的旧式浏览器就会显示 <script> 块的内容。此外,还要注意,脚本块中 HTML 注释的结束标记前有一个 JavaScript 注释,即 //。这是因为旧版本的 Netscape 在遇到 --> 时,会抛出 JavaScript 分析异常,因此必须将其注释掉。幸运的是,现代的浏览器已不需要这一额外操作,所以在为 Intranet 或其他由浏览器控制的环境开发 Web 页面时,您就不必采取此类预防措施了。

如果您对客户端脚本不是很熟悉,alert(string) 函数的作用就是显示一个模式弹出式对话框,对话框中包含的消息由 string 参数指定。所有 HTML 元素都有若干个可以绑定一段客户端 JavaScript 代码的客户端属性(例如,onclickonmouseoveronmouseoutonfocusonblur 等等)。例如,在上面的 HTML 页面中,<input> 元素的 onclick 属性绑定到 doClick() 函数,因此在单击该按钮时将执行 doClick() 函数。有关 JavaScript 事件及其关联的 HTML 属性的列表,请参阅 Introduction to Dynamic HTML 一文。有关客户端 JavaScript 的详细信息,请参阅 HTML and Dynamic HTML 一文。

在本文中,我们将学习如何在 ASP.NET 服务器控件中发送客户端脚本块和 HTML 元素属性。我们首先讨论如何使用 System.Web.UI.Page 类中的两个方法来向 ASP.NET Web 页面添加客户端脚本块,这两个方法是 RegisterStartupScript()RegisterClientScriptBlock()。 掌握这一知识后,我们将构建一个简单的服务器控件,让这个控件在每次加载页面时显示一个客户端弹出式对话框。之后,我们再来了解如何将 HTML 属性添加到 ASP.NET 服务器控件的 HTML 元素。最后,我们将归纳所有知识,实际构建一个 ConfirmButton Web 控件,当单击这个控件时,将向用户提示一个对话框,询问用户是否要继续。

使用 RegisterStartupScript() 和 RegisterClientScriptBlock() 添加客户端脚本块

System.Web.UI.Page 类包含的两个方法可以将客户端脚本代码发送到由 ASP.NET Web 页面提供的 HTML 中:

  • RegisterStartupScript(key, script)
  • RegisterClientScriptBlock(key, script)

这两个方法都接受两个字符串作为输入。第二个参数 script 是要插入到页面中的客户端脚本,包括 <script> 的起始标记和终止标记。第一个参数 key 是插入的客户端脚本的唯一标识符。

这两个方法唯一的不同之处在于从“何处”发送脚本块。RegisterClientScriptBlock() 在 Web 窗体的开始处(紧接着 <form runat="server"> 标识之后)发送脚本块,而 RegisterStartupScript() 在 Web 窗体的结尾处(在 </form> 标识之前)发送脚本块。

为什么会有两种不同的方法来发送客户端脚本?要更好地了解这一点,我们必须首先了解,客户端脚本可以分为两类:一类是在加载页面后立即运行的代码, 一类是在发生某些客户端事件时才运行的代码。前者的常见示例是将焦点设置到文本框的客户端代码。例如,当您访问 Google 时,在页面加载后就会执行一小段客户端代码,以自动将焦点设置到搜索文本框。

以下是后一类代码(为响应客户端事件而运行的代码)的示例。具体而言,在该示例中,单击按钮时将显示一个弹出式对话框:

<html>
<body>
<form>
<script language="JavaScript">
<!--
function displayPopup() {
alert("Hello, world.");
}
// -->
</script>

<input type="button" value="Click Me!" onclick="displayPopup()" />
</form>
</body>
</html>

在这段代码中,<input> 标记中的 onclick="displayPopup()" 用于指明在单击按钮时,JavaScript 函数 displayPopup() 应该运行。

RegisterStartupScript() 方法可用于添加要在加载页面后运行的脚本块。通过这种方法添加的脚本块位于 Web 窗体的结尾处,因为必须在脚本运行前定义脚本要修改的 HTML 元素。也就是说,如果您要使用客户端脚本将焦点设置到文本框,必须确保文本框的 HTML 标记位于设置该文本框的焦点的脚本之前。例如,下面的 HTML 将显示一个文本框,并将焦点设置到该文本框:

<input type="text" id="myTextBox" />

<script language="JavaScript">
<!--
document.getElementById("myTextBox").focus();
// -->
</script>

相反,以下 HTML 不会将焦点设置到文本框,因为文本框是在脚本块“之后”定义的:

<script language="JavaScript">
<!--
document.getElementById("myTextBox").focus();
// -->
</script>

<input type="text" id="myTextBox" />

因此,RegisterStartupScript() 方法将 <script> 块置于 Web 窗体的结尾处,以保证在执行客户端脚本之前已声明 Web 窗体中的所有 HTML 元素。

RegisterClientScriptBlock() 方法用于为响应客户端事件而执行的脚本代码。通过此方法发送的脚本块位于 Web 页面的开始处,因为这种方法不要求将脚本块置于所有 HTML 元素之后。

探讨 IsStartupScriptRegistered() 和 IsClientScriptBlockRegistered()

RegisterStartupScript()RegisterClientScriptBlock() 方法之外,Page 类还包含两个在发送客户端脚本时常用的辅助方法:

  • IsStartupScriptRegistered(key)
  • IsClientScriptBlockRegistered(key)

如上所述,在使用 RegisterStartupScript()RegisterClientScriptBlock() 插入客户端脚本块时,提供了一个唯一标识脚本块的关键字。这两个方法都接受一个输入(字符串 key),并返回一个布尔值,以指示带有指定关键字的脚本块是否已添加到页面中。具体地说,如果带有特定 key 的脚本块已经注册,这些方法将返回 True,否则将返回 False。

要了解如何使用这两个方法,可以看一看 ASP.NET 验证 Web 控件,如 RequiredFieldValidator、RegularExpressionValidator 等等。这些控件都会用到一个常用的验证 JavaScript 文件 (WebValidation.js),该文件位于 ASP.NET Web 应用程序的 aspnet_client/system_web/版本号 目录中。因此,所有这些控件都会发送相同的脚本块,这个脚本块将调用在 WebValidation.js 文件中定义的相应的 JavaScript 函数,以启动客户端的验证过程。要完成这个过程,这些控件会使用 Page 类的 RegisterClientScriptBlock() 方法,并使用关键字 ValidatorIncludeScript

接下来要考虑的是,如果一个 ASP.NET Web 页面中包含多个验证 Web 控件,会出现什么情况呢?所有这些 Web 控件都要使用相同的关键字发送相同的脚本块。如果使用这个关键字调用两次 RegisterClientScriptBlock()RegisterStartupScript() 方法,则第二次调用会被认为是复制脚本块而被忽略。因此,即使一个 Web 页面上有多个验证控件,也只是发送一个公共脚本块的实例。但是,请注意,除第一个控件之外的其他所有验证 Web 控件都会构建要发送的公共客户端脚本,而这只是在浪费时间。

这时就应该使用 IsClientScriptBlock()IsStartupScript() 方法。这样一来,验证 Web 控件就不会先花时间构建要发送的客户端代码,而是先检查是否已经存在使用关键字 ValidatorIncludeScript 注册的脚本。如果存在,控件就会放弃构建客户端脚本块,因为脚本块已经由页面上的其他验证控件构建了。

因此,每次构建客户端脚本时,应该首先调用 IsClientScriptBlock()IsStartupScript() 方法,以确定是否需要生成客户端脚本。在下面一节,我们将看到一些示例,在这些示例中,IsClientScriptBlock()IsStartupScript() 方法先后与 RegisterClientScriptBlock()RegisterStartupScript() 方法结合使用。

从 ASP.NET 服务器控件发送客户端脚本块

请记住,RegisterStartupScript()RegisterClientScriptBlock() 方法是 System.Web.UI.Page 类的方法。幸运的是,可以容易地从 ASP.NET 服务器控件调用这两个方法,因为 System.Web.UI.Control 类(所有 ASP.NET 服务器控件都直接或间接地从这个类导出)有一个包含对 Page 实例的引用的 Page 属性,而这个 Page 实例包含服务器控件。因此,要从 ASP.NET 服务器控件添加客户端脚本块,您只需使用下面的语法:

this.Page.RegisterClientScriptBlock(key, script);

通常,添加客户端脚本块这个任务会使用 OnPreRender() 方法来处理,这个方法在控件生命周期的预呈现阶段执行。

让我们创建一个只显示客户端弹出式对话框的 ASP.NET 服务器控件。此示例将说明构建一个发送客户端脚本的控件是很容易的。

首先,在 Microsoft® Visual Studio® .NET 中创建一个新的 Web Control Library(Web 控件库)项目。这将创建一个只有一个类的新项目,这个类从 System.Web.UI.WebControls.WebControl 导出。但是,我们希望这个类从 System.Web.UI.Control 类导出。为什么呢?因为 WebControl 类用于支持显示为 HTML 元素的服务器控件,而 Control 类则用于不会显示为 HTML 元素的服务器控件。

大多数内置的 ASP.NET 服务器控件都会发送一个 HTML 元素。例如,TextBox Web 控件发送一个 <input> 元素,其类型属性设置为 text;DataGrid Web 控件发送一个 <table> 元素,为每条要显示的记录发送 <tr> 元素,为每个字段发送 <td> 列。但是,不是所有的控件都需要发送 HTML 元素。例如,Literal 控件只是按原样输出它的 Text 属性,而不将这个属性放在 HTML 元素中。同样,Repeater 也不将其输出放在 HTML 元素中。那些显示为 HTML 元素的服务器控件,如 TextBox、Button、DataGrid 等等,是从 System.Web.UI.WebControls.WebControl 类导出的,而那些产生 HTML 元素的控件,如 Literal、Repeater 等,是从 System.Web.UI.Control 类导出的。

既然我们要创建的服务器控件不可见(它只是发送一个显示弹出式控件的客户端脚本块),这个控件最好从 System.Web.UI.Control 导出,而不是从 System.Web.UI.WebControls.WebControl 导出。

这个控件只需要两个属性:

  • PopupMessage - 表示要在弹出式对话框中显示的消息的字符串。
  • Enabled - 表示是否启用控件的布尔值。如果启用控件,则显示弹出式对话框,否则不显示。(必须添加一个 Enabled 属性,是因为导出该控件的 Control 类不包括 Enabled 属性,此属性只是隐含地由那些从 WebControl 导出的控件使用。)

除了这两种属性之外,我们需要覆盖 OnPreRender() 方法。在这里,我们需要调用 RegisterStartupScript(),并传递控件唯一的关键字和恰当的客户端脚本以显示弹出式对话框。这个类的完整代码如下所示:

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

namespace ClientSideScript
{
/// <summary>
/// WebCustomControl1 的摘要描述。
/// </summary>
[DefaultProperty("Text"),
ToolboxData("<{0}吐舌笑脸opupGreeting runat=server></{0}吐舌笑脸opupGreeting>")]
public class PopupGreeting : System.Web.UI.Control
{
[Bindable(true),
Category("Appearance"),
DefaultValue("")]
public string PopupMessage
{
get
{
// 检查 ViewState 中是否存在该项目
object popupMessage = this.ViewState["PopupMessage"];
if (popupMessage != null)
return this.ViewState["PopupMessage"].ToString();
else
return "Welcome to my Web site!";
}

set
{
// 指定 ViewState 变量
ViewState["PopupMessage"] = value;
}
}

[Bindable(true),
Category("Appearance"),
DefaultValue("")]
public bool Enabled
{
get
{
// 检查 ViewState 中是否存在该项目
object enabled = this.ViewState["Enabled"];
if (enabled != null)
return (bool) this.ViewState["Enabled"];
else
return true;
}

set
{
// 指定 ViewState 变量
ViewState["Enabled"] = value;
}
}


protected override void OnPreRender(EventArgs e)
{
base.OnPreRender电子邮件;

string scriptKey = "intoPopupMessage:" + this.UniqueID;

if (!Page.IsStartupScriptRegistered(scriptKey) && this.Enabled &&
!Page.IsPostBack)
{
string scriptBlock =
@"<script language=""JavaScript"">
<!--
alert(""%%POPUP_MESSAGE%%"");
// -->
</script>";
scriptBlock = scriptBlock.Replace("%%POPUP_MESSAGE%%", this.PopupMessage);

Page.RegisterStartupScript(scriptKey, scriptBlock);
}
}
}
}

请记住下面两件事:首先,EnabledPopupMessage 属性保存在 ViewState 中,这样在回传时这些值可以始终保持一致; 其次,在 OnPreRender() 方法中,用于脚本块的关键字是文本 intoPopupMessage: 加上控件的 UniqueID 属性。如果使用一个硬编码的关键字,则当页面中有多个控件时,只有第一个控件能够注册其脚本块,因此只显示一个弹出式对话框。通过在脚本块关键字中使用 UniqueID,就能保证该控件的每个实例都能获取其脚本块。

在注册脚本块之前,代码首先检查三个条件:

  1. 没有使用同一关键字注册的脚本。这当然是不可能的,因为每个控件实例都应该有一个 UniqueID 属性值。但是,不妨先练习使用 IsStartupScriptRegistered() 方法,然后再花时间创建和注册启动脚本。
  2. 控件的 Enabled 属性为 True。
  3. 页面没有被回传。这段代码只允许弹出式对话框在第一次加载页面时显示,而不是在每次回传页面时都显示。我们还可以增添更为灵活的功能,即为该控件添加一个布尔属性,以允许用户指定是否在回传时也生成弹出式对话框。

如果满足这三个条件,则脚本被指定,并且 PopupMessage 属性值被插入到脚本中适当的位置。最后,调用 Page 属性的 RegisterStartupScript() 方法,传入关键字及脚本代码。

PopupGreeting 代码可以从本文结尾处提供的下载中获得。该下载包括名为 ClientSideControlsAndTester 的 Visual Studio .NET 解决方案,其中包含两个项目:

  • ClientSideControls,包含 PopupGreeting 服务器控件
  • ClientSideTester,包括一个为测试 ClientSideControls 而设计的 ASP.NET Web 应用程序

ClientSideControls 项目编译后的程序集名为 ClientSideControls.dll。要在您自己的 ASP.NET Web 应用程序中使用 PopupGreeting 服务器控件,请将 ClientSideControls.dll 文件添加到您的 Web 应用程序的引用中。然后,在设计器中,右键单击 Toolbox(工具箱)并选择“Add/Remove Items . . .”(添加/删除项),再次选择 ClientSideControls.dll 文件。这样就向 Toolbox(工具箱)中添加了名为 PopupGreeting 的新项。然后,您可以从 Toolbox(工具箱)将该控件拖到设计器中。

图 2 显示了 PopupGreeting 控件添加到 Toolbox(工具箱)并添加到设计器后,Visual Studio .NET 的屏幕快照。Toolbox(工具箱)中的 PopupGreeting 控件用红色线圈出,设计器中的 PopupGreeting 输出用蓝色线圈出,在屏幕快照右侧的“Properties”(属性)窗格中可以查看 PopupGreeting 的属性。

图 2:PopupGreeting 服务器控件已添加到 ASP.NET Web 窗体页面

发送 ASP.NET 服务器 Web 控件的 HTML 属性

如上所述,有两种方法可以通过服务器控件发送客户端脚本:

  • 通过使用客户端脚本块
  • 通过 HTML 元素属性

在上一节中,我们探讨了如何使用 Page 类的 RegisterStartupScript()RegisterClientScriptBlock() 方法向 ASP.NET Web 页面添加客户端脚本块。在最后这一节,我们了解如何将 HTML 元素属性添加到服务器控件的 HTML 元素。

在开始之前,请注意这种方法通常只适用于从 System.Web.UI.WebControls.WebControl 类导出的服务器控件,因为从这个类导出的控件会发送某些 HTML 元素。不发送 HTML 元素的服务器控件(如上一节中的 PopupGreeting 服务器控件),则不必写出 HTML 元素属性,因为这些控件运行时不会写出 HTML 元素。

WebControl 类包含一个将 HTML 元素属性添加到由 Web 控件发出的 HTML 元素的方法。该方法称为 AddAttributesToRender(),它只有一个输入参数,即 HtmlTextWriter 的实例。要向 Web 控件添加 HTML 属性,您可以使用 HtmlTextWriter 的以下两个方法之一:

  • AddAttribute()
  • AddStyleAttribute()

AddAttribute() 方法用于将 titleclassstyleonclick 等 HTML 属性添加到 HTML 元素。AddStyleAttribute() 用于将样式设置添加到 HTML 元素,如 background-colorcolorfont-size 等。

AddAttribute() 有几个重载窗体,但在代码中,我们将使用以下窗体:AddAttribute(HtmlTextWriterAttribute, value)。第一个参数,即 HtmlTextWriterAttribute,应该是 HtmlTextWriterAttribute 枚举的成员。该枚举包含像 AlignBgcolorClassOnclick 等项。您可以在 .NET Framework Class Library,HtmlTextWriterAttribute Enumeration 中找到完整的列表。value 输入参数用于指定分配给特定 HTML 属性的值。最后,如果您想添加一个 HtmlTextWriterAttribute 枚举中未定义的 HTML 属性,可以使用 AddAttribute() 方法的替代形式 AddAttribute(attributeName, value),其中的 attributeNamevalue 均为字符串。

为了运用该信息,我们创建一个作为确认按钮的服务器 Web 控件。确认按钮是一种提交按钮,当用户单击此按钮时,将显示一个弹出式对话框,询问用户是否确定要继续操作。用户可以单击“取消”,不提交窗体。此项功能 对用于删除信息的按钮特别有用,因为最终用户(或网站管理员)可能会在无意中单击鼠标删除数据库中的条目,如果没有机会取消,将是非常令人烦恼的事。

为了减少工作量,我们从 System.Web.UI.WebControls.Button 类中导出 ConfirmButton Web 控件,因为这个类本身已完成了涉及呈现提交按钮的所有繁重工作。在导出的类中,我们只需添加一个属性,这样用户可以指定确认消息,然后覆盖按钮的 AddAttributesToRender() 方法,并添加一个属性以处理客户端事件 onclick

首先,在 Visual Studio .NET 中创建一个新的 Web Control Library(Web 控件库)项目,或者在 ClientSideControls 项目中添加一个新的 Web Custom Control(Web 自定义控件)。ConfirmButton 类的完整源代码如下所示:

using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;

namespace ClientSideControls
{
/// <summary>
/// ConfirmButton 的摘要描述。
/// </summary>
[DefaultProperty("Text"),
ToolboxData("<{0}:ConfirmButton runat=server></{0}:ConfirmButton>")]
public class ConfirmButton : Button
{
[Bindable(true),
Category("Appearance"),
DefaultValue("")]
public string PopupMessage
{
get
{
// 检查 ViewState 中是否存在该项目
object popupMessage = this.ViewState["PopupMessage"];
if (popupMessage != null)
return this.ViewState["PopupMessage"].ToString();
else
return "Are you sure you want to continue?";
}

set
{
// 指定 ViewState 变量
ViewState["PopupMessage"] = value;
}
}


protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);

string script = @"return confirm(""%%POPUP_MESSAGE%%"");";
script = script.Replace("%%POPUP_MESSAGE%%",
this.PopupMessage.Replace("\"", "\\\""));

writer.AddAttribute(HtmlTextWriterAttribute.Onclick, script);
}
}
}

首先要注意的是,ConfirmButton 类是从 Button 类导出的。由于 Button 类已包含 Button Web 控件使用的所有属性和方法,因此我们所做的只是添加属性和方法,以在用户单击按钮时显示一个确认对话框。现在我们需要一个属性,即 PopupMessage, 它是要在确认弹出式对话框中显示的消息。默认情况下,这条消息是“Are you sure you want to continue?”(您确定要继续吗?)如果使用 ConfirmButton 来确认删除,可能需要将该消息更改为“This action will permanently delete the selected item. Are you sure you want to do this?”(此操作将永久删除所选项。您确定要继续吗?)

我们只需覆盖一个方法,即 AddAttributesToRender()。在此方法中,我们只要构建当触发 <input> 元素的 onclick 事件时要执行的客户端 JavaScript,然后通过传入的 HtmlTextWriter 对象的 AddAttribute() 方法添加这段 JavaScript。关于这个方法,有一点要注意,必须将 PopupMessage 属性值中的所有双引号实例替换为转义双引号(即 \")。另外还要注意,默认情况下,AddAttribute() 会对第二个参数中的字符进行 HTML 编码。也就是说,ASP.NET Web 页面中如果包含 PopupMessage 属性被设置为“Do you want to continue?”(要继续吗?)的 ConfirmButton,该页面将发送以下 HTML 标记:

<input type="submit" name="ConfirmButton1" 
value="Click Me!" id="ConfirmButton1" onclick="return confirm
(&quot;Do you want to continue?&quot眨眼笑脸;" />

如果您不熟悉 JavaScript 的 confirm(string) 函数,那么请您注意,该函数只接受一个字符串参数,并显示一个带有特定字符串的模式对话框。该对话框中包含两个按钮:“确定”和“取消”。如果单击“确定”,confirm() 函数返回 True,否则返回 False。请注意,onclick 事件将返回 confirm() 函数调用的结果。当通过单击提交按钮来提交表单时,如果提交按钮的 onclick 事件返回 False,则表单未被提交。因此,只有在用户确认后,可以使用 confirm() 函数提交表单。有关 confirm() 的详细信息,请参阅 ASP Warrior 网站中的 Javascript Confirm Form Submission

图 3:操作中的 ConfirmButton

ConfirmButton 在按钮的 onclick 事件处理程序中使用了内嵌的 JavaScript,还可以在 ConfirmButton 的 OnPreRender() 方法的客户端脚本块中创建一个函数,然后调整 onclick 属性以调用该函数。

小结

在本文中,我们探讨了两种通过 ASP.NET 服务器控件插入客户端脚本的方法。第一种方法是使用 Page 类的 RegisterStartupScript()RegisterClientScriptBlock() 方法插入客户端脚本块。第二种方法是向 HTML 元素的属性添加客户端脚本。后者通过覆盖 Web 服务器控件的 AddAttributesToRender() 方法,并使用 HtmlTextWriterAddAttribute() 方法来完成。

我们还在文中介绍了两个简单的服务器控件,它们都利用了客户端脚本来改进其功能。PopupGreeting 控件在页面首次加载时显示一个模式弹出式对话框,ConfirmButton Web 控件在用户单击按钮提交表单时,提示用户进行确认。

您可以在自己的服务器控件中插入客户端脚本,这将显著改善用户体验。本文提供的两个服务器控件相对比较简单,在可用性和独创性上没有什么突出之处。MetaBuilders.com 中展示了很多利用从 ASP.NET 服务器控件中插入客户端脚本而实现的功能,这些功能会给您留下深刻印象。在 MetaBuilders.com,您可以找到一些服务器控件,它们有的可以自动将焦点添加到文本框,有的可以在两个下拉列表之间传递条目,有的可以向下 拉列表中添加或删除条目,还有的可以在一系列下拉列表中显示父子关系的数据,等等。最大的好处是,这些控件是免费的,并包括完整的源代码。

祝大家编程快乐!

作者简介

Scott Mitchell 著有五本关于 ASP/ASP.NET 的书籍,是 4GuysFromRolla.com 网站的创始人,过去五年来一直从事 Microsoft Web 技术方面的研究。Scott 是 ASP 和 ASP.NET 社区非常活跃的一名成员,十分热爱 ASP 和 ASP.NET 技术,并非常愿意帮助其他人了解这些令人振奋的技术。有关 DataGrid、DataList 和 Repeater 控件的详细信息,请参阅 Scott 的著作《ASP.NET Data Web Controls Kick Start》(ISBN 为 0672325012)。

推荐链接:

posted @ 2006-01-05 15:14 javaGrowing 阅读(285) | 评论 (0)编辑 收藏

仅列出标题
共19页: First 上一页 11 12 13 14 15 16 17 18 19 下一页