GWT-Ext 体验之旅,第 4 部分

体验拖拽和通信

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: GWT-Ext 体验之旅,第 4 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:GWT-Ext 体验之旅,第 4 部分

敬请期待该系列的后续内容。

拖拽

在 GWT-EXT 中实现拖拽功能比较简单。GWT-EXT 在 com.gwtext.client.dd 包中提供了与拖拽相关的类。图 1 展示了这些类的关系。

图 1. com.gwtext.client.dd 包中与拖拽相关的类的关系图
com.gwtext.client.dd 包中与拖拽相关的类的关系图
com.gwtext.client.dd 包中与拖拽相关的类的关系图

其中,DragDrop 是一个基类,它定义了一些可以被拖拽的元素的接口和基本操作,如 startDrag, onDrag, onDragOver 和 onDragOut 等 Drag 事件。而继承自这个类的子类,功能上主要分为两类。一个是可以使得对象被拖动;一个是使得拖动对象可以被放置在 DropTarget 中。

首先,介绍一些能够帮助对象被拖动的类。

DD 类

用户可以通过调用 DD 的构造函数来使得对象能够被拖动。这种拖动使得对象会跟随鼠标的移动而移动。

DD dd = new DD(Component component);// 参数 component 是被拖动的对象

DDProxy 类

DDProxy 类继承自 DD 类。使用这个类来构造被拖动的对象时,该对象的边框会跟随鼠标的移动而移动。而等到鼠标释放时,该对象会被重新放置到鼠标停止的位置。

DD dd = new DDProxy(Component component);// 参数 component 是被拖动的对象

这里,举一个拖动 Panel 的例子。首先,定义一个 Panel,

 Panel draggable = new Panel();  
 draggable.setTitle("Draggable");  
 draggable.setBorder(true);

然后,将这个 Panel 作为参数来构造一个 DD 类 ,

DD dd = new DD(draggable);

这样,这个 Panel 就可以被拖动了。

图 2. 对象随鼠标移动的 Panel
对象随鼠标移动的Panel

若将 draggable 这个 Panel 作为参数来构造 DDProxy 类,

DD dd = new DDProxy(draggable);

则会出现这样的效果,

图 3. 边框随鼠标移动的 Panel
边框随鼠标移动的Panel
边框随鼠标移动的Panel

被移动的是 draggable Panel 的边框。

接下来,介绍一些被拖动对象可以被放置在其他对象中的功能类。这些类主要是 DDTarget 和 DragTarget,其中 DragTarget 继承自 DDTarget 。

DDTarget 类

将元素作为参数传递到 DDTarget 中,那么这个元素就可以是一个 Drop Target 。具体在应用中,我们一般使用 DragTarget 来完成这样的操作。

首先,用户需要将元素作为一个参数传递到 DragTarget 类的构造函数中。除此之外,还要定义一个 DropTargetConfig 类来配置 Drag Drop Group 的名字。这里,需要注意的有两点:
第一, 只有 Drag 元素和 DropTarget 元素具有同样 DragDropGroup 名字的,Drag 元素才能够被拖放到正确的 DropTarget 元素中去。方法如下 :

1. 构造 DropTargetConfig 类

DropTargetConfig cfg = new DropTargetConfig();

2. 指定 Drag Drop Group 名字

cfg.setdDdGroup(java.lang.String ddGroup);// 参数 ddGroup 指 drag drop group 的名字。

3. 调用 DropTarget 的构造函数来将拖拽元素和 DropTargetConfig 作为参数传入进去。

DropTarget tg = new DropTarget(Comopnent component ,DropTargetConfig cfg)

第二, 在 DragTarget 中,还需要实现 notifyDrop(DragSource source, EventObject e, DragData data) 方法以实现拖动后的效果。其中,source 是指可以被拖动到 DropTarget 上的元素; e 是指当前的事件; data 是指被拖动元素所包含的数据对象。

这里,我们举一个从 Tree 中拖动 TreeNode 元素到 Grid 中的例子来说明上述步骤。

图 4. 从 Tree 中拖动 TreeNode 元素到 Grid 中
从Tree中拖动TreeNode元素到Grid中
从Tree中拖动TreeNode元素到Grid中

首先,定义 TreePanel 和 GridPanel,并定义 Drag Drop Group 的名字。

 final TreePanel treePanel = new TreePanel(); 
 treePanel. setEnableDD(true); 
 treePanel..setEnableDrop(true); 
 final GridPanel gridPanel = new GridPanel(); 
 gridPanel .setEnableDragDrop(true); 

 tripTreePanel.setDdGroup("myDDGroup"); 
 gridPanel.setDdGroup("myDDGroup");

然后,初始化一个 DropTargetConfig,并指定同样的 Drag Drop Group 的名字。

 DropTargetConfig cfg = new DropTargetConfig();  
 cfg.setdDdGroup("myDDGroup"); // 指定了同样的 DDGroup

最后,定义 DropTarget,以 gridPanel 和 cfg 为参数,并覆写 notifyDrop 函数。

 DropTarget tg = new DropTarget(gridPanel,cfg) {  
    public boolean notifyDrop(DragSource source, EventObject e, DragData data) { 
        // ……
    } 
 };

在 notifyDrop 函数中,可以实现从当前 Tree 中删除选中节点和在 Grid 中增加记录的操作。

通信

GWT-EXT 是 GWT 在界面功能上的扩展,但在通信方面,使用的仍然是 GWT 所提供的通信框架。作为 AJAX 的应用开发包,GWT 使得实现客户端和服务器的通信变得十分简单,特别是异步通信。在本文中,我们首先介绍 GWT 中用来开发异步通信应用的流程,然后介绍一个异步通信的例子。除此之外,对于同步通信,由于 GWT 没有提供特别的支持,但有时我们仍需以同步的方式与 Server 端交互,所以,我们提供了一种变通的方法,即通过 Form 提交 Action 的方式实现此效果,并通过一个文件下载的例子来讲述。

在 GWT 框架下实现异步通信

在 GWT 中,异步通信方面的开发变得非常简单。用户只需在服务器端定义一个 Service 接口及其实现,在客户端声明这样的接口,之后的事情,例如管道管理和数据的传输、转换等复杂的事情将由 GWT 负责。具体的过程,有以下几个步骤。

1. 在客户端定义服务接口。服务,就像是客户端与服务器所签署的一份合约,客户端首先要定义这样的服务规则,也就是一个接口。具体实现时,此接口要继承自 RemoteService 并保存在客户端。

 Public interface MyService extends RemoteService{ 
	 Public List myRequestToServer(int requestParameters); 
 }

值得一提的是,GWT 中的 RPC 机制提供了通过 HTTP 协议在客户端和服务器端传送 java 对象的方法并加以封装。对于用户来说,只要使用 GWT 所能够支持的数据类型或对象作为参数和返回值就可以了。

2. 在客户端定义此服务的异步接口。为了应用 AJAX(被 GWT 所支持)所带来的异步特性,在客户端我们还需要定义一个此接口的异步接口。方法也很简单。

 Public interface MyServiceAsync extends RemoteService{ 
	 Public void myRequestToServer(int requestParameters, AsyncCallback callback); 
 }

可以看出,除了多了一个 AsyncCallback 对象作为参数,此异步接口与同步接口的方法大致相同。此外,该方法的返回值被定义成 void,原因是在 GWT 的异步通信机制中,客户端发送的异步请求信息和服务器端对此请求的回应信息(包括成功或失败)都会通过这个 AsyncCallback 对象来传递。换句话说,这个对象将客户端与服务器端绑定了起来,返回值在这里没有什么作用。除此之外,此异步接口的命名规则是在原接口名字的后面加上 Async 后缀,并放置在客户端的同一个包下面。

3. 实现这个接口。大家知道,所有的服务端接受到 Service 后都要执行一定的操作来响应客户端的请求。这里,所有的 RPC 服务都要扩展自 RemoteServiceServlet 并实现相应的 Service 接口。需要注意的是,它并不需要实现此 Service 的异步接口。

 Public MyServiceImpl extends RemoteServiceServlet implements MyService{ 
    Public List myRequestToService(int requestParameters){ 
        … .// 实现的功能代码
    } 
 }

其中,RemoteServiceServlet 类用于处理客户端请求的反序列化和相应的序列化以及调用接口中定义的方法。 GWT 之所以采用这种基于 Servlet 的处理方式,是因为 Java Servlet 相对简单且广泛流行于 Java Community 。这样,我们所定义的服务及实现就可以轻易的重新部署到其它产品的 Servlet 容器中。

4. 在客户端触发服务。在 GWT 框架下调用 Service 总是经过相同的步骤:

  • 使用 GWT.create()初始化 Service 接口 ;
  • 使用 ServiceDefTarget 为 Service 代理描述 Service 接入点的 URL ;
  • 创建一个异步回调对象,用于 RPC 完成时调用;
  • 发出 RPC 请求。

5. 将此服务作为一个 EntryPoint 加入到应用程序的配置文件 module.xml 中,以便客户端能够找到此服务。

<Servlet path= ” /myService ” class= ” examples.server.MyServiceImpl ” />

以上五步是在 GWT 框架下开发异步通信应用的流程。而在实际开发中,我们可以利用一些 Eclipse 的插件帮助我们快速地完成此过程的搭建。接下来,将介绍一个异步通信的例子并用 Eclipse 下的 GWT 插件 Cypal Studio 来完成创建的过程。

这是一个简单的做两位数加法的例子。用户在输入框中输入两个整型数后,不必刷新整个页面,Server 端就会返回结果。

图 5. 做两位数加法的例子
图5. 做两位数加法的例子
图5. 做两位数加法的例子

为了完成这个例子,首先在一个 Panel 中创建两个 TextField 和两个 Label 。

 TextField textField1 = new TextField(); 
 Label addLabel = new Label( “ + ” ); 
 TextField textField2 = new TextField(); 
 Label equalLabe2 = new Label( “ = ” );

然后,通过 Cypal Studio 插件来创建计算两个数加法的服务。 Cypal Studio 是 GWT 在 Eclipse 中的一个插件,它可用于简化在 GWT 开发过程中执行的许多常见任务。在安装 Cypal Studio 插件之前,您需要配有 Web Tools Platform(WTP) 插件的 Eclipse 版本。 WTP 是支持 Web 应用开发的精选工具集,它包括各种标准的 Web 编辑器,比如 HTML 和层叠样式表(Cascading Style Sheet,CSS)、JavaServer Page(JSP) 编辑器,支持创建和维护 Web 应用程序中使用的数据库,以及开发过程中在 Web 服务器上运行应用程序。因此,Cypal Studio For GWT 插件需要 WTP 才能运行。

这里,对如何安装 WTP 和 Cypal Studio For GWT 插件不介绍,用户可以参考以前的系列文章。

在安装 Cypal Studio For GWT 插件后,可以在 Eclipse 的 Window->Preferences 中找到它。

图 6. Cypal Studio For GWT 配置选项
图6. Cypal Studio For GWT配置选项
图6. Cypal Studio For GWT配置选项

在 Cypal Studio 配置窗口中,可以配置 GWT 的 Home 路径以及 GWT 编译后的 OutPut 路径。

现在,我们就应用 Cypal Studio 插件来创建我们服务。首先,点击 Eclipse 的 File->New->Other …菜单,选择 GWT Remote Service,如图 7 所示。

图 7. 选择向导的类型
图7. 选择向导的类型
图7. 选择向导的类型

然后,依次指定应用程序的配置文件路径,服务的名字,此服务的链接地址(Service URL)。

图 8. 指定配置文件路径,服务的名字和链接地址
图8. 指定配置文件路径,服务的名字和链接地址
图8. 指定配置文件路径,服务的名字和链接地址

设置好之后,点击 Finish 。你会发现此插件为我们自动加入了 CalcuService.java,CalcuServiceAsync.java, CalcuServiceImpl.java,

图 9. CalcuService.java,CalcuServiceAsync.java, CalcuServiceImpl.java 已加入工作空间
图9. CalcuService.java,CalcuServiceAsync.java, CalcuServiceImpl.java已加入工作空间
图9. CalcuService.java,CalcuServiceAsync.java, CalcuServiceImpl.java已加入工作空间

以及此服务的配置信息。

图 10. 服务配置信息
图10. 服务配置信息
图10. 服务配置信息

而我们所需要做的就是在 CalcuService 接口中定义方法以及在 CalcuServiceImpl 中实现此方法,如代码所示:

清单 1. 定义和实现 getAddResult 方法
 public interface CalcuService extends RemoteService { 
   String getAddResult(String str1, String str2); 
 } 

 public class CalcuServiceImpl extends RemoteServiceServlet implements CalcuService { 
   public String getAddResult(String str1, String str2) { 
      // TODO Auto-generated method stub 
      int result = new Integer(str1).intValue() + new Integer(str2).intValue(); 
      return result + ""; 
   } 
 }

另外,Cypal Studio For GWT 插件会自动监控接口中方法的变化。比如,我们在 CalcuService 中添加一个 getAddResult(String Str1,String Str2) 方法后,它会自动帮助我们在 CalcuServiceAsync 异步接口中添加这个方法。

 public interface CalcuServiceAsync { 
   void getAddResult(String str1, String str2, AsyncCallback<String> callback); 
 }

在定义好接口和实现后,接下来要做的就是定义触发此服务的方法。

示例中,一旦我们分别在两个输入框中输入整型数后,就会异步地向服务端发送请求。因此,需要为这两个输入框定义监听事件。

这里,我们定义一个内隐类并继承自 FieldListenerAdapter 类,然后覆写 onBlur 方法使得一旦输入框失去焦点时触发此事件。在 onBlur 方法中实现调用此服务的过程。

清单 2. DemoFieldListenerAdapter 类
 class DemoFieldListenerAdapter extends FieldListenerAdapter{ 
   public void onBlur(Field field) { 
      CalculateServiceAsync service = CalculateService.Util.getInstance();   
      service.getAddResult(textField1.getValueAsString(), 
         textField2.getValueAsString(), 
         new AsyncCallback(){ // 声明一个 //AsyncCallBack 实例
            // 覆写 onFailure 方法
            public void onFailure(Throwable caught) { 
               caught.printStackTrace(); 
               System.out.println("Communication failed"); 
            } 
            // 覆写 onSuccess 方法。
            public void onSuccess(Object result) { 
               String addResult = (String)result; 
               resultLabel.setText(addResult); 
            } 
         } 
      ); 
   } 
 }

需要说明的是,Cypal Studio For GWT 插件在生成 CalculateService 时为我们将初始化 CalculateServiceAsync 实例的过程封装在一个静态内隐类中。

清单 3. 初始化 CalculateServiceAsync 实例
 public static class Util { 
   public static CalcuServiceAsync getInstance() { 
      CalcuServiceAsync instance = (CalcuServiceAsync) GWT.create(CalcuService.class); 
      ServiceDefTarget target = (ServiceDefTarget) instance; 
      target.setServiceEntryPoint(GWT.getModuleBaseURL() + SERVICE_URI); 
      return instance; 
   } 
 }

这样,我们每次调用时只需通过 CalculateService.Util.getInstance() 方法即可获得 CalcuServiceAsync 的实例。获得 CalcuServiceAsync 实例后,即可调用服务接口。这里的参数有三个,第一个是 textField1 的输入值,第二个是 textField2 的输入值,第三个是 AsyncCallback 对象。 AsyncCallback 对象采用匿名内隐类来创建并覆写 onFailure 和 onSuccess 方法。这两个方法会在服务器端执行完服务过程后在客户端执行。如果服务器端有错误发生,则 onFailure 方法被执行;如果没有错误,则 onSuccess 方法被执行。例子中,在 onSuccess 方法里将成功返回的结果放在 ResultLabel 中显示。

在 GWT 框架下实现同步通信

在 GWT 框架中,其所提供的通信方式,包括 HttpRequest, RequestBuilder, RPC 等都是异步的,要实现同步的通信,可以利用 Form 中提交 Action 的方式。即通过 HTML 中的 Document 所得到的 FormElement 来构建指向服务器端的一个链接(URL)。然后,向 Server 端提交此 Form 。这样,客户端会一直等待服务器端对此请求的相应。代码如下 ,

清单 4. Form 中提交 Action
 String url= ” / ” ; 
 FormElement formElement = Document.get().createFormElement(); 
 formElement.setAction(url); 
 formElement.setName("ThisActionName"); 
 formElement.setMethod("post"); 
 Document.get().appendChild(formElement); 
 formElement.submit();

此 URL 是一个指向服务器端的资源路径,我们首先在 Form 中设定资源路径然后提交给服务器端。这与编写 HTML 很相似,只是这里我们用 java 代码的形式写出来。另外,此 URL 也可以指定到服务器中的 Servlet 。这样,可以完成更复杂的应用逻辑。

本例所讲的是一个在 GWT 下应用同步通信的方式完成一个文件下载的例子。

首先,要在客户端的一个触发事件中完成如上述请求服务的代码。

清单 5. 完成请求服务的代码
 // 这里指向一个专门负责下载文件的 Servlet ://DemoServiceConstants.DOWNLOAD_TASK_FILE 
 String URL= GWT.getModuleBaseURL() + DemoServiceConstants.DOWNLOAD_TASK_FILE + 
            "?fileLocation=" + fileLocation;; 

 FormElement formElement = Document.get().createFormElement(); 
 formElement.setAction(url); 
 formElement.setName("ThisActionName"); 
 formElement.setMethod("post"); 
 Document.get().appendChild(formElement); 
 formElement.submit();

其次,要在服务器端定义一个 DownloadFile Servlet 用于响应客户端的请求以及完成从服务器上读取文件和发送文件的 InputStream 和 OutputStream 。

清单 6. DownloadFile Servlet
Public class DownloadFile extends HttpServlet{ 
   // 覆写 doGet 方法, 参数 request 是来自客户端的请求对象
   // 参数 response 是服务器端的响应对象
   protected void doPost(HttpServletRequest request, HttpServletResponse response) throws 
   ServletException, IOException { 
   // 取得文件路径
   String location = request.getParameter("fileLocation"); 
   // 定义读取文件的类 RandomAccessFile 
   File file = new File(location); 
   RandomAccessFile accessFile = new RandomAccessFile(file, "r"); 
   // 如果想要在下载时出现一个下载对话框,可以在 Response Header 中做如下设置
   response.addHeader("Content-Disposition","attachment;filename=\""+file.getName( 
   )"\""); 
   response.addHeader("Content-Length", String.valueOf(accessFile.length())); 
   // 定义 OutputStream 
   OutputStream out = response.getOutputStream(); 
   // 从源文件流中读入并写入到输出流中 , 具体可参见代码
   …
   …
   } 
}

结束语

GWT-Ext 是目前主流的以 Java 为基础的 Web 界面编程工具。从笔者对几个 GWT 的扩展工具的实际经验横向比较来看,GWT-Ext 拥有非常优秀的编译打包速度,丰富的 API,加上大量的编程实例和论坛讨论,无可置疑的使 GWT-Ext 成为目前最优秀的 Web 2.0 的编程工具中的一员,相信 GWT-Ext 能有一个美好的未来。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Web development, Open source
ArticleID=405052
ArticleTitle=GWT-Ext 体验之旅,第 4 部分: 体验拖拽和通信
publish-date=06302009