第1部分:从一个Tweets应用开始
简介
本教程将通过一系列比“Hello World”更深入的教程,从多个的实用角度来展示桌面qooxdoo应用的开发过程。你可以在qooxdoo下载包的framework中的DEMO目录中找到更多的示例程序,比如Feedreader等。
从教程的标题来看,我们创建一个简单的Tweets应用程序(Tweets是Identi.ca网站用来是阅读和发布的象twitter服务一样的公共短信息的应用程序)。
下面的图片展示了应用程序完成后的样子。
你会看到这个应用程序有一个窗口,包含一个工具栏,一个列表,一个文本区域和一个发布消息按钮。这是一个非常典型的qooxdoo应用程序界面。
在教程的第一部分,我们将学习如何创建一个新的应用程序,以及如何建立这个程序界面。
开始
第一步是设置一个qooxdoo应用程序开发环境来开始我们的开发工作。首先你要下载一个ActivePython脚本语言程序交并安装,我们将使用它来建立应用程序的初始框架以及后期开发过程中的程序编译发布等工作。下面我们需要下载一份qooxdoo SDK(假设我们的WEB服务器的文档根目录为D:\WebRoot目录)。并将其解压到D:\WebRoot\qooxdoo目录下。使用D:\WebRoot\qooxdoo目录下的create-application.py脚本命令创建一个qooxdoo应用程序开发框架。使用cmd窗口输入命令如下:
create-application.py -n tweets -t desktop -o d:\webroot\ -s tweets
参数说明:-n 应用名称 -t 应用类型 -o 程序输出目录 -s 应用的命名空间名称
命令执行后应该会自动在d:\webroot下生成一个tweets目录。将当前目录更改到d:\webroot\tweets目录并运行 generate.py source命令来解决应用系统的类的依赖关系。现在你可以打开一个浏览器,输入地址:http://127.0.0.1/tweets/source查看一下初始框架的效果。
用你喜欢的编辑器打开位于source/class/tweets/目录下Application.js文件,这个文件中的main函数就是整个应用系统的入口函数。我们会在这个文件中看到创建了一个按钮的代码,以及当这个按钮按下时触发一个显示hello world的事件,我们不需要这个,所以删除这些代码及所有关于它的监听代码。
接下来,我们首先创建一个窗口。这个窗口包含所有的UI控件,我们将从qooxdoo Window继承一个新的类并在这个类的内部添加控制命令。建立一个新的类就是建立一个新文件。在Application.js文件同目录下建立一个新文件,并命名 MainWindow.js,添加一些代码到新文件中。使用qooxdoo中的qx.Class.define函数创建一个类。将下面的代码添加到新创建的文件中。
qx.Class.define("tweets.MainWindow",
{
extend : qx.ui.window.Window,
construct : function()
{
this.base(arguments, "tweets");
}
});
这样通过对qooxdoo Window的继承我们就建立了一个我们自己的类。在这个类的构造函数中,我们通过qooxdoo Window构造函数的第一个参数设置窗口的标题,通过this.base(arguments)调用覆盖基类的方法,在这里它是在构造函数里调用,所以他所调用的就是基类的构造函数。为了测试这个窗口,我们需要在主应用程序创建它的一个实例。在主应用程序 Application.js文件中添加如下两行代码来创建并打开窗口。请确保将代码添加到application class中的main函数中。
Var main = new tweets.MainWindow();
main.open();
下面所有的测试都可以在浏览器中完成。但在测试之前我们还需要做一件事情,就是运行generate.py生成器命令。因为我们在系统中增加了窗口类。它需要一个新的依赖关系。所以运行需要运行generate.py source命令,然后在浏览器中打开页面。这时您应该看到一个窗口,并且在它的左上角有“tweets”应用名称。
窗口运行配置
教程的最后一个任务就是配置窗口。在打开窗口时窗口的显示位置在浏览器的左上角,很不好看,为了将窗口的显示位置离开浏览器的边缘。需要在您的应用程序添加以下代码:
main.moveTo(50,30);
//main.center(); //窗口居中显示
另外我们应该配置窗口的控制按钮。让用户不能关闭、最小化和最大化窗口。在窗口的构造函数中添加以下代码:
this.setShowClose(false);
this.setShowMaximize(false);
this.setShowMinimize(false);
最后一件事就是改变启动时窗口的大小。当然,运行时用户也可以改变窗口的大小,但是为了启动应用程序就有一个好看的窗口,应该在程序启动时设置窗口的初始大小。改变窗口大小和隐藏控制按钮一样容易,只是在窗口的构造函数加入如下代码:
this.setWidth(250);
this.setHeight(300);
这时运行程序,您的应用程序应该像下图这样。
由于以上代码没有增加新的类的调用,所以应用不需要调用generate.py命令。
我们需要做的第下一件事情就是为我们的窗口设置一个组件布局。 在上面的界面中你可以看到文本区域和按钮并排在一行而其它所有元素垂直排列。但是,所有的元素都可以看做在网格中对齐排列的,因此我们选择为当前窗口选择一个网格布局。在我们的窗口类中添加网格布局,就是在MainWindow.js文件中添加下面这些代码:
var layout = new qx.ui.layout.Grid(0,0);
this.setLayout(layout);
没有任何内容的布局是没有意义的,所以下面给布局添加一些内容,看看它是否正常运行。让我们添加系统的前两个元素Toolbar和List View到窗口布局中。
Layout和Toolbar
我们需要在添加工具栏前创建它。创建工具栏并将其直接添加到布局中。
var toolbar = new qx.ui.toolbar.ToolBar();
this.add(toolbar, {row: 0, column: 0});
上面的代码就将工具栏添加到主窗口的网格布局中。 你唯一需要注意的事情就是add()函数的第二个参数。它包含了布局属性的位置信息。你可以在Layout API中查找在这种网格布局情况下可用的布局属性,在这里,我们只使用row和column两个属性来告诉布局toolbar位于网格布局的第一行、第一列(row和column以0开始)。
Layout和List
添加List到布局的代码看起来很熟悉。
// list
var list = new qx.ui.form.List();
this.add(list, {row: 1, column: 0});
现在在浏览器中看看我们的工作效果。同样的,在代码中我们增加了新的类,它需要新的依赖关系,因此我们需要调用generate.py生成器来重新生成代码。之后,我们就可以在浏览器中看到结果了。但这我们看到的结果却不是我们想要的样子,看不到Toolbar,List与窗口边框有太多的填充而且也没有充满整个窗口。这些事项都我们需要考虑的。
首先,我们不需要List与窗口边框之间的填充。解决这个问题我们中需要修改window对象的一个默认的内容填充属性即可。
this.setContentPadding(0);
将这一行代码加入到主窗口的构造函数中,填充就去除了。
接下来,我们需要考虑List的大小。Layout并不知道哪一列(几列)或哪一行(几行)应该扩展。为此我们需要告诉Layout:
layout.setRowFlex(1, 1);
layout.setColumnFlex(0, 1);
代码第一行告诉Layout第二行(也就是List所在行)大小需要扩展。第二行代码对Layout的第一列执行相同的设置。
我们需要做的最后一件事就是解决Toolbar不可见的问题。如果你知道Toolbar不可见的原因,你肯定也知道怎么解决。原因就是Toolbar没有包含任何一个组件所以它看不到,下面我们给它加入一个组件。在我们教程的这个例子中,我们需要添加一个刷新按钮。我们已经知道如何创建和添加组件,所以只需要在文件中添加以下代码。
var reloadButton = new qx.ui.toolbar.Button("Reload");
toolbar.add(reloadButton);
现在看看所有的修改就否都完成了,记住一定要在重新加载浏览器页面之前在运行代码生成器,因为我们以增加了一个新的类(button)。我们看到的结果正是我们想要的样子。
TextArea 和Button
在以上面容成功后,我们可以继续进行下一个任务了,添加Text Area和“Post”按钮,象前在的场景一样直接添加它们。
// textarea
var textarea = new qx.ui.form.TextArea();
this.add(textarea, {row: 2, column: 0});
// post button
var postButton = new qx.ui.form.Button("Post");
this.add(postButton, {row: 2, column: 1});
这一次,我们在布局的第二列添加一个POST的按钮,并使其与TextArea水平对齐。再次生成并重新加载进行测试。
像上次一样,显示结果并不是我们希望的样子。Toolbar和List没有填满整个窗口。不要仅,这是一个我们自己的问题,因为我们添加按钮的动作将Layout扩大到了两列。 List和Toolbar需要跨越两列才能得到我们想要的结果。这很容易,在向布局中添加List和Toolbar控件时使用一个合并单元格的布局属性colSpan:2。代码应该是下面这样:
this.add(toolbar, {row: 0, column: 0, colSpan: 2});
this.add(list, {row: 1, column: 0, colSpan: 2});
为UI添加动作
现在的用户界面看起来就是我们想要的。 但如何将UI与应用程序的后台逻辑代码进行沟通呢? 使用事件通知的方式将UI与程序逻辑处理分离就是一个好方法。如果你仔细看就会发现我们的应用只需要UI向后台逻辑发送两个事件通知:重载Tweets和发一个Tweet。
我们添加这两个事件到窗口中。 添加事件分为两步。 首先,我们需要声明想要触发什么样的事件。 因此,我们窗口类定义的构造函数中加事件定义部分:
events :
{
"reload" : "qx.event.type.Event",
"post" : "qx.event.type.Data"
},
正如你看到的代码,它逗号结束。是否需要以逗号结尾取决于你将代码复制在什么位置,你只需确定这个类的定义是一个有效的JavaScript对象就可以了。现在再回到事件中,reload事件是一个普通的事件,只是通知接收器重新载入。post事件需要将包含的数据发布到identica中,是一个数据事件,这就是为什么需要使用两种不同类型的事件。
声明该事件是使用事件的第一步骤。 第二个步骤就是触发事件!让我们来看看在重载事件,在载入按钮被触发(qooxdoo的说法是执行)时执行这个重载事件。按钮本身有触发执行事件,所以我们可以使用按钮本身的触发执行事件来触发我们自定义的重载事件。
reloadButton.addListener("execute", function() {
this.fireEvent("reload");
}, this);
这里,我们做了两件事情:第一添加一个事件监听器,第二触发一个事件就象调用一个方法一样简单。 fireEvent()唯一的参数是我们在类定义中声明的事件的名称。另一个有趣的事情是addListener函数的第三个参数this。它设置在我们的窗口实例中回调函数要使用的上下文对象,因此this.fireEvent()中的this才能被正确解析。
接下来的代码虽有一些不同,但也很容易。
postButton.addListener("execute", function() {
this.fireDataEvent("post", textarea.getValue());
}, this);
这一次,我们使用fireDataEvent方法对数据事件进行触发。这个方法的第二个参数就是这个数据事件中需要嵌入的数据。在这里我们只是简单地使用文字区域的值。为了测试这两个事件,我们需为每个事件在我们的应用程序代码中添加一个调试监听器,在application.js的main()方法加入下面代码:
main.addListener("reload", function() {
this.debug("reload");
}, this);
main.addListener("post", function(e) {
this.debug("post: " + e.getData());
}, this);
在这两个事件中我们使用了qooxdoo调试功能中的debug函数。现在我们可以对整个用户界面进行测试了。使用一个你喜欢的浏览器打开索引文件,就会看到UI的效果。如果想看到的调试信息,你必须打开你所选择浏览器中的调试工具或者使用qooxdoo的调试控制台。 按F7可以使qooxdoo控制台可见。
结束工作
在这个任务的最后,我们可以对用户界面进行一些完善。如果文本区域有一些文本告诉你应该在这里输入你的信息,工具栏显示一些提示信息告诉用户一些更详细信息?这样用户界面就显的更加友好了。达到上面的目的非常容易!
reloadButton.setToolTipText("Reload the tweets.");
textarea.setPlaceholder("Enter your message here...");
postButton.setToolTipText("Post this message on identi.ca");
另外一个很好的设置就是在窗口标题栏中加入一个Identica的Logo。只要从identica网上下载一个Logo的PNG图标,将其保存在的应用程序的source/resource/tweets目录中。为窗口添加图标是容易的因为窗口有一个添加图标的属性,你可以在窗口的构造函数中设置这个属性。
this.base(arguments, "tweets", "tweets/logo.png");
这一次,我们以增加了一个新的图像。图像类的也需要依赖关系,因此我们需要再次运行代码生成器。然后,就可以在windows的标题栏中看到图像了。
下面还有两个小的改动需要来完成。 首先,按钮看起来不怎么好。因此我们应该给它一个固定的宽度,并让它的高度自适应。
postButton.setWidth(60);
最后一个任务相对与前面的调整有些复杂。你可能知道,identica信息最大只能有140个字符。所有输入的信息超过140个字符时候应该禁用POST按钮,这样可以帮助后台通信层更好的运行。 完全没有文字的identica消息也是不可用的,那么在在这种情况下也应该禁用POST按钮。为了得到我们想要的效果,当文本区域中的文本被改变的时候应该通知我们进行一些设置,幸运的是,文本区域有一个文字变化的数据事件,下面我们来监听这个事件:
textarea.addListener("input", function(e) {
var value = e.getData();
postButton.setEnabled(value.length < 140 && value.length > 0);
}, this);
这个事件处理程序只有两行,首先取得文本区域中更改的文本内容。第二行根据文本内容长度是否超过140或长度为0来设置POST按钮的enabled属性。有些人可能感觉这些代码不好,因为用户每添加一个字符监听器就会被调用一次, 但这不是一个问题,因为qooxdoo属性系统已经考虑到了,如果传入的setter的值与现有的值一样,它会被忽略也不会触发事件。
我们考试的最后一件事是应用程序启动时。文本区是空的,但POST按钮中有效的。因此我们要禁止按钮。
postButton.setEnabled(false);
现在回到浏览器中测试一下我们的新的调整,构建用户界面工作就全部完成了。