Gwt-Ext 的概述 GWT-Ext 是基于 Google Web Toolkit(GWT)和 ExtJs 的功能强大的网页开发控件库。它非常适用于进行纯 Java 语言的富 Internet 应用的快速开发。本系列文章将详细讲解 GWT-Ext 的基本结构和功能特点,并通过代码示例来演示该技术的具体实现。本文是该系列的第一部分,将带领您快速入门 GWT-Ext,并演示如何快速搭建 GWT-Ext 的开发环境。 概述 GWT-Ext 是基于 Google Web Toolkit(GWT)和 ExtJs 的功能强大的网页开发控件库。它扩展了 GWT,在 ExtJs 的基础上实现了有排序功能的表格(Grid)、分页、过滤,支持有拖拽功能的树,高度可定制的组合下拉框(Combobox)、目录、对话框、表单(Form)以及功能丰富、强大且易用的 API。 GWT-Ext 拥有 GNU Lesser General Public Licence (LGPL)(请参阅 参考资料),V3.0 的许可证 。因此它是一个非常灵活,允许在开源和商业中应用的控件库。 GWT 的介绍(请参阅 参考资料)已经非常丰富,我们这个系列主要关注在 GWT-Ext 的介绍和应用上。GWT-Ext 是在 GWT 和 ExtJs 的基础上对表现层的进一步封装。我们可以理解 GWT 提供了 Ajax 的一套基础框架,而 ExtJs 提供了以 JavaScript 和 CSS 样式表为基础的非常丰富的表现层接口。那么 GWT-Ext 就是在 GWT 的基础上,将 ExtJs 的 JavaScript 接口映射或者封装为 Java 的接口。在 Java 的环境下开发和调试开发表现层,可以大大提高开发的速度。 回页首 GWT-Ext 开发环境搭建 在这个系列中,将通过示例的方式,一步步地介绍如何搭建一个 GWT-Ext 的开发环境。本章将通过图解与代码结合的方式搭建演示平台框架,并且演示如何部署这个平台到 Tomcat 容器中。 下载开发环境 我们将使用 Eclipse 和插件 Cypal Studio 来搭建控件演示平台。Cypal Stutio 是一个 GWT 成熟的开发工具。它能非常方便地帮助我们搭建 GWT 的开发以及调试环境,并且能够实现编译和打包的工作,省去了开发 GWT 过程中很多繁琐的过程。 需要下载的产品和技术请参阅 参考资源 中的“获得产品和技术”部分。 安装 Cypal Studio 插件 解压 Eclipse 到本地硬盘,同时解压 cypal.studio.for.gwt-1.0.zip 到 Eclipse 的 plugins 目录下。然后解压下载的 gwt-windows-1.5.0.zip 。最后,启动 Eclipse 。 选择 Eclipse 目录 Window >Preferences > Cypal Stutio,填写 GWT 相关的信息如 图 1 所示。 GWT Home 填写 GWT 的解压根目录。如果 Java VM 的要求较高,可以从 512M 提高到 1024M 。如图 1 所示。 图 1. Cypal Studio 配置 创建第一个 GWT-Ext 工程 选择 File > New > Dynamic Web Project, 填写必要信息。Dynamic Web Module version 选择版本 2.4,因为 Cypal Studio 是在 2.4 这个版本上开发的。Configuration 选择 Cypal Studio for GWT。工程名填写 GWTEXTDemo。输入以上信息后,点击 Finish。 图 2. 创建 GWTExt 开发工程 在新创建的工程上点击右键,选择 new-> other. 在 Cypal Studio 下找到并选择 GWT Module 节点。 图 3. 创建 GWT Module 在打开的 Panle 里填写 GWT Module 的信息,主要需要填写的是 GWT Module 所在的包名以及它的类名。 GWT Module 必须实现 com.google.gwt.core .client.EntryPoint 接口。点击 Finish 完成 Module 的创建。 图 4. 填写 GWT Module 信息 新创建的 GWT 开发工程的结构如图 5 所示。 在 public 这个目录下放置图片,JavaScript 脚本,CSS 样式表和每个 GWT module 对应的 html 文件。 DemoControlPanel.gwt.xml 是一个 GWT 工程的配置文件。所有 GWT 的配置信息需要在这里声明。这些配置信息包括 Module 的信息,CSS 样式表的相对路径,我们应用到的 JavaScript 的文件相对路径,和所有异步通信中 Servlet 的映射信息。 在 GWT 的应用规范中,显示层的 Java 代码必须放在以包名 client 结尾的目录或者子目录下,并且不能依赖其它非 client 目录下的 Java 代码。 对其它的服务器端的代码,要放到以 server 结尾的包或者子包当中。 图 5. GWT 开发工程的结构图 以上,是建立 GWT 工程的详细过程,接下来,我们开始创建 ExtJs 的开发文件。 因为 GWT-Ext 依赖于 ExtJs,因此在 GWTEXTDemo 工程的 GWTEXTDemo\src\com\ibm\developworks\demo\public 目录下创建 js 目录,js 目录下创建 ext 目录。解压下载的文件 ext-2.0.2.zip,拷贝解压的文件夹 adapter,resources 和文件 ext-all.js 到工程的 public /js/ext 目录下。上述的两个文件夹和文件包括了 ExtJs 的核心 JavaScript 脚本,CSS 样式表和基本图案。如图 6 所示。 图 6. 工程所需的 ExtJs 开发文件 刷新工程,得到图 7 ExtJs 的目录结构。 图 7. 拷贝 ExtJs 后的目录结构 在 public 目录下创建 DemoControlPanel.css 空文件。解压 gwtext-2.0.5.zip,拷贝 gwtext.jar 到工作空间 WebContent/WEB-INF/lib 目录下。 Gwtext.jar 是我们本系列介绍的核心。他包括了所以 GWT 和 ExtJs 的扩展。在后面的文章中,我们要介绍的 Panel,tree 和拖拽等功能和 API 都在这个 jar 文件中能够找到。现在,我们将 gwtext.jar 放到上述目录下,eclipse 会自动将它设置在 classpath 下。 刷新 WebContent/WEB-INF/lib 目录, 可见 gwtext.jar 已被加入工作空间。 图 8. gwtext.jar 已加入工作空间 为了让 GWTEXTDemo 工程能够引用到 GWT-Ext 和 ExtJs 的开发包,并且应用本工程的 CSS 文件,打开 DemoControlPanel.gwt.xml 文件。加入如下代码。 清单 1. GWT 配置文件 4 第一个 Hello GWT-Ext 准备好以上开发环境后,下面我们开始编写我们的第一个 Hello GWT-Ext 文件。本文件实现的功能是在页面上实现一个 Panel,Panel 的 Title 是 Hello GWT-ext,内容是文字加粗的 Hello GWT-ext ! 打开 DemoControlPanel.java 文件,替换已有的 onModuleLoad() 方法。另外添加没有声明的 com.gwtext.client.widgets.Panel 类。如清单 2 所示。 清单 2. Hello GWT-ext public void onModuleLoad() { Panel panel= new Panel(); panel.setTitle("Hello GWT-ext"); panel.setHtml("Hello GWT-ext!"); new Viewport(panel); } 5 配置 GWT 运行环境 选择 Run - > Run Configurations … , 双击 GWT Hosted Mode Application 节点,此时会出现一个 New_configuration 节点用于配置 GWTEXTDemo 工程的运行。选择运行的工程 GWTEXTDemo,接着选择要运行的 Module 名称。如图 9 所示。 图 9. GWTEXTDemo 运行配置 执行这个运行配置,我们就得到了第一个 Hello GWT-Ext 的示例。如图 10 所示。 图 10. 第一个 GWT-Ext 示例 通过上述配置开发,我们已经完成了从工程的搭建,代码的编写,到页面运行展示的过程。因为 GWT 是应用 Java 的编程,因此通过断点调试程序变得异常简单。 GWT 帮助开发人员做到了 Java 到 JavaScript 的映射工作,大大方便了 JavaScript 调试过程,加快了开发的进度。 回页首 GWT-Ext 的部署 GWT 将 Java 的面向对象的编程和 JavaScript 的编程映射起来,因此要将 eclipse 工程里的示例部署到 web 容器中,首先要编译,其次要打包,最后部署与测试。使用 Cypal Studio 插件可简化编译打包过程。 1 编译 选择 Project - >Clean, 弹出 Clean 的窗口如图 11 。选择 Clean projects selected below 同时勾选 GWTEXTDemo 工程。 图 11. 编译 GWT-Ext 工程 因为 GWT 工程的编译不同于普通的 Java 程序,他包括了 Java 到 JavaScript 的转换,编译的时间会相对较长。当在 console 窗口看到“ Compilation succeeded ”表示编译成功,如图 12 所示。 图 12. 编译成功 2 打包 WAR file 如图 13 所示。填写目标存储路径,选择确定。如图 14à在 GWTEXTDemo 工程上点右键,选择 export 所示。 图 13. 打 WAR 包 图 14. 填写目标存储路径 点击 Finish 后,可得到 GWTEXTDemo.war 包 . 3 部署 将打包得到的 GWTEXTDemo.war 文件拷贝到 tomcat 的 webapps 目录下,启动 tomcat 。 4 测试 在浏览器中测试如下 URL:http://localhost:8080/GWTEXTDemo/DemoControlPanel.html 。如果显示如图 10 所示,则部署成功。 回页首 搭建 GWT-Ext 控件演示平台框架 在后面的系列中,我们将在 GWT-Ext 的控件演示平台上对 GWT-Ext 进行详细介绍。 这个 GWT-Ext 的演示平台包括了屏幕左侧的控件导航栏,通过 Accordion Layout 分类显示每章将要介绍的内容。例如在 Layout cases 的导航 tab 上,预先添加了 Accordion Layout 节点,单击这个节点,在右侧就会出现演示结果,如图 15 所示。 图 15. GWT-Ext 的控件演示平台框架 由于 GWT-Ext 控件演示平台本身也是基于 GWT-Ext 的开发,代码实现的原理和细节将在后面的章节中具体介绍。因此在本章的最后,我们把已开发好的 Java 文件拷贝到 GWTEXTDemo 工程中,为后面的章节的介绍做好准备。 读者可以拷贝下图中高亮的几个文件到对应的工程当中。 图 16. 需要 copy 的文件 体验 Panel 和 Window 本部分介绍GWT-Ext 的基本结构和功能特点,并通过代码示例来演示该技术的具体实现。将介绍非常重要的控件 Pannel,它是很多其它控件的基础容器。 Panel 是 GWT-Ext 中间非常重要的一个控件。作为很多控件的基础容器,Panel 提供许多简洁的方法,实现了诸如设置标题,设置图标,设置支持拖拽等实用功能。并且以 Panel 基础的扩展容器控件,如 Tab Panel,Portal,Window,Progress Window 等,给 GWT-Ext 的使用者提供了极大的选择余地和灵活性。下面我们将从 Panel 开始介绍。 Panel Panel 是一种容器,它提供了一组特定的功能和结构化组件,方便用户进行应用程序图形界面的组织。一个 Panel 可以包含头,尾和内容体三部分,并可在其头脚部分实现工具栏。 Panel 本身具备自动伸展的行为,并提供了一些工具按钮来帮助用户构建个性化的功能。 用户可以通过以下的构造方法来初始化一个 Panel, 并指定其标题和内容。 Panel(java.lang.String title, java.lang.String html) 或者可通个空的构造方法来构建一个 Panel 对象,然后通过 Panel 的 setTitle 和 setHtml 方法来设置 Panel 的标题和其内容, public void setTitle(java.lang.String title) public void setHtml(java.lang.String html) 从 setHtml 方法名可以看出,Panel 将自动将传入的内容作为 html 格式来解析,并进行展现。 Panel 提供了伸缩功能,该功能可将内容体进行隐藏,只将 Panel 的标题部分显示,使用户在不进行屏幕上下滑动的同时看到多个 Panel 的内容,并通过标题找到不同 Panel 里的内容。如图 1 所示。 图 1. Panel 的伸缩功能 用户可以通过 Panel 对象的 setCollapsible 方法来设置 Panel 的伸缩特性。 public void setCollapsible(boolean collapsible) throws java.lang.IllegalStateException 当设置 collapsible 参数为 true 时,对应的 Panel 对象将具有可伸缩功能,此时在 Panel 的标题栏右则将会出现一个伸缩操作按钮,用户可以通过点击该按钮来决定将当前的 Panel 内容展现或隐藏。需要注意的是,该方法只有在 Panel 还没有展现前调用,如当 Panel 展现后再调用该方法来设置伸缩性,将会得到 java.lang.IllegalStateException 异常。 在 Panel 的标题栏上除了 Panel 已有的伸缩功能按钮外,用户也可以添加一组个性化的按钮,即工具栏。如图 2 所示。 图 2. 工具栏 设置 Panel 的工具栏,我们需要用到的方法是 public void setTools(Tool[] tools) throws java.lang.IllegalStateException public void addTool(Tool tool) throws java.lang.IllegalStateException 从以上两个方法可以看出,在初始化工具栏时,我们可以将所有工具一次性放入一个工具数组对象来完成,也可通过多次的逐个加入工具对象完成。与 setCollapsible 方法一样,设置 panel 的工具栏也必需要在 panel 还没有展现前完成。 setTools 和 addTool 方法中都用到了一个新的 Tool 类 , 我们可以通过 Tool 类的构造方法来初始化其实例对象。 public Tool(Tool.ToolType type, Function handler) type 参数是 tool 在 panel 中展示的图标类型,Tool 对象中已定义 19 种图标供用户选择,如 Tool.GEAR, Tool.SEARCH 等,用户可以根据自身功能的需要选择最接近的图标。除了图标外,当用户点击图标时,tool 对象应对相应功能进行响应,因此, 需要在 Function 参数中对相应 tool 对象的响应功能进行设置。 Function 本身是一个接口,当用户点击 tool 图标时,接口中的 execute 方法将被调用,所以用户可以通过定义一个匿名内部类并实现 Function 的唯一方法 void execute() 。如清单 1 所示。 清单 1. Function 实现方法 New Function() { Public void execute() { MessageBox.alert( “ Settings ” , “ The Settings tool was clicked ” ); } } 如该匿名内部类就是实现了 Function 接口,当用户点击相应的 tool 图标时,屏幕中将出现一个消息框,如图 3 所示。 图 3. 消息框 Panel 的另外一个主要特性就是可以被设置为可拖拽的浮动框体。如图 4 所示。 图 4. Panel 的拖拽功能 实现 Panel 对象的拖拽功能可以通过 setDraggable 方法来完成 public void setDraggable(boolean draggable) throws java.lang.IllegalStateException 当 draggable 参数被设置为 true 时,该 panel 对象将具有拖拽的效果,该方法只有当 panel 被显示前调用才有效。 在下载 GWTEXTDemo-all.zip中的 CommonPanels.java 中实现了上述所有功能。 回页首 Toolbar 和 Menus 工具栏能方便用户进行相应功能操作,要得到一个 Toolbar 可以调用其构造方法 public Toolbar() 得到 Toolbar 对象后就可以在其中加入 tool 元素了,这些元素可以是工具栏按钮,工具栏菜单按钮或者工具栏分割符等。如图 5 所示。 图 5. Toolbar 对象 我们可以通过以下几个方法分别加入 Toolbar 的不同类型的元素 public void addButton(ToolbarMenuButton button) public void addButton(ToolbarButton button) public void addSeparator() public void addItem(ToolbarItem item) ToolbarMenuButton 是一种 Toolbar 的菜单按钮,点击按钮后将会弹出一个下拉菜单 ; ToolbarButton 是工具栏中的一个普通按钮。为了工具栏的美观,还可以在工具栏中加入分割符将工具栏中的各个元素进行隔离。除了按钮和分割符之外,工具栏的中还可以加入工具栏项,如 ToolbarTextItem,它可以在工具栏中显示为一个字符段。 对于 ToolbarButton 和 ToolbarTextItem 都只需要调用其对应的构造方法来生成实例 public ToolbarButton(java.lang.String text) public ToolbarTextItem(java.lang.String text) 构造方法中的参数是工具栏按钮的标题或工具栏字符域的内容。在获得实例后,通过前面介绍的 Toobar 的方法将他们加入工具栏。 在工具栏中加入菜单按钮的工作相对复杂。首先要实现一个 ToolbarMenuButton,它是按钮类 Button 的一个子类,因此它具有按钮类的所有特性。要生成一个 ToolbarMenuButton 的实例,可以通过构造方法 public ToolbarMenuButton(java.lang.String text,Menu menu) public ToolbarMenuButton(java.lang.String text) 其中构造方法中的 text 参数是设置按钮的标题,构造方法中的 menu 菜单参数用于设置用户点击该按钮后弹出的菜单。如采用第二个构造方法,则要将实例化好的菜单对象通过 setMenu 方法设置进菜单按钮。 Menu 对象即菜单,包含不同类型的项,每个项都可以包含子项。同一层次的项之间在菜单中将表现为上下关系。实现一个菜单对象可通过其构造方法 public Menu() 在获得菜单实例后就是为其加入项。菜单中的项可以分为 Item, MenuItem, Separator,Adapter 几类。其中 Item 类又可分为: CheckItem,如图 6 所示, 图 6. CheckItem 项 ColorItem,如图 7 所示 图 7. ColorItem 项 DateItem,如图 8 所示 图 8. DateItem 项 可分别调用对应类的构造方法生成对像后,调用菜单对象的 addItem 方法将子项加入。 public void addItem(BaseItem item) MenuItem 可用来创建菜单子项。首先通过 Menu 的构造方法创建子菜单对象,并通个 Menu 的 addItem 方法将各个项加入菜单子项,然后将整个子菜单对象通过 MenuItem 的构造方法中的 submenu 参数加入进父菜单中。 public MenuItem(java.lang.String text, Menu submenu) 其中 text 参数用来设置其在父菜单中的标题,submenu 参数即是已生成好的子菜单引用。 MenuItem 对象生成后,通过父菜单的 addItem 方法将其生成父菜单的项即可。 在下载 GWTEXTDemo-all.zip中的 ToolbarMenus.java 中实现了上述所有功能。 回页首 TabPanel TabPanel 本身继承自 Panel 类,因此 Panel 类的伸缩、工具栏、拖拽等特性对 TabPanel 类也适用。除此之外,TabPanel 还是一个 Tab 标签的容器。如图 9 所示。 图 9. TabPanel 在增加 tab 标签之前,我们需要一个 TabPanel 对象。以下是 TabPanel 的构造方法, public TabPanel() 通过无参构造方法生成一个 TabPanel 实例后,需要对 TabPanel 实例的一些属性进行设置,这些属性将影响存在于其中的 tab 标签的展现形式和一些行为,比如采用 setResizeTabs 设置 tab 标签的宽度等。 public void setResizeTabs(boolean resizeTabs) 当 resizeTabs 参数为 true 时,每个 tab 标签的宽度将会根据 TabPanel 容器的长度以及 tab 标签的个数自动调节,从而使得所有 tab 页正好充满整个 TabPanel 容器。此时,如果在同一标签容器里有很多 tab 标签,则每个标签能平均分到的宽度会非常小,导致标签上的标题都有可能看不到。为了避免这种情况,通常在 setResizeTabs 被设置时,通过 setMinTabWidth 方法为标签设置一个最小的宽度。 public void setMinTabWidth(int minTabWidth) minTabWidth 参数是以像素为单位的一个整数。 当设置了最小宽度后,如果所有的 tab 标签的宽度超过了容器 TabPanel 的宽度时,超出部分的 tab 标签将不能看到,此时,通过 setEnableTabScroll 方法可使得 tab 标签页进行左右滚动,这样被隐藏的 tab 标签页可以被看到 public void setEnableTabScroll(boolean enableTabScroll) throws java.lang.IllegalStateException 通过该方法来设置 tab 标签页的左右滚动性,需要在 TabPanel 对象被显示前设置,否则将导致 IllegalStateException 异常。 在 TabPanel 显示时,可以通过 setActiveTab 方法设置默认显示的 tab 标签页 public void setActiveTab(int activeTab) public void setActiveTab(java.lang.String activeTab) 方法一通过 tab 页的相对位置来确定显示的 tab 页,参数 activeTab 表示从左至右从零开始的一个序列数;方法二采用 tab 页的唯一 id 来确定显示的 tab 页 ,参数 activeTab 表示要默认显示的 tab 页的 id 。 当 TabPanel 对象的一些基本属性被设置好后,需要往其中加入 tab 标签页,每一个 TabPanel 中的 tab 标签都是一个 Panel 对象 , TabPanel 对象作为 tab 标签的容器 , 可以通过 add 方法将 panel 加入到 TabPanel 对象作为其一个 tab 标签页。 public void add(Component component) TabPanel 处于显示状态后,可对每一个 tab 标签操作所引起的事件进行监听,这可通过调用 addListener 方法实现。 public void add(Component component) TabPanel 处于显示状态后,可对每一个 tab 标签操作所引起的事件进行监听,这可通过调用 addListener 方法实现。 public void addListener(TabPanelListener listener) 将一个 TabPanelListener 的实例插入到 TabPanel 的监听队列中,TabPanelListener 是一个监听接口,它支持以下几种监听方法 boolean doBeforeTabChange(TabPanel source, Panel newPanel, Panel oldPanel) void onContextMenu(TabPanel source,Panel tab, EventObject e) void onTabChange(TabPanel source,Panel tab) 从接口方法名可以看出,TabPanelListerner 可以捕捉到的 tab 标签事件是:“标签即将切换事件”,“标签事件”和“标签切换后事件”,用户需要实现相应接口来处理的关心事件。 GWTExt 会将事件相关的对象注入到方法中,如 doBeforeTabChange 方法中的 source, newPanel 和 oldPanel 分别代表从原标签对象 oldPanel 切换至新标签对象 oldPanel 的引用,而 source 则代表标签对象所在 TabPanel 的引用。 由于 TabPanelListener 是一个继承于接口 PanelListener 的接口,直接对 TabPanelListener 进行实现将迫使用户去实现定义在 PanelListener 中的众多方法。即使可将方法体全部置空,但数以几十的数量将使对 TabPanelListener 接口的实现变得枯燥而繁琐。 GWTExt 在这个问题上充分为用户考虑,设计了一个 TabPanelListenerAdapter 类,该类是对 TabPanelListener 接口及其父接口方法的一个实现类,但所有的方法体都是空。这样用户在定义 TabPanelLister 的实现时只需对相应方法进行覆盖重写即可,不必把所有接口方法全部实现。 在下载 GWTEXTDemo-all.zip中的 TabPanelDemo.java 中实现了上述所有功能。 回页首 Portal 和 Portlet Portal 本身继承自 Panel 类,它是一个 Portlet 容器。 Portal 可以添加 PortalColumn 或者 Portlet 作为子组件。 Portlet 模拟了网页上可拖拽的 Portal 的效果。每个 Portlet 被分成数列,从上到下顺序放置,每个 Portlet 都可拖拽至任意列的任意位置,如图 10 所示。 图 10. Portal Portal,PortalColumn 和 Portlet 的关系如下 : 1. Portal 作为容器,可以包含任意个 PortalColumn 。以上图为例,Portal 包含了三列。每一列都是一个 PortalColumn 的实例。 2. 每一个 PortalColumn 都可以包含任意个 Portlet,每个 Portlet 都可以拖拽到其它的 PortalColumn 中去。 用户可以通过以下的构造方法来初始化一个 Portal 。 Portal() 我们提到 Portal 是一个容器,为了让 Portal 和他包含的控件之间上下左右出现间隔,Portal 继承了 Panel 的 setPaddings 方法。 public void setPaddings(int padding) throws IllegalStateException 参数 padding 代表了间隔的像素值。如上图所示,Portal 的蓝框在上下左右四个方向和内部的 PortalColumn 留出了 15 个像素的空白,达到了美观的效果。此外,Panel 也提供了 setPaddings 这个方法,可以单独设定某几个方向的空白。 public void setPaddings(int top, int left, int right, int bottom) throws IllegalStateException 这个方法可以更灵活的控制界面的设计。此外,PortalColumn 和 Portlet 也继承了 setPaddings 方法。 当实例化了一个 Portal 之后,我们需要给 Portal 分列。在我们的例子中,Portal 被分为了 3 列。因此我们需要创建三个 PortalColumn, PortalColumn 的构造函数如下所示。 PortalColumn() 为了指定 PortalColumn 在 Portal 容器中横行所占的比例,Portal 提供了一个 add() 的方法,如下所示。 public void add(Component component, LayoutData layoutData) 参数 component 代表了要加入 portal 的控件,layoutData 代表了该控件的布局属性。在本例中,每个 PortalColumn 都占用了 Portal 中 1/3 的空间,因此可以通过如下代码来实现。 portal.add(firstColumn, new ColumnLayoutData(.33)); 现在我们已经成功的创建了 Portal,并且将 PortalColumn 也加入到 Portal 中。接下来的工作就是创建可拖拽的 Portlet 。 Portlet 有三个构造函数。如下所示。 public Portlet() public Portlet(String title, String html) public Portlet(String title, String html, Tool[] tools) Portlet 构造函数中的参数,如 title,html 和 tools 在前文已经提到且功能相同,这里不在赘述。 Portlet 是继承了 Panel 的控件,因此它也是一个容器,它拥有容器的所有功能。比如可以设置布局管理器,可以添加其他 Panel, TreePanel 等控件。 在下载 GWTEXTDemo-all.zip中的 PortalDemo.java 中实现了上述所有功能。 回页首 Window 在 GWT-Ext 中,Window 是继承了 Panel 的控件。它是一个浮动的并且可拖拽的窗口。常用的有如下五个构造函数。如下所示 Window() Window(String title) Window(String title, int width, int height) Window(String title, boolean modal, boolean resizable) Window(String title, int width, int height, boolean modal, boolean resizable) 参数 title 表示在 Window 上显示的标题名;参数 width 表示初始化时的宽度,以像素为单位 ; 参数 height 表示初始化时的高度,以像素为单位;参数 modal 是一个布尔属性,当它为 true 时,表示在初始化的时候这个 Window 出现在最前端,并且后面的所有组件不可选。如果为 false 则没有这样的限制;参数 resizable 表示 Window 在创建之后是否能够调整大小。 为了实现 Window 一系列的属性设置,一般常用的有如下方法。 Window 通过 setDraggable 方法来设置该 Window 是否可以拖拽。 public void setDraggable(boolean draggable) throws IllegalStateException Window 通过 setMaximizable 方法来设置该 Window 是否可以最大化。 public void setMaximizable(boolean maximizable) throws IllegalStateException 当参数 maximizable 设置为 true 时,则在窗口的右上角出现可以最大化的按钮。 Window 通过 setMinimizable 方法来设置该 Window 是否可以最小化。 public void setMinimizable(boolean minimizable) throws IllegalStateException 当参数 minimizable 设置为 true 时,则在窗口的右上角出现可以最小化的按钮。 Window 通过 setResizable 方法来设置该 Window 是否可以调整大小。 public void setResizable(boolean resizable) throws IllegalStateException 当参数 resizable 设置为 true 时,则可以调整 Window 大小。 当 Window 被初始化后,默认为隐藏。只有当调用 show() 这个方法的时候,window 才会出现。如图 11 所示。 public native void show() 图 11. 初始化的 Window Window 也同样拥有 Panel 容器的特性。因此在 Window 的基础上又扩展出来了如下 6 个常用的对话框,通过实现 MessageBox 类的不同方法,或者配置 MessageBox 来实现不同的功能。 (1) Yes/No 对话框 Yes/No 对话框是提示用户是否进行接下来操作的确认框,当页面弹出是,对话框后面的内容不可选。如图 12 所示。 图 12. Yes/No 对话框 MessageBox 通过 confirm 方法实现 Yes/No 对话框。 public static native void confirm(String title, String message, ConfirmCallback cb) 参数 title 表示 MessageBox 的标题,参数 message 表示消息体。 我们可以通过匿名内部类的方式创建实例 MessageBox.ConfirmCallback,并且实现 execute 方法。如代码清单 2 所示,用户点击的 Yes 或 No 按钮的 ID 会通过 execute 方法的参数传入,方便对用户的选择做相应的操作。 清单 2. Yes/No 对话框 MessageBox.confirm("Confirm", "Are you sure you want to do that?", new MessageBox.ConfirmCallback() { public void execute(String btnID) { System.out.println("You press the button " + btnID); } }); (2)Prompt 对话框 Prompt 对话框提供了输入单行文字的方式。如图 13 所示。 图 13. Prompt 对话框 MessageBox 通过 prompt 方法实现 Prompt 对话框。 public static native void prompt(String title, String message, PromptCallback cb) 参数 title 表示标题,message 表示消息体。我们可以通过匿名内部类的方式创建实例 MessageBox. PromptCallback,并且实现 execute 方法。其中参数 btnID 是 ok 或者 Cancel 按钮的 ID,参数 text 是用户所输入的单行文本。如代码清单 3 所示。 清单 3. Prompt 对话框 MessageBox.prompt("Name", "Please enter your name:", new MessageBox.PromptCallback() { public void execute(String btnID, String text) { System.out.println("You clicked the" + btnID +" button and entered the text " + text)); } }); (3) 多行输入对话框 创建多行输入对话框的方式与 Prompt,Confirm 对话框有些不同。对话框的配置信息通过 MessageBoxConfig() 的实例来实现。如代码清单 4 所示。 清单 4. 多行输入对话框 MessageBox.show( new MessageBoxConfig() { { setTitle("Address"); setMsg("Please enter your address:"); setWidth(300); setButtons(MessageBox.OKCANCEL); setMultiline(true); setCallback(new MessageBox.PromptCallback() { public void execute(String btnID, String text) { System.out.println("You clicked the" + btnID +" button and entered the text " + text); } }); setAnimEl(button.getId()); } }); setWidth(300) 设置了对话框的宽度。 setButtons(MessageBox.OKCANCEL) 设置了显示的 OK 和 Cancel 按钮。 SetMultiline(true) 则表示了多行输入框。 setCallback(new MessageBox.PromptCallback()) 方法和 Prompt 对话框类似。不在此赘述。 显示如图 14 所示。 图 14. 多行输入对话框 (4) 分步进度条窗口 分步进度条用于显示当前过程的运行进度。在本例中,我们通过定时器每 1 秒刷新一次进度条,当十秒结束后表示过程已经完成。示例代码如代码清单 5 所示。 清单 5. 分步进度条窗口 MessageBox.show( new MessageBoxConfig() { { setTitle("Please wait..."); setMsg("Initializing..."); setWidth(240); setProgress(true); setClosable(false); setAnimEl(button.getId()); } }); //create bogus progress for(int i=1;i < 12;i++) { final int j = i; Timer timer = new Timer() { public void run() { if (j == 11) { MessageBox.hide(); } else { MessageBox.updateProgress(j * 10, "Loading item " + j + " of 10... "); } } }; timer.schedule(i * 1000); } Timer 的构造函数如下所示。 Timer() Timer 提供了一个定时控制功能,他通过 schedule 方法让任务在指定时间后开始 public void schedule(int delayMillis) 也可以通过 scheduleRepeating 方法指定任务重复运行的时间间隔 public void scheduleRepeating(int periodMillis) 通过 cancel 方法来停止 Timer 。 public void cancel() 如上例所示,Timer 控制进度条,每隔 1 秒前进 1 格。对 MessageBoxConfig 设置 setProgress(true) 方法显示进度条。同时设置 setClosable(false) 表示进度条窗口不可关闭。如图 15 所示。 图 15. 分步进度条窗口 (5) 等待进度条窗口 在不可预知运行状态的情况下,也可以通过等待进度条来实现。等待进度条并不是无限运行下去。它只是显示任务,或者进程等正在处于运行状态,提供一个友好的方式让用户等待。当任务结束的时候,进度条消失。 实现无限进度条的代码如代码清单 6 所示。 清单 6. 等待进度条窗口 MessageBox.show( new MessageBoxConfig() { { setMsg("Saving your data, please wait..."); setProgressText("Saving..."); setWidth(300); setWait(true); setWaitConfig(new WaitConfig() { { setInterval(200); } }); setAnimEl(button.getId()); } }); Timer timer = new Timer() { public void run() { MessageBox.hide(); showMessage("Done", "Your fake data was saved!"); } }; timer.schedule(8000); Timer 的使用方式已经在上例中介绍。本例中,Timer 控制进度条,当 8 秒之后,进度条通过 MessageBox.hide() 的方法被隐藏起来。 通过 setProgressText("Saving...") 方法设置进度条显示文字。 通过 setWait(true) 方法设置 MessageBox 为等待进度条。 通过 setInterval(200) 方法设置进度条更新间隔时间,在设置之前必须先创建 WaitConfig 的实例。 如图 16 所示 图 16. 等待进度条窗口 (6)Alert 对话框 MessageBox 通过 alert 方法实现 Alert 对话框。 public static void alert(String message) 参数 message 表示要显示的消息。如图 17 所示。 图 17. Alert 对话框 在 下载 GWTEXTDemo-all.zip 中的 WindowDemo.java 中实现了上述所有功能。 体验布局和树 我们将体验各种布局管理器的效果,了解我们常用的布局管理器的编程方式和一些经验总结。在对树的介绍中,我们将从树的同步和异步方式的初始化到树的一些特性进行详细介绍。 布局 Layout (1) 水平布局 水平布局使在父容器面板中的所有子对象沿同一水平线依次顺序排列。 水平布局需要实现 com.gwtext.client.widgets.layout.HorizontalLayout 类。 当设定水平布局后,Panel 中新建的子对象将按照加入父容器的次序依次从左向右显示在父面板中。以下代码清单 1 将产生如图 1 的对象布局。 清单 1. 水平布局 Panel.setLayout(new HorizontalLayout(15)); panel.add(new Panel("Item 1", 100, 150)); panel.add(new Panel("Item 2", 75, 150)); panel.add(new Panel("Item 3", 100, 150)); panel.add(new Panel("Item 4", 150, 150)); 图 1. 水平布局 (2) 垂直布局 垂直布局使在父容器面板中的所有子对象沿垂直方向依次顺序排列。 垂直布局需要实现 com.gwtext.client.widgets.layout.VerticalLayout 类。 当设定垂直布局后,所有的子对象将按照加入父容器的次序依次从上向下的显示在父面板中。下列代码清单 2 将产生如图 2 所示的垂直布局。 清单 2. 垂直布局 Panel panel = new Panel(); panel.setLayout(new VerticalLayout(15)); panel.add(new Panel("Item 1", 150, 100)); panel.add(new Panel("Item 2", 350, 75)); panel.add(new Panel("Item 3", 150, 100)); panel.add(new Panel("Item 3", 150, 150)); 图 2. 垂直布局 (3) 边框布局 边框布局是一种高级的页面布局方式,它将整个的 UI 显示空间分为上、下、左、右、和中央五个部分,各个部分可以在各自所属的布局空间中随 UI 界面大小的变化自由缩放。当 UI 空间尺寸变化时,上、下两部分可以在水平方向上自由缩放,垂直高度固定不变,左、右两部分可以在垂直方向上自由缩放,水平宽度固定不变,当上、下、左、右四个部分布局尺寸确定后,中央部分会沿水平和垂直两个方向填充所有剩余空间。以下图 3 为一个边框布局的示例。 图 3. 边框布局 垂直布局需要实现 com.gwtext.client.widgets.layout.BorderLayout 类。 使用 com.gwtext.client.widgets.layout.BorderLayoutData 来定义各个子空间的显示属性,如最大尺寸、最小尺寸、空白等属性。 BorderLayoutData 用 setMargins 来定义各个子空间在边框布局中的位置。 public void setMinSize(int minSize) public void setMaxSize(int maxSize) public void setSplit(boolean split) public void setMargins(int top,int left,int right,int bottom) 使用类 com.gwtext.client.core.RegionPosition 来定义各个边框布局子空间的位置。 RegionPosition.NORTH 表示上部子空间,RegionPosition.SOUTH 表示下部子空间,RegionPosition.WEST 表示左侧子空间,RegionPosition.EAST 表示右侧子空间,RegionPosition.CENTER 则表示中央子空间。需要说明一下的是,上下左右这些相对位置都是针对 RegionPosition.CENTER 这个中央子空间来放置的。因此 GWT-Ext 如果发现使用边框布局,但是没有指定放置在中间的控件,会初始化失败。通过使用以下构造函数可定义各个子对象所属的空间布局。 public BorderLayoutData(RegionPosition region) 代码清单 3 中 borderPanel 为具有边框布局的父面板,southPanel 为一个子对象面板。使用 add 方法将 southPanel 放在 borderPanel 面板的下部子空间中。其中参数 component 表示要添加的对象即 southPanel,layoutData 是 BorderLayoutData 类的实例对象。在这个例子里,我们使用 RegionPosition.SOUTH 构造了 southData,并设置了该空间的最大尺寸、最小尺寸、空白和位置等属性。 public void add(Component component, LayoutData layoutData) 清单 3. 边框布局 Panel borderPanel = new Panel(); borderPanel.setLayout(new BorderLayout()); Panel southPanel = new Panel(); BorderLayoutData southData = new BorderLayoutData(RegionPosition.SOUTH); southData.setMinSize(100); southData.setMaxSize(200); southData.setMargins(new Margins(0, 0, 0, 0)); southData.setSplit(true); borderPanel.add(southPanel, southData); (4) 手风琴下拉布局 手风琴下拉布局是一个垂直的布局结构,位于手风琴下拉布局中的各个子界面成员共享公共的界面显示空间,当其中的任意一个成员对象处于激活状态时,其他的各个子成员对象会被自动隐藏。 手风琴下拉布局需要实现 com.gwtext.client.widgets.layout.AccordionLayout 类。 当设定手风琴下拉布局后,所有的子对象将按照加入父容器的次序依次从上向下的显示在父面板中。代码清单 4 将产生如图 4 所示的手风琴下拉布局。 清单 4. 手风琴下拉布局 Panel accordionPanel = new Panel(); accordionPanel.setLayout(new AccordionLayout(true)); Panel panelOne = new Panel("Panel 1", " 从 XML 中我们可以看到树的根节点对应于 XML 标签 , 根节点下的中间节点对应于 XML 标签 , 叶子节点则对应了 XML 标签 。 在各个 XML 标签中有属性节点,GWT-Ext 可以取得这些节点值并且赋值于各个树中的各个节点。具体的实现介绍如下。 首先,新建一个负责异步通信的 XMLTreeLoader 的实例 loader,他的构造函数如下。 public XMLTreeLoader() 其次,XMLTreeLoader 提供了很多方法来映射 XML 的不同节点。代码清单 11 提供了具体的实例来解释。 清单 11. Tree 的异步数据加载 loader.setDataUrl("data/countries.xml"); loader.setMethod(Connection.GET); loader.setRootTag("countries"); loader.setFolderIdMapping("@id"); loader.setLeafIdMapping("@id"); loader.setFolderTitleMapping("@title"); loader.setFolderTag("team"); loader.setLeafTitleMapping("@title"); loader.setLeafTag("country"); loader.setQtipMapping("@qtip"); loader.setCheckedMapping("@checked"); loader.setIconMapping("@icon"); loader.setAttributeMappings(new String[] { "@rank" }); 方法 setDataUrl 用于设置 XML 映射文件的相对位置,参数 url 是相对于 com.ibm.developmentworks.demo.public 的路径。在本例中,我们在 public 目录下创建了新的 data 目录,同时在该目录下新建 countries.xml 文件。 public void setDataUrl(java.lang.String dataUrl) 方法 setMethod 用于设置通信方法。由于 XMLTreeLoader 是建立在 HTTP 上的异步通信,因此这里有 Connection.GET 和 Connection.POST 两种通信方法。 public void setMethod(Connection.Method method) 方法 setRootTag 用于指定 XML 文件中顶级标签的名字。在本例中,顶级标签是 countries 。对应于树的根节点。 public void setRootTag(java.lang.String rootTag) 方法 setFolderTag(String tag)用于指定 XML 文件中非顶级和最底级标签的名字。在本例中,该标签是 team 。对于树中的目录节点。目录节点可以包含非根节点外的其它任何节点。 public void setFolderTag(java.lang.String folderTag) 方法 setLeafTag 用于指定 XML 文件中最底级标签的名字。在本例中,该标签的名字是 country 。对应于树中的叶子节点。叶子节点下不能再包含其它节点。 public void setLeafTag(java.lang.String leafTag) 方法 setFolderIdMapping 用于设置节点的标识 ID 。对于树的每个节点来说都有一个唯一 ID,这个 ID 在 XML 文件中出现在标签 的属性节点。如 id="team-a",GWT-Ext 取得属性节点值的方式是在属性名前加上 @ 符号,取得属性值。如果在 xml 的标签中没有该指定属性,GWT-Ext 会生成随机唯一 ID 。 public void setFolderIdMapping(java.lang.String folderIdMapping) 方法 setLeafIdMapping(String id)用于设置叶子节点的标识 ID 。方式和 setFolderIdMapping 一样。 public void setLeafIdMapping(java.lang.String leafIdMapping) 方法 setQtipMapping(String qTip)用于设置鼠标指到对应树节点后显示的提示信息。本例中对应于各个节点的 qtip 属性。在本例中,通过 @qtip 来获得提示。 public void setQtipMapping(java.lang.String qtipMapping) 方法 setCheckedMapping 用于指定非根节点前的可选框是否被选择。他的获取值是 true 或者 false 。如果取得值是 true,则可选框被勾选,false 则没有被选择。在本例中,通过 @checked 取得 boolean 值。 public void setCheckedMapping(java.lang.String checkedMapping) 方法 setIconMapping(String icon)用于指定各个节点的图标所对应的相对路径。在本例中,各国家被分成 3 组,每一组由一种颜色的旗子代替。我们将准备好的图标文件 flag_yellow.gif,flag_blue.gif 和 flag_green.gif 拷贝到 com.ibm.developmentworks.demo.public.images 目录下。参数值 icon 是相对于 public 目录的相对路径。在本例中,通过 @icon 取得相对路径。没有该属性的节点,GWT-Ext 用默认图标显示。 public void setIconMapping(java.lang.String iconMapping) 方法 setAttributeMappings 用于存储属性对数组。对于每一个树节点,他拥有可显示的数据,也可以暂存一系列不可显示的属性。这给程序带来了极大的灵活性。在本例中,attributeMappings 数组中只有 @rank 一个属性对被存储到各个节点当中。 public void setAttributeMappings(java.lang.String[] attributeMappings) 在正确设置上述方法之后,树和 XML 的关系就建立了起来。接下来需要建立一个 AsyncTreeNode 这样一个异步根节点来实现树显示。如下所示。 AsyncTreeNode root = new AsyncTreeNode("Countries", loader); treePanel.setRootNode(root); 此外在树被建立起来后为了让树自动展开,可以调用方法如下。 treePanel.expandAll(); 这样,显示的结果如图 12 所示。 图 12. Tree 的异步数据加载 体验拖拽和通信 本文是该系列的最后一部分,将体验拖拽的效果,了解常用的两种拖拽方式和一些经验总结。在对通信的介绍中,本文还将对同步通信和异步通信进行详细介绍。 拖拽 在 GWT-EXT 中实现拖拽功能比较简单。GWT-EXT 在 com.gwtext.client.dd 包中提供了与拖拽相关的类。图 1 展示了这些类的关系。 图 1. 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 若将 draggable 这个 Panel 作为参数来构造 DDProxy 类, DD dd = new DDProxy(draggable); 则会出现这样的效果, 图 3. 边框随鼠标移动的 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 中 首先,定义 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 中,以便客户端能够找到此服务。 以上五步是在 GWT 框架下开发异步通信应用的流程。而在实际开发中,我们可以利用一些 Eclipse 的插件帮助我们快速地完成此过程的搭建。接下来,将介绍一个异步通信的例子并用 Eclipse 下的 GWT 插件 Cypal Studio 来完成创建的过程。 这是一个简单的做两位数加法的例子。用户在输入框中输入两个整型数后,不必刷新整个页面,Server 端就会返回结果。 图 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 配置选项 在 Cypal Studio 配置窗口中,可以配置 GWT 的 Home 路径以及 GWT 编译后的 OutPut 路径。 现在,我们就应用 Cypal Studio 插件来创建我们服务。首先,点击 Eclipse 的 File->New->Other …菜单,选择 GWT Remote Service,如图 7 所示。 图 7. 选择向导的类型 然后,依次指定应用程序的配置文件路径,服务的名字,此服务的链接地址(Service URL)。 图 8. 指定配置文件路径,服务的名字和链接地址 设置好之后,点击 Finish 。你会发现此插件为我们自动加入了 CalcuService.java,CalcuServiceAsync.java, CalcuServiceImpl.java, 图 9. CalcuService.java,CalcuServiceAsync.java, CalcuServiceImpl.java 已加入工作空间 以及此服务的配置信息。 图 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 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(); // 从源文件流中读入并写入到输出流中 , 具体可参见代码 … … } }
Panel1 content!
"); accordionPanel.add(panelOne); Panel panelTwo = new Panel("Panel 2", "Panel2 content!
"); accordionPanel.add(panelTwo); Panel panelThree = new Panel("Panel 3", "Panel3 content!
"); accordionPanel.add(panelThree); 图 4. 手风琴下拉式布局 (5) 锚点式布局 锚点式布局是一种可以在水平和垂直方向上按照父容器的尺寸变换的布局方式。在锚点式布局中,子对象按照百分比或绝对像素数值在水平和垂直两个方向上指定它针对父容器的相对尺寸。当父容器的尺寸发生变化后,子对象会按照已经指定的相对尺寸改变自己的大小。 锚点式布局需要实现 com.gwtext.client.widgets.layout.AnchorLayout 类。 使用 com.gwtext.client.widgets.layout.AnchorLayoutData 对象来定义锚式布局所需要的数据。 AnchorLayoutData 对象的构造函数使用字符串定义需要的布局参数。参数 anchor 包括了水平和垂直方向的百分比或者绝对像素数值,以空格间隔。比如“ 100% 20% ”表示该对象相对于父容器,水平方向是 100%,垂直方向是父容器的 20% 。而“ -100 30% ”表示该对象水平放心相对于父容器少 100 像素,而垂直方向是父容器的 30% 。 public AnchorLayoutData(String anchor) 使用 add 方法将对象放入父容器中。其中参数 component 表示要添加的对象,layoutData 是 AnchorLayoutData 类的实例对象。 public void add(Component component, LayoutData layoutData) 代码清单 5 将产生如图 5 所示的锚点式布局。 清单 5. 锚点式布局 Panel wrapperPanel = new Panel(); wrapperPanel.setLayout(new AnchorLayout()); wrapperPanel.add(new Panel( "Item 1","Anchor : '100% 20%' width is 100% of the containing element and 20% of its height"), new AnchorLayoutData("100% 20%")); wrapperPanel.add(new Panel("Item 2","Anchor : '50% 25%' width is 50% of the containing element and 25% of its height"), new AnchorLayoutData("50% 25%")); wrapperPanel.add(new Panel("Item 3","Anchor : '-100 30%' width is (100% of the containing element - 100px) and height is 30% of containing element."), new AnchorLayoutData("-100 30%")); panel.add(wrapperPanel); 图 5. 锚点式布局 (6) 列布局 在列布局中,各个子 UI 组件通过设定自己在水平方向上使用的宽度来共享父组件的宽度。 列布局需要实现 com.gwtext.client.widgets.layout. ColumnLayout 类。 使用 com.gwtext.client.widgets.layout.ColumnLayoutData 类对象来定义列布局所需要的数据. public ColumnLayoutData(double columnWidth) 使用 add 方法将对象放入父容器中。其中参数 component 表示要添加的对象,layoutData 是 ColumnLayoutData 类的实例对象。 public void add(Component component, LayoutData layoutData) 代码清单 6 将产生如图 6 所示的列布局。 清单 6. 百分比列布局 Panel wrapperPanel = new Panel(); wrapperPanel.setLayout(new ColumnLayout()); wrapperPanel.add(new Panel("Column 1", "25% width"),new ColumnLayoutData(.25)); wrapperPanel.add(new Panel("Column 2", "60% width"),new ColumnLayoutData(.6)); wrapperPanel.add(new Panel("Column 3", "15% width"),new ColumnLayoutData(.15)); 图 6. 百分比列布局 列布局管理器可以使用绝对像素数值,百分比或者两者的混合来定义宽度。 代码清单 7 将产生如图 7 所示的混合列布局。 清单 7. 混合列布局 Panel wrapperPanel = new Panel(); wrapperPanel.setLayout(new ColumnLayout()); Panel p1 = new Panel("Column 1", "120px width"); p1.setWidth(120); wrapperPanel.add(p1); wrapperPanel.add(new Panel("Column 2", "80% width"), new ColumnLayoutData(.8)); wrapperPanel.add(new Panel("Column 3", "20% width"), new ColumnLayoutData(.2)); 图 7. 混合列布局 回页首 树 Tree 树是 UI 界面编程中经常使用的一种显示结构化的,具有继承关系的数据的方法。 GWT-Ext 在 JAVA 对象 API 的层面上提供对树状 UI 控件的支持,它内建了对树形控件中节点的编辑、排序、过滤,拖拽等功能的支持,同时提供了一个完善的 UI 事件监听和处理机制。综合使用树和 GWT-EXT 中的其他组件,可以构建出功能强大的表现层 UI 组件。 GWT-EXT 中对树型 UI 控件提供支持的接口和类位于 com.gwtext.client.widgets.tree 和 com.gwtext.client.widgets.tree.event 两个包中。前者包含了构建表现层的工具类,主要用于 HTML 页面的渲染。而后者则用于提供对树型控件的事件监听和处理功能。 (1) 构建树 在 GWT-EXT 中使用 com.gwtext.client.widgets.tree.TreePanel 构建包含树的面板。使用 com.gwtext.client.widgets.tree.TreeNode 构建树的各个节点。各个节点按照继承关系添加到树中的相应位置。 TreePanel 树面板对象是所有数中节点的父 UI 组件,它将作为容器来包含树中的所有节点对象。树中的每一个节点都是一个 TreeNode 类的实例对象,使用 TreeNode 类中的方法 appendChild 可以在当前节点下添加其下的子节点。其中参数 child 是 TreeNode 的实例对象。 public native void appendChild(Node child) 构建树的步骤 a. 使用构造函数 public TreePanel() 构建 TreePanel 对象 TreePanel treePanel = new TreePanel(); b. 使用 TreeNode 的构造函数 public TreeNode() 构建一个树的根节点对象 TreeNode rootTreeNode = new TreeNode(); rootTreeNode.setText("root node"); c. 使用 TreeNode 的 public void setRootNode(Node node) 方法将这个 Node 对象作为根节点加入到 Tree 中。此时即可得到一个包含了一个根节点的树状 UI 组件。 treePanel.setRootNode(rootTreeNode); d. 为了得到更多的树节点,可以使用构造函数 public TreeNode() 生成更多的节点对象,并依次使用 TreeNode 对象的 appendChild(Node child) 方法将他们添加到根节点或其他字节点中即可。 TreeNode generation1childNodeA=new TreeNode("generation 1 child a"); rootTreeNode.appendChild(generation1childNodeA); TreeNode generation2childNodeA=new TreeNode("generation 2 child a"); generation1childNodeA.appendChild(generation2childNodeA); TreeNode generation2childNodeB=new TreeNode("generation 2 child b"); generation1childNodeA.appendChild(generation2childNodeB); TreeNode generation3childNodeA=new TreeNode("generation 3 child a"); generation2childNodeB.appendChild(generation3childNodeA); TreeNode generation1childNodeB=new TreeNode("generation 1 child b"); rootTreeNode.appendChild(generation1childNodeB); e. 调用 expand 方法展开节点。 TreeNode 对象的 expand() 方法是展开 TreeNode 的子节点。 TreePanel 的 expandAll() 方法是展开所以节点。 rootTreeNode.expand(); treePanel.expandAll(); 综合以上示例代码,将产生一棵如图 8 的树: 图 8. 构建树 (2) Tree 的事件监听与处理 GWT-EXT 中提供了一套丰富,简便且易于使用的针对树控件的事件监听与处理机制和简便的 API 。这些 API 存在于包 com.gwtext.client.widgets.tree.event 中。 GWT-EXT 还定义了数个用于事件监听的接口,并且提供了实现了这些接口的类供用户使用,以下几个是编程中会经常用到的事件监听类: com.gwtext.client.widgets.tree.event.TreePanelListenerAdapter:监听所有 TreePanel 中对象的事件。 com.gwtext.client.widgets.tree.event.TreeNodeListenerAdapter:监听树中各个节点对象的事件。 com.gwtext.client.widgets.tree.event.AsyncTreeNodeListenerAdapter:监听树异步节点对象的事件。 com.gwtext.client.widgets.tree.event.TreeLoaderListenerAdapter:监听树的数据加载事件。 在事件监听类中定义了以下各种 UI 事件监听方法,当相关联的 UI 事件发生时,监听器方法中定义的程序逻辑会自动被 GWT-EXT 系统回调执行。 当在当前 Node 上添加了新的子节点时,onAppend 方法将自动被系统调用。 public void onAppend(Tree tree, TreeNode parent, TreeNode node, int index) 当删除选定节点时 onRemove 方法自动被系统调用。 public void onRemove(Tree tree, TreeNode parent, TreeNode node): 在选定的节点上单击鼠标左键时,onClick 方法将自动被系统调用。 public void onClick(TreeNode node, EventObject e): 在选定的父节点上点击子树图标以收缩该节点的所有子节点的子树时,onCollapseNode 方法将自动被系统调用。 public void onCollapseNode(TreeNode node): 在选定的节点上单击鼠标右键时,onContextMenu 方法将自动被系统调用。 public void onContextMenu(TreeNode node, EventObject e): 在选定的节点上双击鼠标左键时,onContextMenu 方法将自动被系统调用。 public void onDblClick(TreeNode node, EventObject e): 当选定的节点状态改变时(由禁用变为可用或由可用变为禁用),onDisabledChange 方法将自动被系统调用。 public void onDisabledChange(TreeNode node, boolean disabled): 当在选定的节点上发生鼠标拖拽事件时,onDragDrop 方法将自动被系统调用。 public void onDragDrop(TreePanel treePanel, TreeNode node, DD dd): 当在选定的节点上鼠标拖拽事件结束时,onEndDrag 方法将自动被系统调用。 public void onEndDrag(TreePanel treePanel, TreeNode node): 在选定的父节点上点击子树图标以展开该节点的所有子节点的子树时,onExpandNode 方法将自动被系统调用。 public void onExpandNode(TreeNode node): 当在选定的节点上发生数据加载事件时,onLoad 方法将自动被系统调用。 public void onLoad(TreeNode node): 当选定的节点被删除时,onNodeDrop 方法将自动被系统调用。 public void onNodeDrop(TreePanel treePanel, TreeNode target, DragData dragData, String point, DragDrop source, TreeNode dropNode): 当选定的节点被移动到另一个位置时,onMoveNode 方法将自动被系统调用。 public void onMoveNode(Tree treePanel, TreeNode node, TreeNode oldParent, TreeNode newParent, int index): 当在选定的节点上开始一个鼠标拖拽事件时,onStartDrag 方法将自动被系统调用。 public void onStartDrag(TreePanel treePanel, TreeNode node): 在代码中添加监听器支持非常简单,只需实例化一个监听器类,定义需要监听的事件方法,并将该监听器类的实例添加到 TreePanel 或 TreeNode 上即可。以下代码清单 8 向一个 TreePanel 添加了一个 TreePanelListenerAdapter 监听器实例,该监听器实例将监听树控件 panel 中对象的鼠标左键单击和右键单击事件,并在事件发生时弹出 GWT-EXT 的消息对话框。 清单 8. Tree 的事件监听和处理 TreePanelListenerAdapter tpla=new TreePanelListenerAdapter () { public void onClick(TreeNode node, EventObject e) { MessageBox.alert("Bacis Tree Sample MessageBox", node.getText()+" Clicked") ; } public void onContextMenu(TreeNode node, EventObject e) { MessageBox.alert("Bacis Tree Sample MessageBox", node.getText()+" Right Clicked") ; } }; treePanel.addListener(tpla); (3) Tree 中对象的拖拽功能 GWT-EXT 中内建了对树控件的拖拽功能的支持。 下面的图 9,图 10 和图 11 是一个树控件之间的拖拽事例。左侧树中的“ Sent ”节点被拖拽到右侧树中“ ToDo ”节点上,成为“ ToDo ”节点的子节点。在 GWT-EXT 中,树中的节点可以被拖拽到任何支持拖拽功能的组件中。 图 9. Tree 中对象的拖拽功能 1 图 10. Tree 中对象的拖拽功能 2 图 11. Tree 中对象的拖拽功能 3 在 GWT-EXT 使用 TreePanel 的 setEnableDD(true) 方法启用拖拽功能支持,再使用 setDdGroup(String groupName) 方法设定对象所属的拖拽组。拖拽的目的和源必须属于同一个拖拽组。 public void setEnableDD(boolean enableDD) public void setDdGroup(java.lang.String ddGroup) 代码清单 9 生成了两个支持拖拽的树控件,并定义“ treeDD ”为它们所属的拖拽组。 清单 9. Tree 中对象的拖拽功能 TreePanel treePanel = getSampleTreePanel(); treePanel.setEnableDD(true); treePanel.setDdGroup("treeDD"); TreePanel treePanel2 = getSampleTreePanel(); treePanel2.setEnableDD(true); treePanel2.setDdGroup("treeDD"); private TreePanel getSampleTreePanel() { TreePanel treePanel = new TreePanel(); TreeNode root = new TreeNode("Mail"); root.setExpanded(true); TreeNode inbox = new TreeNode("Inbox"); root.appendChild(inbox); TreeNode drafts = new TreeNode("Drafts"); root.appendChild(drafts); ...... treePanel.setRootNode(root); return treePanel; } 方法 getSampleTreePanel() 用来生成本节示例中的可拖拽树。 (4) Tree 的异步数据加载 树控件中各个节点的数据可以通过编程的方式初始化,也可以通过 XML,Json 等方式异步加载。下面我们将通过 Tree 异步提取 XML 这个比较常用的方式来介绍 Tree 的异步数据加载功能。 在前面的例子我们看到,一棵树是由一个根节点和若干个中间节点以及叶子节点组成的。和 XML 文件比较起来,树的根节点对应于 XML 的顶级标签,树的叶子节点对应于 XML 所有底级的标签,树的中间节点对应于其它的 XML 标签。因为 XML 的结构和树的结构的对应关系,我们可以应用 GWT-Ext 提供的接口轻易的将 XML 的信息转换为一棵树,并且显示出来。 清单 10. 待转换成 Tree 的 XML 文件