面向对象的编程-Application 28

Previously on OOP:

In the latest article, a Thread has been created, started, and finally interrupted. The argument of Thread constructor is an object of Runnable Interface, which can be implemented using Lambda expression.

本课程的最后一章是GUI,Graphic User Interface。本黄鸭会在这篇文章中举一个这一章的例子。但是各位宝宝们千万不要以为距离“结课大吉”不远了,因为在下一轮的学习中,我们将要结合好几章的内容,举一些综合性的例子。

GUI有三个组成部分:

(1)Model:界面背后的逻辑,其他所有章节的代码都视为这一部分。

(2)View: interface display

(3)Controller: user interaction; events。如果在本课程的考试中遇到了GUI章节的题目,一般是要求写一个简单的Controller,5到10行左右。

这三个部分之间是有关联的,创建它们的实例的代码如下所示:

第一个被创建的是Model;第二个是View,因为它的constructor需要的参数是Model;第三个是Controller,因为它的constructor需要的参数是Model和View。

可以用代码“v.setVisible(true);”使得JFrame变得可以被用户看见。此时,看见的是一个空的窗口。而且,在关闭这个空的窗口之后程序的运行不会终止,因为关闭窗口这个event在Controller中没有handler。

现在,我们的Model是一个Counter类的实例。

Counter类里面只有一个attribute,名称是“value”,功能是计数器。

另外,里面有两个methods,分别是increment()和decrement(),也就是增加计数器的值和降低计数器的值。在decrement()函数中,如果当前数为,再要求降低计数器的值的话,会扔出一个Exception。这个Exception在Counter类中没有handler,所以应该查看哪里调用了decrement()函数,顺着hierarchy of invocation向上找。

下面是View的代码:

View类import进来的库类一般都以“java.awt”或者“javax.swing”开头,说明这些packages都是和用户界面显示有关的。

此外,View类必须是JFrame的子类,因为JFrame是一个空的窗口,只有有了窗口,才能在里面画各种控件。

在本例中,main函数位于CounterApp类。但是想要用Model, View, and Controller这三位大侠就搞定的话,不想新增加其他类的话,可以考虑把main函数放在View类里面。

因为View类的constructor的参数是Model类的实例,所以在View类中,至少要有一个负责存放Model类的实例的attribute。

本例的控件一共是三个。第一个按钮上面写的字是“+ Duck”,名称是“increment”。所谓“按钮上面的字”,就是在用户界面中用户能看到的字。第二个按钮的名字是“decrement”。最后一个控件是一个Label,即一个字符串,内容是“Number of Duck?”,并且居中放置。

现在点击这些控件是不会有任何剧情被触发的,因为Controller还没有编写,而且View和Model没有连起来。

Constructor的最重要的任务在于给attributes赋初始值,所以View类的constructor的第一步是把传递进来的Model类的实例存为自己的attributes。

然而,View类的constructor比起普通constructor有更加艰巨的几项其他任务。本段代码中是第一项:设定窗口的大小;第二项:设定窗口的标题。

第三项艰巨的任务是设定窗口内的布局。现在选用的是BorderLayout,把窗口分成五个部分,分别是NORTH, SOUTH, EAST, WEST, CENTER。其中NORTH和SOUTH的长度默认为刚才设定的窗口的长度。如果用户拉伸或者压缩窗口,这两块的长度是不会随之变化的。

再选定布局之后,我们可以调用add()函数,把控件加入到布局中。add()函数前面可以多一些dotted notation的调用(如红色的字所示),表明是加入控件到当前的pane中。在本段代码中,NORTH是increment按钮,SOUTH是decrement按钮,CENTER是valuelabel。

这里有两点需要强调:

(1)凡是存在的控件,都必须分配一块地方来放。

(2)如果两个或者多个控件被加入到同一块区域中,那么并不是后面的覆盖前面的,从而显示后面加入的控件,而是会显示“?”。

另外,除了BorderLayout之外,有以下布局可以选用:

第四项艰巨的任务是设置默认的关闭窗口操作。setDefaultCloseOperation()方法的参数有以下两种比较常用:

(1)EXIT_ON_CLOSE:当用户点击“X”关闭窗口时,调用System.exit()退出窗口,并且终止程序的执行,但是不会关闭任何文件。

(2)DISPOSE_ON_CLOSE:一直关,直到所有与该用户界面相关的线程全部被关闭了。

第五项艰巨的任务是让窗口能够被用户看到,只要调用setVisible()方法就可以了。

最后一项艰巨的任务是把View类和Model类连接在一起。update()函数的内容如下所示:

“value”是JLabel类型控件的名称。在update()函数中,我们重新设定了这个控件里面的字符串的内容,把Model类里面的值也写入字符串中。这样,在用户界面上,就能看到Model类中的计数器的值。

下面是Controller的代码:

Controller类的attributes负责存放Model和View类的实例,它的constructor除了给这两个attributes赋值之外,还有一项重要的任务,就是给View类中所有可以按的控件增加监听器;在本例中是increment和decrement这两个按钮,还有一个JLabel相当于字符串,不能按。

To register a listener, use the methodaddActionListener(). Its required argument is a Lambda expression.表示在监听到event “e”的时候,调用pressIncrement()函数。pressIncrement()和pressDecrement()函数的代码如下所示:

当按了increment按钮时,model调用increment()方法,view调用update()方法,使得计数器和用户界面都显示为增加后的数据。

在按decrement按钮的时候,做法非常相似,只是Model类中可能会产生Exception,如果当前计数器的数据是的话。又因为在Model类中没有Exception handler,那么只能沿着hierarchy of invocation传递到pressDecrement()中,被catch block处理掉。

Exception的处理的方式是先打印一条error message,在commend interface中,也就是说,用户是看不到这一条的。再在用户界面中弹出一个message dialog,显示error message。

如果不想用addActionListener()给控件增加监听器,那么使用Observer and Observableclasses也是可以的。在综合性的例子中,本黄鸭会详细说明这两个classes的用法。

欢迎使用本黄鸭编写的小程序~

微信公众号二维码:

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181102G1EXVE00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券