博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

5.事件的监听和处理

Posted on 2007-04-17 14:39  daniel-shen  阅读(1601)  评论(0)    收藏  举报
事件的监听和处理
    本章将描述事件是如何处理的
    通过标记语言添加事件监听器
    增加监听器最简单的方法是在zuml页面中声明一个属性。监听事件的属性的值是能够被beanshell解释的java代码。
    <window title="hello" border="normal">
        <button label="say hello" onClick="alert(&quot;hello world!&quot)"/>
    </window>
    通过程序添加和移除事件监听器
    通过程序有两种方法添加事件监听器
   
    声明一个成员
    当通过自定义的类overriding一个组件的时候,你可以声明一个方法来作为一个事件监听器。
    在一张zuml页面中,use属性用来指定代替默认的自定义类。像下面表示的一样,这里需要zk使用myclass类来代替org.zkoss.zul.window.
    <window use="myclass">
    ...
    </window>
    然后你需要像下面这样通过扩展默认的类来实现mywindow.java
    public class MyWindow extends org.zkoss.zul.window{
        public void onOK(){//add an event listener
            ...//handles the onOK event(sent when enter is pressed)
        }
   }
   
    如果你想得到事件的更多信息,你可以像下面这样定义
public void onOK(org.zkoss.zk.ui.event.KeyEvent event) {
...
}
    不同的事件可能和不同的事件对象结合在一起。
  
动态的添加和移除事件监听器
程序员可以使用org.zkoss.ui.Component接口的addEventListener 和 removeEventListener方法来动态的添加和移除事件监听器。像下面展示的一样,可以被动态添加的监听器本身必须继承自org.zkoss.zk.ui.event.EventListener接口。
void init(Component comp) {
...
comp.addEventListener("onClick", new MyListener());
...
}
class MyListener implements org.zkoss.zk.ui.event.EventListener {
public void onEvent(Event event) throws UiException {
...//processing the event
}
public boolean isAsap() {
return true; //Refer the following section for description
}
}
ASAP是什么
在接口enentlistener中的isAsap方法定义了监听器的紧急事件。如果返回的是true,那么事件一旦发生就会被发送到服务器。
如果返回的是false,事件将不会被发送到服务器直到另一个ASAP事件发生。这样服务器的性能得到了提升,因为服务器和客户机的通信频率降低了。
注意这样做不会影响程序的正确性,因为应用保持空闲直到接受到一个事件,事件到达的顺序却是一致的(和发生的顺序一致)
动态的给页面添加和移除监听器
开发人员可以动态的给页面(org.zkoss.zk.ui.page)添加监听器,一旦添加了,all events of the specified name the are sent to any components of the specified page will be sent to the listener.
所有页面级的事件监听器均是非ASAP的.换句话说,isArap方法是被忽视的.一个典型的例子是使用一个页面级的事件监听器来保持修改是否发生的修饰符.
public class ModificationListener implements EventListener {
private final Window _owner;
private final Page _page;
private boolean _modified;
public ModificationListener(Window owner) {
//Note: we have to remember the page because unregister might
//be called after the owner is detached
_owner = owner;
_page = owner.getPage();
_page.addEventListener("onChange", this);
_page.addEventListener("onSelect", this);
_page.addEventListener("onCheck", this);
}
/** Called to unregister the event listener.
*/
public void unregister() {
_page.removeEventListener("onChange", this);
_page.removeEventListener("onSelect", this);
_page.removeEventListener("onCheck", this);
}
/** Returns whether the modified flag is set.
*/
public boolean isModified() {
return _modified;
}
//-- EventListener --//
public void onEvent(Event event) throws UiException {
_modified = true;
}
public boolean isAsap() {
return false;
}
}
调用的步骤
调用事件监听器的顺序如下.我们假设接受到一个onClick事件.
1.调用被加到目标组件上的关于onClick事件的事件监听器,如果监听器实现了org.zkoss.zk.ui.event.express接口.第一个添加的,就第一个调用.
2.如果在目标组件的onClick属性中有指定的脚本,则执行.
3.调用被加到目标组件上 关于onClick事件的监听器,若监听器没有实现org.zkoss.zk.ui.event.express接口.第一个添加的,就第一个调用.
4.如果在目标组件上有onclick的成员方法,则执行.
5.调用组件所属的页面的onClick事件的监听器.第一个添加的,就第一个调用.
org.zkoss.zk.ui.event.Express接口是一个装饰器用来改变事件监听器的优先级别.注意如果监听器被加到页面而不是组件,则是无意义的.
中断invocation sequence
你可以通过org.zkoss.zk.ui.event.Event类的stopPropagation方法来中断invocation sequence.一旦一个监听器调用了这个方法,则之后的所有监听器将被忽略.
 

通过事件监听器发送和提交事件

做为事件接受的补充,应用应当可以通过向事件监听器提交和发送事件在事件监听器之间交流。

      提交事件

通过使用类org.zkoss.zk.ui.event.EventspostEvent方法,一个事件监听器可以提交一个事件到一个事件队列的队尾。将事件放置完毕后立即返回。直到该事件之前的事件均被处理后,该事件才被进行处理。

发送时间

       通过使用类org.zkoss.zk.ui.event.EventssendEvent方法,一个事件监听器可以使ZK立刻处理一个指定的事件。直到所有的指定事件的事件监听器都被处理了才返回。事件是在同一个线程中处理的。

 

线程模型

对于每一个桌面,事件是顺序处理的,所以线程模型是很简单的。类似于开发桌面应用,你不需要去担心多线程。你所需要去做的是登记事件监听器和被调用时候的处理代码。

       小提示:each event listener executes in an independent thread called event processing thread.while the ZUML page is evaluated in the servlet thread.

 

挂起和恢复

对于高级的应用,你可能需要挂气一个执行知道满足继续执行的条件。类org.zkoss.zk.ui.ExecutionsWait,notifynotifyAll方法提供这些支持。

 

当一个事件监听器想挂起自己,它可以调用wait方法。另一个线程在应用指定的条件下可以通过调用notify或者notifyAll来唤醒该进程。下面是一个使用这种机制的例子。

Public void doModal() throws InterruptedException{

       Executions.wait(_mutex);

}

Public void endModal(){

       Executions.notify(_mutex);

}

这样的使用类似于类java.long.objectwait,notifynotifyAll的用法。尽管如此,你还是不能使用java.lang.Object的方法来进行挂起和恢复事件监听器的操作。否则,关联到这个桌面的所有事件处理都将被停止。

 

注意,不同于java objectwaitnotify方法,是否使用synchronized锁来关闭Executionswaitnotify是可选的。在上面的例子中,我们不需要这样做,因为没有没有可能的racing condition。尽管如此,如果存在racing condition,你可以象在java Object中使用waitnotify那样使用synchronized block

//thread 1

Public void request()

{

Synchronized (mutex)

{

              Executions.wait(mutex);

}

}

//thread 2

Public void process()

{

       Synchronized(mutex)

       {

              Executions.notify(mutex);

       }

}

 

长操作

对于同一个桌面事件是被顺序处理的。换个说法,一个事件的处理程序将可以阻塞所有的后续处理程序。一个长时间被阻塞的请求可能是不可接受的(the time blocking user’s requests might not be acceptable),如果一个事件的处理将花费大量的时间。象桌面应用,你需要创建一个专用于工作这种长时间处理的工作线程来减少阻塞时间。

限制于http协议,我们必须符合以下的规则。

1.  在创建了工作线程后,使用wait方法来挂起事件处理进程本身。

2.  因为工作线程不是一个事件监听器,所以它不能进入任何组件(除非组件不属于任何桌面)。因此,在开始工作线程前你可能需要手工通过一些必要的信息。

3.  然后,工作线程可以crush the information和创建组件(若必要的话)。只是不把任何组件关联到任何桌面。

4.  在工作线程结束之后,在工作线程中使用类org.zkoss.zk.ui.Executions notify(Desktop desktop,Object flag)或者notifyAll(Desktop desktop,Object flag)方法来恢复事件处理进程。

5.  直到从客户另一个事件被发送过来,恢复的事件处理进程才会执行。为了强制发送一个事件,你可以使用timer组件(org.zkoss.zul.Timer)来触发一个事件a moment later or periodically.这个timer的时间监听器不做任何事情。

例子:工作线程异步的创建标签

假设我们想异步的创建标签。当然,这有点象用牛刀来杀鸡了,但是我们可以用sophisticated one 来替换这个任务。

//Working Thread

Package test;

Public class WorkingThread extends Thread

{

private static int _cnt;

private Desktop _desktop;

private Label )label;

private final Object _mutex = new Integer(0);

 

public static final Label asyncCreate(Desktop desktop)

        throws InterruptedException{

        final WorkingThread worker = new WorkingThread(desktop);

        synchronized(worker._mutex)

        {

               worker.start();

               Executions.wait(worker._mutex);

               return worker._label;

        }

}

public WorkingThread(Desktop desktop)

{

        _desktop = desktop;

}

 

public void run()

{

        _label = new Label("Execute "+ ++_cnt);

        synchronized(_mutex)

        {

               Executions.notify(_desktop,_mutex);

        }

}

}

 

然后,我们有一个ZUML页面在一个事件监听器里来调用这个工作线程,如在onClick

<window id="main" title="Working Thread">

<button label="Start Working Thread">

        <attribute name="onClick">

               timer.start();

               Label label = test.WorkingThread.asyncCreate(desktop);

               main.appendChild(label);

               timer.stop();

        </attribute>

</button>

<timer id="timer" runing="false" delay="1000" repeats="true"/>

</window>

 

注意我们需要使用timer来真正恢复被挂起的事件监听器(onclick)。这看起来是多余的,但是归因于http的限制:为了保持web页面在浏览器中的alive,当事件处理进程被挂起的时候我们必须返回回应。然后,工作线程完成了工作,唤醒了事件监听器,http的请求已经gone了。因此,我们需要”piggyback”这个结果,这就是timer被使用的原因。

 

更准确的来说,当工作进程唤醒了事件监听器,ZK只是把它加到一个等待队列中。当另一个http请求到达的时候,监听器才真正的恢复。(在上面的例子中,是onTimer事件)

 

在这个简单里例子中,我们对onTimer事件什么都没做。对于一个sophisticated应用,你可以使用它来返回处理的状态。

 

另一个事例:没有挂起和恢复

没有挂起和恢复来执行一个长时间的操作是可能的。当同步代码对于调试来说太复杂的情况下是有用的。

 

主意很简单。工作进程在一个临时空间里保存结果,然后onTimer事件监听器将结果弹出到桌面。

//WorkingThread2

package test;

public class WorkingThread2 extends Thread

{

private static int _cnt;

private final Desktop _desktop;

private final List _result;

public WorkThread2(Desktop desktop,List result)

{

        _desktop = desktop;

        _result=result;

}

public void run(){

        _result.add(new Label("Execute "+ ++_cnt));

}

}

 

然后,在onTimer事件监听器上面追加labels

 

 

<window id = "main" title="working thread2">

<zscript>

int numpending = 0;

List result=Collections.synchronizedList(new LinkedList());

</zscript>

<button label="start working thread">

        <attribute name="onClick">

               ++numpending;

               timer.start()

               new test.workingthread2(desktop,result).start();

        </attribute>

</button>

<timer id="timer" running="false" delay="1000" repeats="true">

        <attribute name="onTimer">

               while(!result.isEmpty())

               {

                      main.appendChild(result.remove(0));

                      --numpending;

               }

               if(numpending==0)time.stop();

        </attribute>

</timer>

</window>

 

初始和清除事件处理线程

在处理每个事件之前初始化

 

一个事件监听器是在一个事件处理线程中执行的。有时,你不得不在处理所有事件之前初始该线程。

 

一个典型的例子是初始认证所使用的线程。一些j2ee或者web容器在thread local storage中存储着认证信息,因此它们可以在需要时自动进行重复认证。

 

为了进行事件处理线程的初始化,你需要在web-inf/zk.xml文件注册一个继承自org.zkoss.zk.ui.event.EventThreadInit接口的类。

 

一旦进行了注册,在开始一个事件处理线程之前,在主线程中一个指定类的实例就被创建了。然后在处理其他事情之前,该实例的init方法在事件处理线程的上下文中被调用了。

 

注意那个构造体和init方法是在不同的线程中被调用的,因此开发者可以从一个线程获得线程独立的数据发送到另一个线程。

 

下面是jboss的认证机制的例子。在这个例子中,我们在构造体中获得储藏在servlet线程中的信息。然后,我们在init方法调用的时候初始事件处理线程。

 

import java.security.Principal;

import org.jboss.security.SecurityAssociation;

import org.zkoss.zk.ui.Component;

import org.zkoss.zk.ui.event.Event;

import org.zkoss.zk.ui.event.EventThreadInit;

 

public class JBossEventThreadInit implements EventThreadInit

{

       private final Principal _principal;

       private final Object _credential;

 

       public JBossEventThreadInit()

       {

              _principal=SecurityAssociation.getPrincipal();

              _credential=SecurityAssociation.getCredential();

       }

 

       public void init(Component comp,Event evt)

       {

              SecurityAssociation.setPrincipal(_principal);

              SecurityAssociation.setCredential(_credential);

       }

}

 

然后在web-inf/zk,xml中,如下进行注册:

<zk>

       <listener>

              <listener-class>JBossEventThreadInit</listener-class>

       </listener>

</zk>

 

在处理完每个事件后清除

同样的,你可能不得不在处理完一个事件后清除一个事件处理进程。

 

典型的例子是关闭一个transaction,如果它没有被适当的关闭。

 

为了清除一个事件处理线程,你需要注册一个实现org.zkoss.zk.ui.event.EventThreadCleanup接口的监听类,然后在web-inf/zk.xml中注册。

 

<zk>

       <listener>

              <listener-class>my.MyEventThreadCleanup </listener-class>

       </listener>

</zk>