Saturday, August 26, 2006

JSF中请求过程的生命周期


前面我们讲了JSF中包含的:组件、事件、监听器以及很多好用的组件,这些功能都使得JSF的开发变得更简单。那么这些部分的执行过程是什么样的呢?为了更容易理解,我们讲分析JSF框架的底层Servlet API,这对于你更好的理解JSF的执行过程是有好处的。了解了这些内容也有助于你开发出更好的应用程序,因为你明白底层到底是如何运行的。当然如果你是一个WEB界面开发人员,你完全可以跳过这个部分。
在这一节中我们讲讨论JSF是如何对请求作出响应的。或者说,由包含JSF组件的页面发出的请求是如何被JSF处理的。
图2.4是一个状态图,它显示了当接收到来自客户端发出的请求后JSF是如何处理的,即JSF的请求处理过程。这个过程开始于JSFservlet接收到一个来自客户端的请求(记住JSF是建立在Servlet基础之上的)

图2.4

列表2.2对以上每一个状态进行了注释,其中主要有六个状态,每一个状态之后多会有相应的时间产生。JSF将这些事件交给相应的监听器进行处理,不管监听器是处理某些业务逻辑还是组件,都能够继续跳转到最终的状态,显示响应结果。监视器也可以跳过最终的状态直接显示一个响应结果,比如它可以返回一个二进制内容,执行一个页面跳转或者返回其他与JSF有关的内容比如XML文档或者是一个HTML。
以上六个步骤中有四个可以产生消息,它们分别是:接收请求值(Apply Request Values),执行验证(Process Validations),更新模型值(Update Model Vlaues),和提交应用程序(Invoke Application)。不管是否带有消息,这些状态都能够向用户反馈响应,除非监听器、装饰器或者组件自身直接返回响应。这些状态之后是一个依据时间主线提交应用程序的过程。一系列的组件、校验过程、支持Bean和模型对象将被更新。简单的讲,JSF为你完成了大部分工作:它捕获了请求的详细信息并把它们传输给包含组件和时间的高层视图,而且还更新了相关对象的属性。
表2.2 JSF 各个状态的请求响应过程

状态
描述
触发的事件
还原页面(Restore View)
为选择的页面查找或创建一个组件树。一些组件,比如HtmlCommandButton组件在此状态下将产生动作事件(或者其他事件)
状态事件
接收请求值(Apply Request Values)
比较请求传输的值并更新相应组件的值,也可以使用转换器。如果出现异常将抛出一个异常消息。同时会产生来自请求参数的事件。
状态事件
数据模型事件
动作事件
处理验证(Process Validations)
对每个组件执行验证。可能会产生验证消息。
状态事件
数据模型事件
值变化事件
更新模型值(Update Model Values)
更新与组件相关的所有模型对象和支持Bean的值。可能会产生异常消息。
状态事件
数据模型事件
提交应用程序(Invoke Application)
调用注册的监听器。缺省的监听器将执行命令组件(比如HtmlCommandButton)的事件方法或者选择下一个要显示的视图。
状态事件
动作事件
返回相应(Render Response)
显示当前逻辑指定的下一个响应页面。
状态事件

为了让你对此有一个更清晰的了解。我们将通过一个hello world的例子展示JSF处理请求过程的整个生命周期。这个例子来自于我们前面章节讲述的hello.jsp。列表2.1显示了实际上Http的处理过程。
列表2.1 Http 的请求过程
POST /jia-hello-world/faces/hello.jsp HTTP/2.1 (1)接收来自URL的请求
Host: deadlock:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:2.2.1)
Gecko/20021130
Accept: text/xml,application/xml,application/xhtml+xml,text/html;
q=0.9,text/plain;q=0.8,video/x-mng,image/png,
image/jpeg,image/gif;q=0.2,text/css,*/*;q=0.1
Accept-Language: en-us, en;q=0.50
Accept-Encoding: gzip, deflate, compress;q=0.9
Accept-Charset: ISO-8859-1, utf-8;q=0.66, *;q=0.66
Keep-Alive: 300
Connection: keep-alive
Referer: http://deadlock:8080/jia-hello-world/faces/hello.jsp (2)同样是接收来自URL的请求
Cookie: JSESSIONID=58324750039276F39E61932ABDE319DF (3)会话标记
Content-Type: application/x-www-form-urlencoded
Content-Length: 92
welcomeForm%3AhelloInput=64& (4)组件值
welcomeForm%3AredisplayCommand=Redisplay&
welcomeForm=welcomeForm
我们并不打算讨论Http处理的详细过程,不过上表其中几行与我们的讨论是有关的:
(1)请求数据来源于/jia-hello-world/faces/hello.jsp 这个连接的POST数据。
(2)这里的referer(引用)是由请求页面产生的,他与/jia-hello-world/faces/hello.jsp是相同的页面。
(3)这里的Cookies将被Servlet容器使用,它用于将请求数据映射到一个指定的会话中。在这个例中JSF使用Servlet会话来存储当前的视图(页面也可以保存在由客户端控制的一个值对象中,比如隐藏字段中)。
(4)这是非常重要的一个部分,这里包含了JSF传输的实际参数值(&用于分隔不同的参数,%3A将被解析为“:”冒号)。首先是一个名为 welcomeForm:helloInput的参数,并且它的值是64,这个值是通过浏览器页面输入的,第二个参数名为welcomeForm: redisplayCommand,他的值是“Redisplay”,最后一个参数是welcomeForm,它的值也是welcomeForm。在下面 的讲解中我们将逐步介绍这些值是如何处理的。
一旦JSF接收到请求,它就会创建并实例化一个javax.faces.context.FacesContext对象。这个对象代表当前的请求状态,并且负责处理底层Servlet request对象的各个部分,事件监听器可以通过它获得当前页面的操作句柄,以此来进行增加消息、记录事件等等操作。在JSF中它代表了请求处理生命周期的整个过程。我们将在下面的讲解中依次讨论每一个状态。


Thursday, August 24, 2006

JSF中的导航功能

之前我们讲述的概念中所有操作都是在一个单独的页面上。在实际的Web应用开发中,这是不太可能的。Web应用的开发会有很多页面,我们必须提供一种方法实现不同页面之间的切换。实现从一个页面到另一个页面切换的机制被称为“导航”。

JSF中有一套非常完善的导航系统。导航控制器根据请求实现的返回值确定下一个页面跳转到哪里。对于给定的页面,一个导航规则定义了根据业务逻辑的动作事件判断页面应该如何跳转。根据导航规则发生的每一个页面跳转被称为一个导航事件。导航规则定义在JSF的配置文件中。下面这个例子演示了针对longin.jsp页面的两个导航事件:success和failure的一个导航规则。

<navigation-rule>
     <from-view-id>/login.jsp</from-view-id>
     <navigation-case>
     <from-outcome>success</from-outcome>
     <to-view-id>/mainmenu.jsp</to-view-id>
     </navigation-case>
    <navigation-case>
    <from-outcome>failure</from-outcome>
    <to-view-id>/login.jsp</to-view-id>
    </navigation-case>
</navigation-rule>

如你所见,每一个出口对应一个JSP页面,这里你不需要通过编码实现这些规则。如果你曾经使用过Struts你对这种定义方式一定不会陌生。你会发现,所有的导航事件只映射一个出口(对应一个jsp),也就是说,如果将来需要进行变更你只需要修改需要变更的那一个页面就可以了而不是修改多处。

现在,JSF中的几个基本概念已经介绍完了。在随后的讲解中我们会看到更多的例子。已经这些概念集中到一起是如何使用的。

Wednesday, August 23, 2006

Java7中将增加闭包的支持

现在越来越多的动态语言比如:PythonRuby等受到了大家的欢迎,Java中也衍生出了Groovey,在这些语言中都提供了“闭包”的支持。在Java未来将发布的最新版本Dolphin也将增加“闭包”的功能(closure)

类似GroovyPythonRuby这样的语言中都存在闭包的概念,JavaC++C#这样的语言都不支持闭包,什么是闭包呢?记得以前看到的一篇文章中这样说过“对象是附带过程的数据,闭包是附带数据的过程”。听起来有点抽象,简单一点讲闭包就是“一段封闭的代码”(block),下面是Java7规范草案中有关闭包的一段代码:

int(int) plus2b = (int x) {return x+2; };

闭包可以包含参数和返回结果,这比起Java中的匿名内部类要要简单灵活的多。个人认为Java中增加闭包函数除了增加灵活性;而且更易于对代码进行抽象;同时可以减少临时对象对内存的开销,虽然Java有自动垃圾回收机制,但是有时候在一些应用程序中临时对象的存在可能就是性能的瓶颈。目前Java7中的闭包部分的规范还在起草阶段,期望未来的Java中可以增加更多的动态语言的支持。

如果你对此有兴趣,可以下载规范草案

http://blogs.sun.com/roller/resources/ahe/closures.pdf

什么是闭包

有很多人问我,“闭包是什么意思”。是否可以通过实际的开发语言举一个具体的例子,闭包到底意味着什么?
如果你没使用过闭包进行编程,或者你在过去的10年里对Java接触的较少的话,闭包的含义确实较难理解。下面让我从一个实际的例子开始,逐步的向你介绍什么是闭包。

问题的提出

假设你现在要开发一个这样的应用程序,它需要维护保存在一个列表中的文档,每一个使用这个程序的用户需要对这些文档添加注释。为了讲解闭包的原理,在这个例子中假设有两组互不相干的文档列表。这时,如果你从一个用户那里获得一组文档的注释,需要从上面的两组文档列表中获得相应的文档注释内容:

class Document { ... }
class DocAnnotation { ... }
class Person { ... }
class Documents {
    static List allDocuments();
}
class Persons {
    static Set allPersons();
    static Person GEORGE_W_BUSH = ...;
}
class DocAnnotations {
    static Map> allAnnotations = ...;
}

现在,针对上面的程序你可能会问诸如:乔治.布什在这些文档中是否提到有关伊拉克的机密问题。为了实现这个需求,在这个假想的程序中我们可以这样作:

boolean bushMarkedAnyIraqDocsSecret() {
    Iterator docI = Documents.allDocuments().iterator();
    Iterator annI =
        DocAnnotations.allAnnotations.get(GEORGE_W_BUSH).iterator();
    while (docI.hasNext() && annI.hasNext()) {
        Document doc = docI.next();
        DocAnnotation docAnn = annI.next();
        if (doc.mentions("Iraq") && docAnn.marked("secret")) {
            return true;
        }
    }
    return false;
}

我们可以将上面的需求进一步抽象为,我们要找的文档是什么,我们要找的文档注释是谁写的以及我们要找的文档注释属于哪种类型,抽象后的方法如下:

boolean personAnnotatedOnKeyword(Person person, String ann, String key) {
    Iterator docI = Documents.allDocuments().iterator();
    Iterator annI =
        DocAnnotations.allAnnotations.get(person).iterator();
    while (docI.hasNext() && annI.hasNext()) {
        Document doc = docI.next();
        DocAnnotation docAnn = annI.next();
        if (doc.mentions(key) && docAnn.marked(ann)) {
            return true;
        }
    }
    return false;
}
通过上面的抽象,我们已经尽可能的避免了代码的重复,如果再提出类似的问题,只要变化一下参数就可以了。对方法或类的抽象是非常必要的,因为这可以在我们需要重构的时候尽量减少代码的修改。比如,如果我们需要把一个人的注释表现形式改成Map而不是List,又或者希望DocAnnotation包含一个Document的引用,那么我们就必须修改上面方法中的循环。所以如果将这段循环放在一个单独的地方那么维护起来就方便的多了。

JDK5中对循环进行抽象
下一步要作的是对循环本身进行抽象。Java中提供了很多方便的循环结构,比如最近提供的包含Iterable 接口的for-each循环。在这里可以使用它,但是我们需要介绍在上面的例子中我们需要进行迭代循环的是什么类型:

class DocAndAnnotation {
    final Document doc;
    final DocAnnotation docAnn;
    DocAndAnnotation(Document doc, DocAnnotation docAnn) {
        this.doc = doc;
        this.docAnn = docAnn;
    }
}


现在我们可以提供一个只进行一次循环就包含了文档和注释的集合。


Collection docsWithAnnotations(Person person) {
    Iterator docI = Documents.allDocuments().iterator();
    Iterator annI =
        DocAnnotations.allAnnotations.get(person).iterator();
    List result =
        new ArrayList();
    while (docI.hasNext() && annI.hasNext()) {
        Document doc = docI.next();
        DocAnnotation docAnn = annI.next();
        result.add(new DocAndAnnotation(doc, docAnn));
    }
    return result;
}


这样我们就可以写一个更特别的循环,就像下面这样:


boolean personAnnotatedOnKeyword(Person person, String ann, String key) {
    for (DocAndAnnotation docAndAnn : docsWithAnnotations(person)) {
        if (docWithAnn.doc.mentions(key) && docWithAnn.docAnn.marked(ann)) {
            return true;
        }
    }
    return false;
}
目前为止,上面的方法存在一个问题:每次它都会把全部的文档和注释装入列表集合中,
尽管可能我们的答案在第一个注释文档中就可以找到。我们可以作一些改进,那就是创建一个
“懒加载”的迭代器取代上面的集合。我们重写了docsWithAnnotations如下:
Iterable docsWithAnnotations(Person person) {
final Iterator docI =
Documents.allDocuments().iterator();
final Iterator annI =

DocAnnotations.allAnnotations.get(person).iterator();

return new Iterable() {

public Iterator iterator() {

return new Iterator() {

public boolean hasNext() {

return docI.hasNext() && annI.hasNext();

}

public DocAndAnnotation next() {

return new DocAndAnnotation(docI.next(), annI.next());

}
public void remove() {
throw new UnsupportedOperationException();

}

};

}

};
}
现在你不需要修改personAnnotatedOnKeyword它同样可以运行。
但是新的问题又出现了,上面的方法产生了一大堆小的、临时的垃圾对象。
Iterator, Iterable, DocAndAnnotation对象的存在只是临时性的,
它们的作用就是简单的把数据从一个地方搬到另一个地方。
这是不是什么大问题。
HotSpot有很多垃圾回收机制,
它们可以很好的将这些临时对象进行重新整理和释放。但是在有些应用中它们可能是性能的瓶颈。


有没有好的循环方式可以避免这一问题呢?答案是肯定的,Java有一个标准的习惯用法用于处理这一问题。方法就是控制循环的内部回路。较之在personAnnotatedOnKeyword方法中执行循环更好的方式是,我们提供一个接口,由接口中的方法执行循环并把值传给由personAnnotatedOnKeyword提供哦的一段代码。就像这样:

interface WithDocumentAndAnnotation {
void doIt(Document doc, DocAnnotation docAnn);
}void docsWithAnnotations(
Person person, WithDocumentAndAnnotation body)
{

Iterator docI = Documents.allDocuments().iterator();

Iterator annI =

DocAnnotations.allAnnotations.get(person).iterator();

while (docI.hasNext() && annI.hasNext()) {

Document doc = docI.next();

DocAnnotation docAnn = annI.next();

body.doIt(doc, docAnn);

}

}


客户端可以通过提供一个实现这个接口内部类来执行循环:

boolean personAnnotatedOnKeyword(
        Person person, final String ann, final String key) {
    class MyBody implements WithDocumentAndAnnotation {
        boolean result = false;
        public void doIt(Document doc, DocAnnotation docAnn) {
            if (doc.mentions(key) && docAnn.marked(ann)) {
                result = true;
            }
        }
    }
    MyBody body = new MyBody();
    docsWithAnnotations(person, body);
    return body.result;
}

这种方法解决了临时对象内存分配的问题(尽管它还存在问题),但是这种方法还是没有解决我们开始提出的问题,那就是不管是否我们需要找的文档就在第一个文档中它还是会装入所有的文法。我们可以通过修改WithDocumentAndAnnotation接口的方法,让它返回一个boolean值,用来判读循环是否需要继续执行。这就需要修改客户段程序中docsWithAnnotations的很多部分来适应新的API,然而有时这是不可行的。看来我们应该修改接口的功能,但是我们发现客户端使用不同的控制流程来迭代整个文档和它们的注释。现在,我们向读者描述了上面练习的详细内容。

通过闭包抽象上面的循环
闭包提供了更便利的方法来抽象上面的循环:

void docsWithAnnotations(Person person, void(Document,DocAnnotation) block) {
    Iterator docI = Documents.allDocuments().iterator();
    Iterator annI =
        DocAnnotations.allAnnotations.get(person).iterator();
    while (docI.hasNext() && annI.hasNext()) {
        Document doc = docI.next();
        DocAnnotation docAnn = annI.next();
        block(doc, docAnn);
    }
}

上面的方法中除了没有WithDocumentAndAnnotation接口之外和我们最后一个JDK5版本的例子非常类似。
让我们来看看客户端是什么样子:
boolean personAnnotatedOnKeyword(
Person person, final String ann, final String key) {

docsWithAnnotations(person, (Document doc, DocAnnotation docAnn) {

if (doc.mentions(key) && docAnn.marked(ann)) {

return personAnnotatedOnKeyword: true;
}
});

return false;
}


就这样,如果我们采纳功能修改建议的话,客户端还可以是这样:

boolean personAnnotatedOnKeyword(
Person person, final String ann, final String key) {


docsWithAnnotations(person) (
Document doc, DocAnnotation docAnn) {

if (doc.mentions(key) && docAnn.marked(ann)) {

return true;

}

}

return false;

}
上面代码中仅有的临时对象是闭包对象和两个用于实现docsWithAnnotations迭代器对象。如你所见,docsWithAnnotations的调用者可以使用控制层的操作,比如docsWithAnnotations的实现必须首先确定哪一个类型的控制层是必须的,否则什么都不返回。
这样,只有很少的临时对象需要处理。因此HotSpot虚拟机只需要处理很少的异常,避免了内存的开销。
上面几个版本的迭代向我们展示了闭包的优美之处:它可以让你抛开繁复的代码写出更好的抽象。




Tuesday, August 22, 2006

groovy中的闭包

groovy中引入了闭包的概念(closure) 。这是一个很好的功能,类似与Java中的内部匿名类,但是它可以支持状态的自动传入传出,而且还支持参数,这比起内部匿名类更加方便。据说Java7中也将支持闭包,期待中。

Sunday, August 20, 2006

翻译:JakataStruts 和JavaServerFace

翻译来源http://java.sys-con.com/read/260036.htm
设计一个包含有序选择的冗长列表需要非常复杂的设计模式。这种模式需要将可选项显示在一个列表中,同时还需要在另一个地方进行选择,所以需要用户的可以方便的进行选择和修改.
这种模式通常称为双重列表或者叫双重列表框选择器.它又被称为选择概要或者列表创建者模式.按照Java Look and Feel的设计规则,它又被称为增加-移除模式。
标准的Struts实现,需要试用JSP,Java和JavaScript。JavaServerFace不需要JSP技术,但是比起Struts要简单的多。
列表1是使用Struts技术的JSP代码,列表2是使用JavaServerFace技术的JSP代码。JSF的支持Bean,配置文件和其他文件的代码可以从JDJ网站下载。
Jakarta Struts的实现方式
使用Jakarta Struts技术,JSP需要定义一个表格来显示树状列。在第一列和第三列,可选列通过<html:select>标签和<html:optionCollection>来实现。为了实现国际化要求,label标签使用了JSP标准表情库中的<fmt:message>标签。很多人发现结合Struts和JSTL标签一切使用功能非常强大。
<table border="0" cellpadding="0" cellspacing="5">
<tr align="middle" valign="center">
<td>
<fmt:message key="titles.available"/><br />
<html:select property="availableValues"
multiple="true" size="7"
style="width:80px;" styleId="available"
onchange="doUpdate( false, true );">
<html:optionsCollection
property="availableList"/>
</html:select>
</td>
...
<td>
<fmt:message key="titles.chosen"/><br />
<html:select property="chosenValues"
multiple="true" size="7"
style="width:80px;" styleId="chosen"
onchange="doUpdate( true, false );">
<html:optionsCollection
property="chosenList"/>
</html:select> </td>
</tr>
</table>
Struts的表单Bean包含了用于JSP的“可见列表”,“可见值”,“可选列表”和“可选值”属性。
private List availableList = new ArrayList();
private String[] availableValues = new String[ 0 ];
private List chosenList = new ArrayList();
private String[] chosenValues = new String[ 0 ];
...
public List getAvailableList()
public void setAvailableList( List list )
public String[] getAvailableValues()
public void setAvailableValues( String[] list )
public List getChosenList()
public void setChosenList( List list )
public String[] getChosenValues()
public void setChosenValues( String[] list )
“availableList ”和“chosenList ”属性通过标签值Bean的方式封装了列表的可见值。“availableValues ”和“chosenValues ”以字符串数组的形式存储了可选的值。
对于增加-选择模式来说,不会关心这些可选值。对于服务器来说我们只要告诉它列表中显示什么值而无需告诉它那个值被选中了。然而当表单被提交后,只有被选中的值会被提交给服务器。这是通常使用的设计方法,但是这种设计模式存在问题。如图1.
下面有集中方法可以解决这个问题。一种方法是在每次列表内容发生变化的时候都向服务器提交一次值。者引发了过度的页面引用和网络负载。另一种方法是使用AJAX技术与服务器进行通信。这减少了页面的刷新,但是仍然会代理网络的负载。直到用户完成列表变更之前都不应该产生网络负载。
最好的解决方案是把选中的值存储在一个隐藏字段中。使用这种方法当表单提交时只向服务器提交一次值。在这个例子中我们把选中的值存储在一个隐藏字段中。利用这个隐藏字段,两个列表中见的按钮可以这样实现:

<td>
<br />
<input type="button" style="width:100px;" id="add" onclick="
doMove( 'available', 'chosen', false );" value="<fmt:message key='add'/>" /><br />
<input type="button" style="width:100px;" id="addAll" onclick=
"doMove( 'available', 'chosen', true );" value="<fmt:message key='addAll'/>" /><br />
<br />
<input type="button" style="width:100px;" id="remove" onclick=
"doMove( 'chosen', 'available', false );" value="<fmt:message key='remove'/>" /><br />
<input type="button" style="width:100px;" id="removeAll" onclick=
"doMove( 'chosen', 'available', true );" value="<fmt:message key='removeAll'/>" />
<html:hidden styleId="chosenItem" property="chosenItem" />
</td>
这个按钮通过HTML标签<input>实现。<html:hidden>字段存储了选中的值。被选中的值通过JavaScript方法“doMove()”进行保存,所有4个按钮都执行这个方法。

/**
* Move selected items between lists.
* <p>
* @param sourceId ID of source list
* @param destId ID of destination list
* @param all true iff moving all
*/
function doMove( sourceId, destId, all )
{
// Move the items between the lists.
var sourceElem = document.getElementById( sourceId );
var destElem = document.getElementById( destId );
for( var i = 0; ( i < sourceElem.length ); )
{ if( sourceElem.options[ i ].selected || all )
{ var newOption = document.createElement( "OPTION" );
newOption.text = sourceElem.options[ i ].text;
newOption.value = sourceElem.options[ i ].value;
destElem.options[ destElem.length ] = newOption;
sourceElem.remove( i );
}
else
i++;
}
// Update the button states.
doUpdate( false, false );
// Store the chosen items in a hidden field.
var chosenItem = document.getElementById( "chosenItem" );
var chosenList = document.getElementById( "chosen" );
chosenItem.value = "";
for( var i = 0; ( i < chosenList.length ); i++ )
{ chosenItem.value += chosenList.options[ i ].value + '|';
}
}
上面的方法非常易于理解,使用起来也很方便。按钮只要在需要的时候才会显示。比如如果没有列表值被选中,那么“Add”和“Remove”按钮就无法使用。当显示列表和选择列表都是空的时候,那么“Add All”和“Remove All”按钮就无法使用。
“doUPdate()”函数根据用户的选择和列表中包含的内容判断按钮是否可以显示。如图2
“doUpdate()”方法由上面由列表控件的OnChange方法触发的“doMove()”方法调用。或者在body的onload方法中调用。

/**
* Update the button states based on whether
* lists have contents and selected items.
* Deselect list items if requested to ensure
* at most one list contains selections.
* <p>
* @param offAvailable deselect available list
* @param offChosen deselect chosen list
*/
function doUpdate( offAvailable, offChosen )
{
// Get the lists and deselect if requested.
var availableList = document.getElementById( "available" );
var chosenList = document.getElementById( "chosen" );
if( offAvailable ) availableList.selectedIndex = -1;
if( offChosen ) chosenList.selectedIndex = -1;
// Update the button states.
document.getElementById( "addAll" ).disabled =
( availableList.length == 0 );
document.getElementById( "removeAll" ).disabled =
( chosenList.length == 0 );
document.getElementById( "add" ).disabled =
( availableList.selectedIndex < 0 );
document.getElementById( "remove" ).disabled =
( chosenList.selectedIndex < 0 );
}
使用JavaScript函数,列表中包含的内容被正确的更新,用户选择的值被保存在一个隐藏字段中。在这个例子中它被用于控制两个列表中值和用户的选择。
列表中的值被存储在表单Bean中的一个“languageList”属性里。这个列表中包含了所有可能的选项。move方法利用“chosenItem”列表中的值控制了“可见”和“可选”列表中的值。

private List languageList = new ArrayList();
private String chosenItem = "";
...
public List getLanguageList()
public void setLanguageList( List list )
public String getChosenItem()
public void setChosenItem( String items )
public void move( List sourceList, String[] sourceValues, List destList )
...
"languageList" ,"chosenItem" 属性和 "move"方法通过表单提交的Action中的“execute”方法执行。在Struts中是StrutsAction类的实例。

/**
* Populate the lists from the hidden field.
* <p>
* @param mapping action mapping
* @param form action form
* @param request HTTP servlet request
* @param response HTTP servlet reponse
* @throws Exception
*/
public ActionForward execute( ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response ) throws Exception
{
// Populate available list from language list.
ExampleForm eForm = ( ExampleForm )form;
eForm.getAvailableList().clear();
eForm.getAvailableList().addAll( eForm.getLanguageList());
// Populate chosen list from hidden field.
eForm.getChosenList().clear();
eForm.move( eForm.getAvailableList(),
eForm.getChosenItem().split( "\\|" ),
eForm.getChosenList());
}
JavaServerFace实现
那么JavaServerFace中是如何实现上面的模式呢?JSF由一群曾经设计过Struts的人设计,所以我们可以较容易的从Struts过度到JSF。
JSF中的JSP也包含了两个列表,因为JSF的标签更加紧密。代替<table>,<tr>和<td>标签,JSF使用了<h:panelGrid>标签。代替Struts中的<fmt:message>标签JSF使用了<h:outputText>。代替<html:select>和<html:optionsCollection>标签,JSF使用了<h:selectManyListBox>和<f:selectItems>。

<h:panelGrid columns="3" rowClasses="center">
<h:outputText value="#{bundle.available}" />
<h:outputText value="" />
<h:outputText value="#{bundle.chosen}" />
<h:selectManyListbox style="width:100px; height:120px;"
id="available" value="#{example.availableValues}"
onchange="doUpdate( false, true );">
<f:selectItems value="#{example.availableList}"/>
</h:selectManyListbox>
...
<h:selectManyListbox style="width:100px; height:120px;"
id="chosen" value="#{example.chosenValues}"
onchange="doUpdate( true, false );">
<f:selectItems value="#{example.chosenList}"/>
</h:selectManyListbox>
</h:panelGrid>
就像Struts中实现的那样,<h:selectManyListBox>标签映射了支持Bean中的“availableList”“availableValues”和“chosenList”以及“chosenValues”属性,他们等同于Struts中的相应属性。
就像Struts实现中的一样,四个按钮也由标准HTML的<input>标签实现。
在JSF中这些标签还必须附上<f:verbatim>标签。除此之外这些标签和Struts中的没什么不同。这里有一点与Struts类似的就是在每个JavaScript方法中经常需要包含一个“form:”前缀。

<h:panelGrid columns="1">
<f:verbatim>
<input type="button" style="width:100px;"
id="add" onclick="doMove( 'form:available', 'form:chosen', false );"
value="<fmt:message key='add'/>" /><br />
<input type="button" style="width:100px;"
id="addAll" onclick="doMove( 'form:available', 'form:chosen', true );"
value="<fmt:message key='addAll'/>" /><br />
<br />
<input type="button" style="width:100px;"
id="remove" onclick="doMove( 'form:chosen', 'form:available', false );"
value="<fmt:message key='remove'/>" /><br />
<input type="button" style="width:100px;"
id="removeAll" onclick="doMove( 'form:chosen', 'form:available', true );"
value="<fmt:message key='removeAll'/>" />
</f:verbatim>
<h:inputHidden id="chosenItem" value="#{example.chosenItem}" />
</h:panelGrid>
除此之外,JSF中的doMove方法和doUpdate方法没什么不同。
JSF的优势是它控制了提交事件。在JSF中你可以明确的定义一个按钮具体执行那个请求。
<h:commandButton action="#{example.submit}" value="#{bundle.submit}" />
“example:submit”方法会读取隐藏字段中内容并更新列表。因为这个方法直接关联在一个提交按钮上,所以你不必单独实现一个Action对象。
public String submit()
{
// Populate the available list from the language list.
availableList.clear();
availableList.addAll( languageList );
// Populate the chosen list from the hidden field.
chosenList.clear();
move( availableList, chosenItem.split( "\\|" ), chosenList );
...
return( "success" );
}
JSF中不需在学什么,虽然标签不同,但是通常和JSP中的类似。Java代码也差不多。
需要强调的是JSF中的组件架构。如果你下载了免费的SUN Java Studio Creator,你会发现它包含了一整套可以在你的GUI中任意增加-删除的组件。SUN的组件包含很多功能,比如“Move Up”和“Move Down ”按钮可以改变列表中值的顺序。我们还可以列举很多这样的有用组件。
结论

这篇文章展示了一个从两个列表中取值的标准UI设计模式。通过Jakarta Struts和JavaServer Face两种方式实现。
JSF提供了一种从Struts平滑过度的方法。JSP的标签非常简单那;支持Bean的代码也和Struts类似;并且,如果你需要使用JavaScript在JSF中的用法也是一样的。
你可以把JSF当成是一个简单的,组件化的Struts。它增加了很多有用的特性,并且减少了复杂性。
对于大多数刚接触Web项目的开发人员,JavaServer Faces是一个很好的选择。对于熟悉Struts的开发人员,过度到JSF也很容易。

Thursday, August 17, 2006

存储过程写完了

今天下午用了2个小事的时间终于把导数据的存储过程写完了,明天开始测试。哈哈,好高兴!

Java 将支持Visual Basic

SUN负责SUN平台小组的负责人Graham Hamilton在一次访谈中提到JAVA SE7(命名代码:海豚)将支持Visual Basic 。
SUN不准备克隆某个固定版本的VB,它将致力于在未来的Java平台中增加通用的VB.NET的支持。“如果你对VB.Net熟悉的话,你会发现这将是非常容易学习的语言”Hamilton说对于Basic语言支持的基础实现已经在开发中。
这意味着Java将有VB语言的特性,而不是实现VB语言本身。基于这一点,我认为SUN不会提供基于VB代码的编译器。不管怎么说这都是件好事,尤其是对于以前从事过VB开发的人,而对于像我一样的从未使用过VB的人也不是什么坏事,毕竟这只是一个新增的特性。你怎么看?

Wednesday, August 16, 2006

JSF 中的消息机制

之前我们讨论了不同的JSF组件,那么如果发生错误是会怎样呢?现在最大的问题是如何让UI组件显示一个恰当的错误消息。这个消息可以分为两种:一种是应用程序错误(比如应用程序逻辑错误,数据库错误,连接错误等等),另一种是用户输入错误(比如文本框输入校验和空值校验等等)。

应用程序错误一般会产生一个新的页面以便显示错误消息;而用户输入错误一般会回显原来的页面,然后把错误信息显示在一个文本中。通常你需要在不同的页面抛出相同的错误信息,所以你必须保证对于同样的错误用户看到的消息是一样的。你一定不希望在一个页面中向用户显示“请输入电话号码”而在另一个页面中却显示“电话号码是必须输入的”这样的消息。

JSF提供了消息机制来处理这一问题。一个消息包含了:摘要信息、详细信息和错误级别信息。消息可以自动定制为用户当期所使用的语言。你的应用程序中任何UI组件、校验器、转换器、事件和监听器都可以加入消息。你可以通过组件或者应用程序代码来控制当期的消息输出。

消息不一定非要显示错误信息,他们同样可以用于显示其他信息。比如事件监听器可以增加一个显示记录被成功保存的消息。消息本身可以关联到一个特定的组件(比如输入错误)也可以是应用程序级的消息。

你可以通过HtmlMessage组件为一个特定的组件增加显示错误信息的供。你一定还记得Hello Wold 应用成中的HtmlMessage组件吧:

上面这个标签会显示所有由helloInput组件产生的错误消息。你也可以使用HtmlMessage组件显示非UI组件的消息。

消息机制提供了一种适当的方法向用户显示错误信息或者其他信息。消息是JSF校验器的内部类型,一旦校验错误就会向产生一个错误消息。当你向希望向用户输出一个消息的时候这也是一个好办法,因为你不用关心消息是怎么产生的,你只需给事件监听器增加一个消息,这个消息就会自动显示出来。后面我们会讲解如何增加应用程序消息和如何在代码中控制消息输出。

Python的内容搬家了

为了更好的归类Blog文章,以后的Python和wxPython 的文章都发布到专门的blog :Python BLog

Tuesday, August 15, 2006

WxPython 编程指南 1.2.2 开发应用程序和框架窗口

一旦你导入了wx包,你就可以创建应用程序和框架窗口了。每个wxPython程序必须有一个应用程序对象和至少一个框架窗口对象。这些对象我们会在后续讲解中详细介绍。现在你只需知道应用程序对象必须是wx.App的一个实例或者是定义了OnInit方法的wx.App子类。当应用程序开始运行的时候父类将调用子类的OnInit方法。

子类化wxPython应用程序。

下面是定义我们的wx.App子类的方法:

class MyApp(wx.App):

def OnInit(self):

frame = wx.Frame(parent=None, id=-1, title="Bare")

frame.Show()

return True

我们将类命名位“MyApp,通常OnInit方法是你创建框架窗口对象的方法。但是,你一般不需要象我们上面的代码那样直接创建frame对象。替代的方法是创建一个wx.Frame的子类,就像我们创建MyApp那样(你会在随后的讲解中看到相关的例子)。随后的章节会详细介绍wx.Frame,现在我们只要给它传入几个简单的参数直接创建一个frame对象就可以了。上面例子中的三个参数只有第一个是必须的,其它参数都有默认值。

调用Show方法之后就可以创建并显示一个窗口,我们还可以通过输入不同的Show方法参数来控制框架窗口的显示:

frame.Show(False) # 不显示框架窗口.

frame.Show(True) # True 是缺省的参数.

frame.Hide() # 等同与Show(False).

定义应用程序初始化方法。

注意,我们没有在应用程序中定义__init__方法。在Python程序中这意味着父类wx.App.__init__方法会在应用程序创建的时候自动调用。这是好事,但是如果你定义了一个__init__方法记得调用父类的__init__方法,就像下面这样:

class App(wx.App):

def __init__(self):

# Call the base class constructor.

wx.App.__init__(self)

# Do something here...

如果你忘了调用父类的__init__方法,那么应用程序将不能被初始化而起OnInit方法也不会被调用。
创建应用程序实例并进入主事件循环

最后一步是创建子类化wx.App的实例,然后调用它的MainLopp()方法。

app = App()

app.MainLoop()

好了,一旦程序的MainLoop开始执行,控制权就交给了wxPython。与普通的程序不同,一个wxPython GUI程序主要由相关的各种事件控制,任何响应都取决于用户鼠标或者键盘的动作。当应用程序中的所有窗口都关闭之后,MainLoop方法会返回一个值,程序也将退出。

WxPython 编程指南 1.2.1 导入wxPython

你要作的第一件事情就是导入主要的wxPython包。这个包名为wx:

import wx

一旦包被导入,你就可以引用wxPython中的类、函数等,每次引用的时候都需要使用wx作为前缀。比如:

class App(wx.App):

老式的导入风格:在我写这篇文章的时候,wxPython包的命名方式已经发生了变化。由于老式风格的写法仍然被支持,所以你可能遇到老式风格写法的代码。所以,我们将暂时跑题介绍一下老式风格代码的写法,以及为什么wxPython会作出改变。老式的包名为位wxPython并且有一个内部的模块名为wx。有两种方法从wxPython中导入wx,一种方法是:

from wxPython import wx # 这种方法不建议使用。

第二种方法是直接从wxPython中导入所有需要的模块

from wxPython.wx import * # 这种方法也不在支持,最好不要使用。

这两种方式存在严重的缺点。第二种方法在Python中会引起很大的麻烦,原因是会导致名称空间的冲突。以前的wx模块为了避免这种冲突给每个引用前面都加了一个wx前缀。尽管这样还是存在潜在的问题,但是很多wxPython程序员把这种方式作为了首选风格,你会在以前很多的代码中看到这种风格。基于这种风格,wxPython中的类名一般以小写字母开头,而方法名以大写字母开头。

然而,为了避免通过from wxPython import wx 这种方式带来的名称空间冲突,我们必须在每个类和方法前面都要输入两次wx.,比如wx.wxWindow。很多程序员看到了这种方式的弊端,认为应该去掉前缀,并且最终他们的要求得到了满足。如果你对此感兴趣你可以搜索wxPython的邮件列表,其中有对此问题的详细讨论。

更多需要了解的有关wxPython导入的知识是:在你从wxPython导入其他包之前你必须先导入wx。这一规则并不是Python中要求的,在Python 没有对导入顺序的要求。但是,毕竟wxPython是一个独立的模块,它包括了很多复杂的模块,这些模块实际上是封装了底层的C++ 小窗口部件工具。当你第一次导入wx模块的时候,它作了很多初始化的工作,这对于其他的wxPython模块是至关重要的。因此如果wx包没有导入那么wxPython的子包中的很多模块将无法使用,比如xrc模块:

import wx # 总是先导入wx

from wx import xrc # 再导入其他包

from wx import html

这种限制只有在wxPython中是必须的,其他Python包不受限制,你可以在任何需要的地方导入他们,可以在wxPython之前也可以在其后。因此,下面这个例子中的写法是正确的:

import sys

import wx

import os

from wx import xrc

import urllib

WxPython 编程指南 1.2 创建一个空的wxPython应用程序

我们将创建一个最简单的应用程序。创建一个名为bare.py的程序,键入下面的代码。记住在Python中每行之前都要有缩进。

import wx

class App(wx.App):

def OnInit(self):

frame=wx.Frame(parent=None,title="bare")

frame.Show()

return true

app=App()

app.MainLoop()

不需要再作什么了,整个代码之后8行。这个程序看起来很空洞,仅仅显示了一个空的窗口。先不要着急,我们会逐步完善这个程序让他变得更有用。

这段程序的目的是为了确保你可以创建一个Python程序,并且确认所有必须的软件都已经安装。所以这一步要完成的工作就是,创建一个文件键入如下代码,把它保存位“bare.py”,然后运行这个程序。

这个程序的运行依赖于你的操作系统。通常你需要通过一个命令行来运行它,使用下面的命令:

Python bare.py 或者

Pythonw bare.py

1.51.7显示了这个程序在不同操作系统下的样子:

1.5 bare.py windows 下的效果

1.6 bare.py linux 下的效果

1.7 bare.py Mac OS 下的效果。

术语:大多数人看到这个应用程序,它们把看到的东西叫做“窗口”。但是wxPython 并不把它叫做窗口,而是叫做“框架”。在wxPython 中,窗口是一个通用的术语,任何显示在屏幕上的对象都叫窗口。所以在wxPython 中按钮和文本框都叫“窗口”。这看起来容易混淆,但是更接近于原始的C++工具箱。为了避免“窗口”这个术语的冲突,不仅因为它容易混淆,而且还与一个很大的厂商的产品重名。我们将使用窗口小部件(widget)作为通用的术语。

上面的代码足够小,它是运行一个程序的必须的代码,少一行程序都无法运行。创建这个应用程序分为下面几个步骤:

1、 导入必要的wxPython包。

2、 子类化wxPython应用程序类。

3、 定义一个应用程序初始化方法。

4、 创建一个应用程序类的实例。

5、 进入应用程序的主事件循环。

下面让我们仔细分析每一步是如何执行的。

WxPython 编程指南 1.1 开始使用WxPython

现在我们开始写一个新的wxPython程序,尽管它是一个简单的程序。我们并不打算建立多么复杂的应用程序,现在只是教你一步步的创建一个初步的wxPython程序。现在确保开发所需的所有软件都已经安装。表格1.1列出了运行wxPython所需的所有软件。

当所有软件都安装完毕之后,我们将通过3个步骤创建一个显示单一图片文件的wxPython程序。

1、 首先创建一个空的应用程序,什么都不做。

2、 让代码变得更结构化和坚固。

3、 最终版本的应用程序将显示一个wxPython徽标。

表格1.1 运行wxPython程序所需的所有软件

软件名称

说明

适合的操作系统

这是最简单的一个――你可以有很多选择。可选的操作系统有:

任何windows 32位操作系统,就是说所有win98以上操作系统(当然,如果需要你也可以使用win95操作系统,不过需要下载一些扩展程序。)

Python语言环境

www.python.org上下载python2.3以上版本的程序,大多数linux操作系统已经包含了pythonMac10.3以上的操作系统也包含python。当然你也可以下载最新的版本。

wxPython工具

你可以从www.wxpython.org下载。你可以根据你的操作系统和python语言下载不同的版本。确保你已经安装了适合你操作系统的运行时环境。最好再下载演示程序和文档。

如果你安装过其他软件,你会发现安装wxPyton演示包非常类似。尽管linux或者Mac操作系统可能已经安装了wxPython你还是应该下载最新版本的wxPython

一个文本编辑器

我建议你找一个可以解析python语法并高亮着色python语法的编辑器,这样可以使你的代码可读性更高。很多编辑器都支持Python代码,所以找一个适合你的。

如果你不太喜欢使用文本编辑器,试试IDLE,这个集成开发环境包含了源代码编辑器,交互式shell,调试器以及其他工具。Python的网站上列出了很多Python的开发工具。

www.python.org/editors

1.21.4显示了我们的程序再不同操作系统下的样子:

1.2 hello.pywindows上运行的效果。

1.3 hello.py linux操作系统上的效果。


WxPython 编程指南 第一章 初识WxPython

第一章 初识WxPython
本章包括
WxPython入门
创建一个最小的WxPython程序
引入WxPython
学习Python语言
结合Python和WxPython开发

下面是一个简单的应用程序,这个应用程序创建了一个包含显示鼠标坐标位置文本框的窗口,整个代码包括空行一共20行。
列表1.1 20行的WxPython程序。
#!/bin/env python
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "My Frame", size=(300, 300))
panel = wx.Panel(self, -1)
panel.Bind(wx.EVT_MOTION, self.OnMove)
wx.StaticText(panel, -1, "Pos:", pos=(10, 12))
self.posCtrl = wx.TextCtrl(panel, -1, "", pos=(40, 10))
def OnMove(self, event):
pos = event.GetPosition()
self.posCtrl.SetValue("%s, %s" % (pos.x, pos.y))
if __name__ == '__main__':
app = wx.PySimpleApp()
frame = MyFrame()
frame.Show(True)
app.MainLoop()

看看上面的代码,我们能说什么?对于一个应用程序来时,它非常的断。当然它并没有多少功能,不过毕竟它创建了一个窗口,并且在一个文本框中显示鼠标的位置,对于一个20行的程序来说这很不错。可以毫不夸张的说如果用其他语言的话代码量可能是它的3到4倍。图.1.1 显示了程序运行界面。


这些代码非常易读,不管你是否懂得Python或者WxPython,如果你有一定的经验,你对Frame,__init__,EVT_MONTION,TextCtrl,MainLoop的含义并不陌生。如果你以前没有用过Python你一定对这种代码缩进的方式觉得奇怪,而且对于其中的参数也不太明白。但是理解起来应该不困难。
本书我们将向你展示为什么wxPython是最简单,最强有力的开发GUI的工具。很多语言都有自己的界面GUI开发工具(比如VB),但是都没有Python具有的可扩展性,继承性。大多数语言都要求你使用特定的开放环境和工具。你会发现wxPython在这方面非常宽容,因为它是开源的。它允许你在协议允许下免费使用它的源代码和二进制文件用于商业和开源的环境。
在本书的结尾你将学会使用WxPython开发GUI程序。你将能够创建并使用简单的按钮、菜单以及Html组件。这章我们将讲述什么是WxPython,如何使用它,已经为什么要选择它来开发GUI程序。一个好的应用程序允许用户简单方便的操作各种功能。反之用户使用起来会非常不方便。

WxPython 编程指南 第一部分

从第一章“初识Wxpython”开始,我们将介绍WxPython,了解它的起源,了解它的强大功能并开始使用。我们将展示一些代码,和一些屏幕快照,这些都属于wxWidght的一部分。在第二章中我们将介绍WxPython中两个必须的对象。第一个是应用程序对象,它控制了整个应用的事件循环和整个应用程序的生命周期。第二个对象是顶层窗口,它是用户和应用程序交互的主要部分。我们将演示如何使用这两个对象。
在第三章“事件驱动环境”中我们将集中讲解wxpython的事件生命周期,你将明白什么是事件,以及事件是怎么产生的。我们将看到事件产生的整个过程,你还将看到如何自定义事件。第四章“使用PyCrust让WxPython的开发更简单”中将介绍PyCrust交互命令行工具,以及一些有用的PY包。我们将向你演示如何在PyCrust中开发和调试应用程序。
在第五章中,我们将向你介绍一些开发用户界面的有用的内容。在有关如何让你的代码变的干净和易于控制方面我们将提供一些好的建议。还将讲述如何在MVC模式下使用WxPython。这章的最后还讲述如何在WxPython中执行单元测试。在第六章中我们将开发一个演示程序,这个程序包含了WxPython中很多有用的功能。学完第一部分之后你将具备WxPython的基础知识,为后续章节的学习打下基础。

WxPython 编程指南 前言

WxPython最早开始与1995年,由Harri Pasanen和 Robin Dunn发明。 Robin同时还是本书的核心作者。我觉得应该把WxPython诞生的故事告诉大家。
早在1995年我工作的一个项目需要开发的应用可以运行在HP-UINUX的平台上,但是我的老板还希望这个程序可以在他的笔记本电脑上的Windows3.1系统上。 于是我开始寻找一个平台独立的C++GUI工具。在那时还没有Google,所以找的很辛苦。但是,我还是找到了几个商业工具(今天这些工具已经不负存在了)还有一些免费的开源工具。
在我评估哪个免费工具适合我近期的应用并选择为使用那个商业工具作为长期应用拿不到主意的时候。我在wxWidgets网站上看到了“Python 群组”(这个“群组”中提到了Python和wxWidgets之间的联系)。我对此充满了好奇(在这之前我从来没有听说过Python语言),我在Python1.2指南中一个一个的打开链接。3个小时后我从一个C++大师变成了一个Python布道者。我向周围所有的开发人员介绍和掩饰我找到的好东西。
我放弃了开发程序的原型,开始和Harri Pasanen在荷兰以前研究wxWidgets与Python的绑定。当时的wxPython0.2还得到了Edward Zimmerman.的帮助。当时在邮件列表中的发布声明档案可以在这里找到(http://www.google.com/groups?selm=PA.95Jul27032244%40ok.tekla.fi&oe=UTF-8)。当时的版本具备了基本的功能,足够我的老板使用它创建原型。但是当时的使用wxPython开放简直是个恶梦,因为所有的开放都是手工编写(C++实现模型代码,Python作为代理模型,编译系统等等工作),一个小小的改动或者完善都要在wxPython代码中修改多处。当代码达到数万行的时候开始变得越来越笨拙、脆弱。那时还没有源代码仓库的概念(sourceforce.net还没有出现),我们通过emial互相交换代码-代码的变更难于控制。
那时,我必须为我的主要项目作些“实际”的工作了,每次开放会议上我手下的几个开发人员都看着我项目的最后期限也快到了,并且我发现我又完全回到了C++的世界,尽管这时我已经可以使用python为项目作一些东西,或者些测试脚本。Harri也没有更多时间花在这上面了,所以wxPython的开发变得越来越慢,甚至最终几乎停滞不前。
1997年,我开始研究SWIG,我注意到它对于发布的wxPython项目维护很有帮助。在接下来的3、4周里通过SWIG我几乎解决了wxPython中遗留的所有问题,后来又全力的工作了几周并和Harri利用几个月的时间手工完成了大部分工作。在完成了另一个项目之后不久,我意识到可以开发wxPython2.0了,但是需要一个全新的架构,所以我必须从头再来。不过新的架构开放只用了一周的时间。所以在1998年的夏天,第一个“摩登版本”的wxPython发布了,并且到了一个活跃的开发阶段。第一个版本发布文档在这里(http://groups.yahoo.com/
group/python-announce-list/message/95)
如你所见,以上就是wxPython的历史。特别需要提到的是SWIG让我可以轻松的控制成千上万行的代码,所以说David Beazley和其他开发人员为wxPython作出了巨大的贡献。
在这本书中我希望和你共享wxPython中振奋人心的部分,这是一个非常易于使用的GUI工具。我们写这本书的目的是为初学者和开发人员提供有用的参考资源。

WxPython 编程指南(章节 目录)

章节
第一部分 WxPython介绍
1、 初识WxPython
2、 给你的WxPython程序提供实用的功能
3、 在事件驱动环境中工作
4、 使用PyCrust让WxPython的控制更加简单
5、 蓝图设计
6、 运行一段简单的代码
第二部分 WxPython基础
7、 基本控件的使用
8、 在窗口中放置控件
9、 为用户提供选择窗口
10、 创建和使用菜单
11、 调整控件的尺寸
12、 控制简单的图片
第三部分 WxPython高级应用
13、 使用列表控件
14、 网格控件
15、 树状控件
16、 在你的应用程序中使用HTML
17、 WxPython的打印框架
18、 其他WxPython功能






















1、 初识WxPython
1.1 WxPython入门
1.2 建立一个最小的WxPython应用程序
1.3 扩展这个最小的应用程序
1.4 创建最终的hello.py应用程序
1.5 WxPython能作什么
1.6 为什么选择WxPython
1.7 WxPython是如何运行的
1.8 小节
2、 给你的WxPython提供实用的功能
2.1 有关需求对象我们需要知道些什么?
2.2 如何创建并使用应用程序对象?
2.3 如何从WxPython程序直接输出?
2.4 如何关闭WxPython程序
2.5 如何使用顶端窗口对象
2.6 如何在窗口中增加对象和子窗口
2.7 如何使用通用对话框
2.8 窗口的通用错误是什么
2.9 小节
3、在事件驱动环境中工作
3.1 我们应该知道哪些有关事件(Events)的术语
3.2 什么是事件驱动的程序
3.3 如何绑定一个事件
3.4 WxPython 的事件是如何运行的
3.5 应用程序对象还包含哪些事件属性
3.6 如何创建自定义事件
3.7 小节
4、使用PyCrust让WxPython开发更简单
4.1 我使如何开放WxPython程序的
4.2 PyCrust 有哪些有用的功能
4.3 PyCrust 记事本选项卡是什么
4.4 我是如何使用在PyCrust的协助开放WxPython程序的
4.5 还有什么其他的Python包
4.6 如何在WxPython中使用其他Python包
4.7 小节
5、创建你的蓝图
5.1 如何重构提升我的代码
5.2 如何分离试图和模型
5.3 如何对GUI程序进行单元测试
5.4 小节
6、允许一段简单的程序
6.1 在屏幕上绘图
6.2 增加Windows装饰
6.3 获得标准信息
6.4 让你的应用程序看起来更漂亮
6.5 小节
第二部分 WxPython基础
7、使用标准控件
7.1 显示文本
7.2 使用按钮
7.3 输入并显示数字
7.4 让用户选择
7.5 小节
8、在窗口上放置组件
8.1 窗口的生命周期
8.2 使用窗口
8.3 非传统窗口
8.4 使用拆分窗口
8.5 小节
9、为用户提供选择窗口
9.1 使用模态对话框
9.2 使用标准对话框
9.3 创建向导窗口
9.4 显示每日一帖
9.5 使用校验器控制对话框中的数据
9.6 小节
10、创建并使用菜单
10.1 创建菜单
10.2 介绍菜单项
10.3 装饰你的菜单
10.4 菜单的有用规则
10.5 小节
11、调整尺寸对象
11.1 尺寸对象(Sizer)是什么
11.2 网格的尺寸对象
11.3 使用其他的尺寸对象
11.4 使用尺寸对象的实例
11.5 小节
12、操作基本的图片
12.1 操作基本的图片
12.2 设备环境详解
12.3 图形控制
12.4 小节
第三部分 高级WxPython
13、创建列表控件并操作列表项目
13.1 创建列表控件
13.2 操作列表控件中的项目
13.3 显示列表控件
13.4 列表控件的编辑和排序
13.5 创建虚拟的列表控件
13.6 小节
14、网格控件
14.1 创建网格控件
14.2 使用网格控件
14.3 自定义装饰器和编辑器
14.4 捕获用户事件
14.5 小节
15、树型控件
15.1 创建树型控件并添加项目
15.2 通过styles控制树型控件的显示
15.3 树型控件排序
15.4 操作树型控件显示图片
15.5 树型控件的导航
15.6 控制树型控件的选择
15.7 控制树型控件中的项目是否显示
15.8 让树型控件科研编辑
15.9 树型控件的其他事件
15.10 使用树型控件
15.11 小节
16、在应用程序中使用HTML
16.1 显示HTML
16.2 操作HTML窗口
16.3 扩展HTML窗口
16.4 小节
17、WxPython打印框架
17.1 WxPython 是如何打印的
17.2 如何显示打印窗口
17.3 如何显示页面设置窗口
17.4 如何输出打印
17.5 如何执行打印预览
17.6 小节
18、使用WxPython的其他功能
18.1 使用剪贴板
18.2 实现拖放的源
18.3 实现拖放的目标
18.4 自定义对象传输
18.5 使用wx.Timer 设置时间事件
18.6 创建多线程窗口
18.7 小节

导数据真是件苦差事

昨天写了一部分到数据的存储过程,将一个表的数据导入到4个表中,存储过程竟然有400行之多,别说我水平差,我已经尽量优化了。主要是数据关系太复杂了。

开始导数据了

今天开始正式的将原有系统的数据导入到新系统中,两个系统的表结构差异太大,很麻烦啊。有个dba就好了:)

郁闷的心情

中午本来高高兴兴去吃午饭的,可是出门的时候忽然想起忘了把钥匙给同事。就因为这事她又和我生气了,虽然哄了好久还是不理我。搞不清楚一点点小事,至于吗。是,我是有这样的毛病,一出门就发现自己忘记了什么。可是两个人在一起不是应该互相忍让的吗?烦:(

Monday, August 14, 2006

JDO 中的实例回调

你的持久化类可以通过继承javax.jdo.InstanceCallbacks接口来实现回调。这个接口包括4个方法:

jdoPostLoad:这个方法在你的持久化类从数据库中提取数据的时候被JDO实现调用。有关持久字段与数据库字段的映射和提取的描述在JDO源文件中进行描述。

jdoPreStore:这个方法在你向数据库写入数据时被调用。

jdoPreClaer:这个方法在你清除持久化字段的值之前被调用。

jdoPreDelete: 这个方法在从数据库中删除一个对象的时候被调用。

与PersistenceCapable接口不同,如果你要实现回调那么这个接口里的所有方法你都得自己来实现。

下面给出一个实例回调得例子:

public class Host implements InstanceCallbacks

{

//由于InetAddress字段不能被JDO直接持久化,所以我们通过jdoPostLoad和

//jdoPreStore方法间接的通过主机名这个字段来实现对它的存储。

private transient InetAddress address; // 非持久化字段

private String hostName; // 可持久化字段

// 定义一个HashSet对象准备把InetAddress放进去

private Set devices = new HashSet ();

public void jdoPostLoad ()

{

//通过主机名获得 InetAddress

try

{

address = InetAddress.getByName (hostName);

}

catch (IOException ioe)

{

throw new JDOException ("Invalid host name: " + hostName, ioe);

}

}

public void jdoPreStore ()

{

// 获得主机名

hostName = address.getHostName ();

}

public void jdoPreDelete ()

{

// 当主机地址被删除时,删除所有相关的信息

JDOHelper.getPersistenceManager (this).deletePersistentAll (devices);

}

public void jdoPreClear ()

{

}

}

JDOHelper 介绍

这一节我们主要讲述JDOHelper这个助手类中的方法,下面的表格列出了JDOHelper中几个常用的方法,如果你想详细的了解JDOHelper所有的方法可以参考JavaDoc文档中的定义。
JDOHelper

static void makeDirty(Object pc ,String field)

static Object getObjectId(Object pc)

static PresistenceManager getPersistenceManager(Object pc)

static boolean isDirty(Object pc)

static boolean isTransactional(Object pc)

static boolean isPersistent(Object pc)

static boolean isNew(Object pc)

static boolean isDelete(Object pc)

static PersistenceManagerFactory getPersistenceManagerFactory(Properties props)



JDOHelper主要进行3个类型的操作:

1、 对象的持久化操作

2、 对象生命周期的操作

3、 创建PersistenceManagerFactory对象。

下面我将一一讲述这3个操作的过程:

1、 对象持久化操作

对象持久化操作要使用到3个方法:makeDirty,getObjectId和getPersistenceManager。其中makedirty(Object pc,String field)方法是在你操作一个持久化对象的数组字段时使用的,你只要调用这个方法传入要操作的持久化对象,和已经改变了值的字段就会通知JDO实现,你已经修改了字段的值,可以把新的值保存到数据库中了。getObjectId用户比较持久化对象的一致性,如果传入的参数对象与当前比较的持久化对象不是同一个对象这个方法将返回null值。最后一个方法会得到一个PersistenceManager对象。



2、 对象生命周期操作

public static boolean isDirty (Object pc);

public static boolean isTransactional (Object pc);

public static boolean isPersistent (Object pc);

public static boolean isNew (Object pc);

public static boolean isDeleted (Object pc);

上面列出的几个方法是对一个持久化对象状态的判断,这些状态的判断是JDO实现进行的,你根本不用操心。

3、 创建PersistenceManagerFactory对象

public static PersistenceManagerFactory getPersistenceManagerFactory (Properties props);



你可以通过JDOHelper的getPersistenceManagerFactory方法来获得PersistenceManagerFactory对象,这个方法只有一个参数,是一个java.util.Properties对象。在使用之前你要先设置这个Properties对象的实例。通过调用getPersistenceManagerFactory方法JDO实现会创建一个PersistenceManagerFactory对象或者是一个池化的包含很多PersistenceManagerFactory的实例池。下面我给出一个获得PersistenceManagerFactory的例子:



// properties 通常来自于一个文件

Properties props = new Properties ();



// 这个property参数告诉JDOHelper将产生什么样的PMFactory

props.setProperty ("javax.jdo.PersistenceManagerFactoryClass",

"kodo.jdbc.runtime.JDBCPersistenceManagerFactory");



// 项目的方法定义了 persistence managers 的缺省设置

props.setProperty ("javax.jdo.option.Optimistic", "true");

props.setProperty ("javax.jdo.option.RetainValues", "true");

//下面是设置有关数据库信息的,包括用户名、密码、驱动、连接

props.setProperty ("javax.jdo.option.ConnectionUserName", "solarmetric");//

props.setProperty ("javax.jdo.option.ConnectionPassword", "kodo");//

props.setProperty ("javax.jdo.option.ConnectionURL", "jdbc:hsql:database");//

props.setProperty ("javax.jdo.option.ConnectionDriverName", //

"org.hsqldb.jdbcDriver");

//获得一个pmfactory对象。

PersistenceManagerFactory pmf = JDOHelper.getPersistenceManagerFactory (props);

JDO 中的PersistenceManageFactory

PersistenceManageFactory

JDO中一个很重要的类除了JDOHelper助手类之外就是这个PersistenceManagerFactory了,它是进行JDO开发初始化配置和获得PersistenceManager的工厂类,写这篇文章的目的是为了详细介绍这个类的内部方法和它们的用途以便大家在使用JDO是作为参考。

PersistenceManagerFactory



上图显示了PersistenceManagerFactory的主要属性和方法。PersistenceManagerFactory负责为应用程序创建PersistenceManager对象实例,它可以设置数据库连接的相关参数并可以设置创建的PersistenceManager对象的缺省设置。你也可以使用它来规划JDO厂商的实现,根据不同厂商提供的附加支持实现应用的优化。

下面我将对PersistenceManagerFactory进行详细的介绍:

1、 如何获得PersistenceManagerFactory

多数JDO厂商都提供构造PersistenceManagerFactory的方法, 每个厂商之间也许存在着不同,但一般情况下还是推荐使用标准的JDOHelper的getPersistenceManagerFactory方法,这个方法的Properties参数可以设置这个工厂对象的属性,一旦获得这个工厂对象,那么他的所有属性会被“冻结”,也就是说你不能再修改这个对象的属性,如果你试图这样做的话,会抛出一个JDOUserException异常,这是因为这个工厂对象可能是来自一个对象池或着它还要被共享供其他的应用使用。

JDO要求具体的PersistenceManagerFactory类必须实现Serializable接口,这样就可以把它放到一个文件或是JNDI树中以便随时使用。



2、 PersistenceManagerFactory的属性

PersistenceManagerFactory中大部分方法是标准的JavaBean风格的getter和setter方法他们对应相应的属性字段(如上图所示)。这些属性可以分为两个范畴Connection的配置以及PersistenceManager和Transaction的缺省选项

1) Connection配置

所谓Connection就是数据库连接,我通过一段例子代码来讲述它的设置过程。

下面这段代码告诉JDOHelper如何连接到数据库和数据库的登录用户名

public String getConnectionUserName ();

public void setConnectionUserName (String user);

props.setProperty ("javax.jdo.option.ConnectionUserName", user);

设置连接数据库的密码



public String getConnectionPassword ();

public void setConnectionPassword (String pass);

props.setProperty ("javax.jdo.option.ConnectionPassword", pass);

设置数据库连接的信息

public String getConnectionURL ();

public void setConnectionURL (String url);

props.setProperty ("javax.jdo.option.ConnectionURL", url);

设置数据库的驱动

public String getConnectionFactoryName ();

public void setConnectionFactoryName (String name);

props.setProperty ("javax.jdo.option.ConnectionFactoryName", name);

通过JNDI查找连接工厂,你可以使用连接池通过DataSource获得这个连接工厂

public Object getConnectionFactory ();

public void setConnectionFactory (Object factory);

//用于获得本地连接的方法

public Object getConnectionFactory2 ();

public void setConnectionFactory2 (Object factory);

2) PersistenceManager和Transaction的缺省选项

下面的方法将设置由PersistenceManagerFactory创建的PersistenceMangere和相关的Transaction对象的缺省属性。有些JDO实现厂商也许没有实现全部这些属性,当你设置一个没有被支持的属性的时候会抛出一个JDOUnsupportedOptionException异常。

public boolean getMultithreaded ();

public void setMultithreaded (boolean multithreaded);

props.setProperty ("javax.jdo.option.Multithreaded", multithreaded);

上面的值如果被设置为true,代表PersistenceManger对象和由这个对象控制的持久化对象可以同时被多个线程访问,如果被设置为false,一些JDO实现可以通过避免同步来优化执行。

public boolean getOptimistic ();

public void setOptimistic (boolean optimistic);

props.setProperty ("javax.jdo.option.Optimistic", optimistic);

上面的方法缺省为true,代表使用优化处理,有关优化处理将在TransactionType一节中讨论。

public boolean getRetainValues ();

public void setRetainValues (boolean retain);

props.setProperty ("javax.jdo.option.RetainValues", retain);

如果上面的值设置为true,代表当数据被提交到数据库之后,持久化对象的字段值仍然保留,否则就清除字段的值。

public boolean getRestoreValues ();

public void setRestoreValues (boolean restore);

props.setProperty ("javax.jdo.option.RestoreValues", restore);

上面的方法用于控制持久化对象的行为和字段的回滚。

public boolean getNontransactionalRead ();

public void setNontransactionalRead (boolean read);

props.setProperty ("javax.jdo.option.NontransactionalRead", read);

这些方法定义了你是否能够通过Transaction读取持久对象的状态。如果被设置为false,那么你通过外部的Transaction来对持久化对象执行查询就会抛出一个JDOUserException异常。

public boolean getNontransactionalWrite ();

public void setNontransactionalWrite (boolean write);

props.setProperty ("javax.jdo.option.NontransactionalWrite", write);

这些方法定义了你是否可以通过外部的Transaction对持久化对象进行写的操作,如果是false,那么你通过外部的Transaction来对持久化对象执行更新操作时就会抛出一个JDOUserException异常。



public boolean getIgnoreCache ();

public void setIgnoreCache (boolean ignore);

props.setProperty ("javax.jdo.option.IgnoreCache", ignore);

上面的方法设置当在一个持久化对象上执行”测试查询”时,是否在当前的处理过程中更改持久化对象的状态,true标识忽略更改。如果设置为false那么在执行查询的时候会立即将结果更新的到数据库。

3、 如何获得PersistenceManager

获得PersistenceManager可以调用下面的方法

public PersistenceManager getPersistenceManager ();

public PersistenceManager getPersistenceManager (String user, String pass);

两个方法都可以获得PersistenceManage,第一个方法通过PersistenceManagerFactory的setConnectionUserName和setConnectionPassword方法来设置数据库的连接信息,第二个通过传入的参数来设置数据库连接的用户名和密码。



4、 属性和支持的选项设置



public Properties getProperties ();

public Collection supportedOptions ();

getProperties方法可以获得一个Properites对象,他包含的特定的JDO厂商信息和版本信息,其中包含两个键:

VendorName:JDO厂商的名称

VersionNumber:JDO实现的版本号

getProperties方法可以获得一组包含字符串(String)的集合,它包含JDO实现实现的选项:

l javax.jdo.option.TransientTransactional

l javax.jdo.option.NontransactionalRead

l javax.jdo.option.NontransactionalWrite

l javax.jdo.option.RetainValues

l javax.jdo.option.Optimistic

l javax.jdo.option.ApplicationIdentity

l javax.jdo.option.DatastoreIdentity

l javax.jdo.option.NonDurableIdentity

l javax.jdo.option.ArrayList

l javax.jdo.option.HashMap

l javax.jdo.option.Hashtable

l javax.jdo.option.LinkedList

l javax.jdo.option.TreeMap

l javax.jdo.option.TreeSet

l javax.jdo.option.Vector

l javax.jdo.option.Map

l javax.jdo.option.List

l javax.jdo.option.Array

l javax.jdo.option.NullCollection

l javax.jdo.option.ChangeApplicationIdentity

l javax.jdo.query.JDOQL

JSF中的状态事件

当JSF接收到一个请求的时候,会执行6个步骤,称之为请求处理生命周期(RPL)。在这个过程中JSF会重建请求视图,通过数据输入组件把请求参数翻译成组件值,更新支持Bean或者数据模型,提交事件监听器,最后将响应返回给用户。状态事件在上面的每一个步骤的前后都会产生(我们将在随后陆续介绍RPL)。
状态事件由JSF框架本身产生而不是由组件触发,必须实现一个Java接口来注册这个监听器。这一实现过程通常由JSF内部完成,当然开发人员也可以根据自己的需要实现相应的接口。例如,通过它你可以在页面显示之前初始化一个支持Bean(SUN的Java Studio Creater允许自动控制支持Bean操作状态事件)。下面这个例子演示了在页面显示之前注册状态监听器的方法:
lifecycle.addPhaseListener(
new PhaseListener()
{
public void beforePhase(PhaseEvent event)
{
priceQuote = QuoteService.getLatestQuote(currentQuoteId);
}
public void afterPhase(PhaseEvent event)
{
}
public PhaseId getPhaseId()
{
return PhaseId.RENDER_RESPONSE;
}
});
在上面的例子中我们给先前定义的lifecycle对象增加了一个PhaseListener状态监听器。PhaseId(就是不同的方法,如before,after)告诉lifecycle什么时候触发这些事件。在这个例子中,会在页面显示之前触发一个事件。beforePhase在页面显示之前会被触发。afterPhase在页面显示之后触发。在beforePhase方法中我们用来自QuoteService中最后的值更新了priceQuote属性。因为在页面显示之前我们就更新了priceQuote的值所以在页面显示后这个值可以被组件显示。
如你所见,在JSF中你无需关注底层协议,只要处理相应的事件和监听器就可以了。当然这并不意味着,你不必理解底层协议的工作,这样作的目的是为了每天的开发工作变得简单(如果你还是喜欢从总体上进行控制,别担心,如果需要你仍然可以使用 Servlet API)。因为事件和监听器是JSF开发的基础,所以你在后续的讲解中会不断的看到各种有关的例子,这里是对事件和监听器一个概要的介绍。

Sunday, August 13, 2006

JSF中的数据模型事件

当一个数据模型组件中执行行操作的时候,就会触发数据模型事件。这个事件一般在类似HtmlDataTable这样的“数据表格”组件中被触发。与值变化事件和动作事件不同,数据模型事件必须实现一个Java接口。数据模型事件和前面提到的事件稍有不同,因为它实际上不是由UI组件触发的,而是由一个叫做“DataModel”的实例触发的,这个“DataModel”是数据模型组件的内部对象。“DataModel”可以封装为列表、数组、数据库游标结果集或者其他数据源。由于这个事件是由组件内部的模型对象触发的,所以你不能在JSP中为它注册监听器。你只能通过Java代码来注册:
FacesContext facesContext = FacesContext.getCurrentInstance();
dataTable = (HtmlDataTable)facesContext.getApplication().createComponent(
HtmlDataTable.COMPONENT_TYPE);
DataModel myDataModel = new ResultSetDataModel(myResultSet);
myDataModel.addDataModelListener(new DataModelListener()
{
public void rowSelected(DataModelEvent e)
{
FacesContext.getCurrentInstance().getExternalContext().
log("row selected:" + e.getRowIndex());
}
});
dataTable.setValue(myDataModel);
在上面的例子中我们首先创建了一个HtmlDataTable组件的实例,并通过已有的JDBC结果集创建了一个ResultSetDataModel模型。接着我们为ResultSetDataModel增加了一个DataModelListener监听器,并且把数据模型的设置为HtmlDataTable组件的值。每次HtmlDataTable中的记录进行迭代的时候我们的监听器就会被触发,这通常发生在组件被显示的时候。由于数据模型事件会被触发多次,所以它一般会被用于开发数据驱动的组件,在实际的应用程序开发中很少会用到。

Friday, August 04, 2006

用gmail写博客

  刚刚申请了blogger.com。就是google的博客。除了可以在blogger中写blog外,你还可以通过邮件编写blog.
方法就是在"设置"中。设置Mail-to-Blogger 地址,这样你只要向这个地址发邮件就可以张贴blog了。我已经试过了。非常好用:)

忽然发现blogger.com用了python

当访问atom时,发现blogger.com返回一个500.py的页面。哈哈

Thursday, August 03, 2006

JSF中的事件监听器2(动作事件)

动作事件在用户通过一个组件发出命令时被触发。比如按钮或者超链接这样的组件可以产生动作事件,或者叫动作源。动作事件由动作监听器控制。

有两种类型的动作监听器。一种会涉及到页面的导航,一种不会。涉及到页面导航的动作事件会执行一个过程并且返回一个结果,JSF导航系统根据这个结果跳转到下一个页面(也可以时当前的页面)。不涉及导航的动作监听器负责控制当前页面的组件,或者执行一个过程来改变模型对象或者支持Bean的属性,但是它并不会修改用户正在访问的页面。因此,通常只有在监听器执行完操作之后页面才会重显显示。

理论上说,所有的导航都是通过单一的动作监听器控制的。监听器会自动处理来自任何组件触发的事件,因此,无需手工注册监听器。缺省情况下,监听器就是你在支持Bean中实现的动作方法(action method,所以你可以在应用程序的不同部分使用不同的动作方法来控制程序。通常,应用程序的业务逻辑也都放在这些方法中实现(动作监听器是插件化的,就是说你可以在很多地方使用一个动作监听器)。

当组件触发一个事件的时候,缺省的监听器会判断它返回的结果字符串,比如"mainmenu", "success",或者"failure"。有两种基本的返回类型:静态的和动态的。静态结果是硬编码的,它又组件声明或者在代码中设置,比如:

success"
immediate="true"/>

在上面的例子中,当用户点击一个按钮的时候,会返回一个“success”结果,并触发一个动作事件,但是不会调用动作方法。

动态结果又动作方法返回,一个动作方法会根据不同的业务逻辑返回不同的结果。动作监听器会查找又JSF EL定义的action属性,这个属性就是动作方法。下面是一个HtmlCommandButton执行动作方法的例子:

action="#{loginForm.login}"/>

当用户单击按钮时,会执行下面的动作方法:

public class LoginForm

{

...

public String login()

{

if (...) // login is successful

{

return "success";

}

else

{

return "failure";

}

}

...

}

根据应用程序逻辑这个动作方法会返回"success"或者"failure"结果。LoginForm时一个支持Bean,它把页面组件的值封住为自己的属性。

在我的应用程序中有自己的业务逻辑,你的应用程序中的动作方法同样可以控制JSF组件、模型对象或者增加消息等。他们还可以完成其他更多的任务,比如执行页面跳转,装饰相应对象(一个图片或者其他二进制数据对象),增加事件,或者和数据库,EJB服务器,web服务等进行交互。监听器通过返回结果来控制页面的导航。

当你需要执行一个业务逻辑的时候,你无需设置页面导航,你只要给组件设置一个事件监听器就可以了。与事件方法不同事件监听器可以在组件触发事件的时候直接访问组件。看看下面的例子:

actionListener="#{myForm.doIt}"

在上面的例子中用户点击按钮之后触发了动作事件,但是不是调用动作方法而是调用了事件监听器方法:

public void doIt(ActionEvent event)

{

HtmlCommandButton button = (HtmlCommandButton)event.getComponent();

button.setValue("It's done!");

}

这个方法修改了按钮的标签值,当然这并不常用。与动作方法不同,动作方法没有参数并行会返回一个结果,动作监听器有一个ActionEvent参数而且不返回任何结果。这个方法执行后页面会重显。

通常你可以使用动作监听器有关有关页面的值。和值变化监听器一样你也可以通过实现一个接口来实现动作监听器类,不过通常将它作为一个支持Bean中的方法就足够用了。

JSF 中的事件监听器已近值变化事件

我和我妻子养了几只猫。他们要吃各种各样的食物,所以我必须每天喂他们好几次。有趣的是,不管是我早上从楼上下来还是晚上很晚的回来,他们总是跑过来向我要吃的。这听起来没什么,但是不管在何时我有点害怕这些猫又来要吃的。

前不久,我发现这些猫是事件驱动的。这些猫并不是真的饿了,只不过是在我长时间不在后又出现在他们面前的时候,他们以为是吃饭的事件到了。我的出现对于猫来说意义重大,那代表着吃饭的时间到了。

对于UI来说,事件捕获了来自用户对UI组件的操作。一个事件可以是简单的鼠标单击动作,也可以是相对复杂的操作,比如用户执行一个特定的命令。就像Swing那样,JSF通过JavaBean来控制事件对象和监听器。任何组件都可以发出零到多个事件,开发人员(或者组件自身)也可以注册零到多个监听器来处理这些事件。我把我的猫想象成食物监听器,他们监听着产生食物的事件

对于Web应用程序来说事件的内容发生了根本性的变化。通常,web应用程序的开发人员必须考虑请求-相应对象,这两个对象是底层Http协议的通信机制。对于大多数应用程序来说这没什么问题,但对于面向业务的应用程序来说这带来了不必要的复杂性,这使应用程序与底层协议关联的太多。

在开发JSF应用程序的时候,集成应用程序业务逻辑的过程实际山就是给组件分配适当的监听器来处理相应的事件。你无需再考虑请求相应对象。JSF提供了常见的面向接口的方法来开发事件监听器,你还可以将一个方法直接注册成一个监听器(只要有适当的签名)。

4种标准事件:值变化事件,动作事件,数据模型事件和状态事件。当用户修改输入组件的值时,值变化事件将被触发。当用户激活命令组件的时候,比如按钮,动作事件被触发。当一个数据感知组件被选中一行的时候,数据模型事件被触发。当JSF处理一个HTTP请求的时候,状态事件被触发。

尽管,JSF框架只定义了这4种默认的事件,但是对于事件的类型并没有限制,第三方组件或者是开发人员自己都可以定义自己需要的事件类型。

1、 值变化事件

当用户在输入组件种录入一个新的值时,就会产生一个值变化事件。你可以通过值变化监听器来处理这个事件。

比如你在同一个页面上存在HtmlInputText HtmlPanelGrid两个组件:

...

注意:对于HtmlInputText组件我们通过JSF EL 指定了一个valueChangeListener监听器。这个监听器的指向了我们在支持Bean中定义的方法:myForm.processValueChanged。另外,panelGrid绑定到了支持BeanmyForm.changePanel属性,所以它可以通过Java代码控制。panelGridrendered属性是false,所以初始化时用户时看不到它的。

当用户修改了HtmlInputText的值并提交之后,JSF将产生一个值变化事件,这个事件将被注册的监听器方法处理:

public void processValueChanged(ValueChangeEvent event)

{

HtmlInputText sender = (HtmlInputText)event.getComponent();

sender.setReadonly(true);

changePanel.setRendered(true);

}

在上面的例子中监听器方面将组件的只读属性设置为true。因此,用户永远都无法修改这个组件的值。并且将changePanel面板的rendered属性设置为true,所以这个面板在页面再次显示的时候将会被用户看到。事件监听器还可以增加消息或者指向其他的JSF操作,或者执行应用程序逻辑。

另外,你还可以通过实现一个接口来创建一个事件监听器类,但是,大多数时候给组件关联一个相应的方法就足够用了。