第3章+Swing组件的体系结构.docx
《第3章+Swing组件的体系结构.docx》由会员分享,可在线阅读,更多相关《第3章+Swing组件的体系结构.docx(31页珍藏版)》请在冰点文库上搜索。
![第3章+Swing组件的体系结构.docx](https://file1.bingdoc.com/fileroot1/2023-5/1/0dabc61a-ebcd-4cde-9337-4cc0e27540de/0dabc61a-ebcd-4cde-9337-4cc0e27540de1.gif)
第3章+Swing组件的体系结构
第3章Swing组件的体系结构
轻量Swing组件把它们的界面样式(lookandfeel)交给一个UI代表来处理,这个UI代表负责绘制组件(即look)并处理组件的事件(即feel)。
可在构造组件之时或之后,把UI代表插入这个组件中。
“插入式界面样式”这个术语在1.4节中介绍过。
Swing的插入式界面样式由一个基于Smalltalk的“模型-视图-控制器(Model-View-Controller,MVC)”设计的组件体系结构和用于管理界面样式的下层构件组成。
前者是本章重点,首先我们给出典型的MVC的概览,然后再介绍SwingMVC的实现。
后者稍后将在第7章中介绍。
3.1典型的“模型-视图-控制器”体系结构
MVC体系结构是为那些需要为同样的数据提供多个视图的应用程序而设计的。
MVC把应用程序分为三种对象类型:
·模型:
维护数据并提供数据访问方法。
·视图:
绘制模型的部分数据或所有数据的可视图。
·控制器:
处理事件。
模型负责维护数据,例如,一个笔记本应用程序将把文档文本存储在模型中。
模型通常提供访问和修改数据的方法。
当模型变化时,这个模型还把事件发送给已登记的视图,对此,视图根据模型的变化来更新自己。
视图负责提供模型的部分数据的可视图。
例如,一个笔记本应用程序通过显示存储在模型中的部分文本或所有文本来提供当前文档的一个视图。
控制器为视图处理事件。
鼠标和动作监听器等AWT和Swing监听器都是MVC控制器。
前面提到的笔记本应用程序应该有鼠标和键盘监听器,以便适时地改变模型或视图。
MVC需要很强的设计功能。
首先,应当可以把多个视图和控制器插入到单个模型中,这是Swing插入式界面样式的基础。
其次,当模型改变时,模型的视图能够自动地得到通知;在一个视图中改变模型的属性,将导致模型其他的视图也随之更新。
最后,由于模型独立于视图,所以,不需要修改模型来适应新类型的视图或控制器。
3.1.1插入式视图和控制器
Swing(和AWT)容器把定位它们所包含的组件及确定这些组件的大小的工作委托给一个布局管理器。
布局管理器封装了布局组件的策略。
例如,FlowLayout布局管理器的策略是用组件的首选大小来安排组件的大小,并以从左到右、从上到下的顺序定位组件。
封装的策略使它们是可插入的;例如,布局管理器可以在编译时,也可以在运行时刻插入到容器中。
通过封装在视图中可视地表示数据的策略及封装控制器中处理事件的策略,MVC体系结构提供了可插入视图和控制器。
就像布局管理器可以插入到AWT和Swing组件中一样,视图和控制器也可以插入到模型中。
3.1.2视图更新
Swing(和AWT)事件由向事件源登记了的事件监听器来处理。
例如,按钮的激活事件由一个对象所处理,这个对象实现ActionListener接口,并且通过调用这个按钮的addActionListener方法向这个按钮进行了登记(注:
处理按钮动作事件的例子,请参见8.4节“JButton事件”)
事件源和监听器是Observer样式的一个例子,Observer样式允许单个对象在所观察的对象修改时通知许多观察器。
Observer样式需要在被观察对象与它的观察器之间有一个很小的接口区。
例如,上面描述的动作监听器可以是任何类型的对象,只要它实现ActionListener接口。
而且,按钮除了知道如何和何时通过观察器之外,对观察器一无所知。
当模型改变时,MVC体系结构使用Observer样式来通知视图。
模型可以有许多视图,所有的视图通常都用模型的通知来同步。
而且,任何类型的视图都可以在不使模型本身有任务变化的情况下观察一个模型。
图3-1示出了一个典型的MVC实现的信息流,并说明了在模型变化时,视图如何更新。
事件由控制器处理,控制器根据事件的类型来改变模型或一个或多个视图。
模型维护一个视图列表,这些视图为获得模型变化通知已经向模型登记过了。
当模型发生变化时,该模型通知已向此模型登记的每个视图。
视图通常从该模型中获得信息以进一步澄清这个事件,接着再更新它们自己。
Swing提示
MVC的优点
很久以来,MVC体系结构一直是建立Smalltalk应用程序的基础。
面向对象开发的最基础的方面是确认抽象并在类中封装抽象。
例如,一个工资册应用程序可能确认雇员、工资等抽象。
在类中封装抽象允许在对象间建立松散的联系,这样就减少了依赖性,增加了灵活性和再使用性。
MVC封装了三个在大多数图形应用程序都存在的通用抽象:
模型、视图和控制器。
通过封装其他体系结构的优秀特性,MVC应用程序比相应的传统应用程序更灵活和更具有使用性。
3.2SwingMVC
SwingMVC是典型MVC的专业版本,其设计目的是支持插入式界面模式而不是通用应用程序。
Swing轻量组件由下面的对象组成:
·一个维护组件的数据模型。
·UI代表,它是一个带事件处理监听器的视图。
·一个扩展JComponent的组件(注:
参见第4章“JComponent”类)
Swing模型可以直接对应典型的MVC模型;这两个模型都维护数据并提供数据访问方法,在它们发生变化时,它们都通知监听器。
Swing组件把它们的界面样式交给一个UI代表来处理。
UI代表与典型的MVC中的视图/控制器组合相对应。
从现在开始,控制器又称作监听器。
Swing监听器通常作为UI代表的内部类来实现。
例如,一个滑杆的UI代表实现一个响应模型变化的变化监听器。
这个变化监听器是作为BasicSliderUI的内部类实现的:
//Fromjavax.swing.plaf.basic.BasicSliderUI.java:
publicclassBasicSliderUIextendsSliderUI{
...
//installUIiscalledwhenaUIisbeinginstalled
//foracomponent
publicvoidinstallUI(JComponentc){
...
changeListener=createChangeListener(slider);
...
installListeners(slider);
}
...
protectedChangeListener
createChangeListener(JSliderslider){
returnnewChangeHander();
}
...
protectedvoidinstallListeners(JSliderslider){
...
slider.getModel().addChangeListener(ChangeListener);
...
}
...
publicclassChangeHandlerimplementsChangeListener{
publicovidstateChanged(ChangeEvente){
if(!
isDragging){
calculateThumbLocation();
slider.repaint();
}
}
}
...
}
BasicSliderUI创建了ChangeHandler的一个实例,该实例计算滑杆的滑块(即滑柄)的位置并重画该滑杆。
根据组件所表现的复杂程度,组件代表可以有许多处理事件的内部类监听器。
例如,BasicSliderUI类实现六个内部类监听器,如图3-2所示。
3.2.1Swing组件
组件为开发人员提供了一个API以操纵构成一个Swing组件的对象集。
组件间接地创建它们的UI代表,并在适当的时候把任务交给这些UI代表。
参见3.2.6节“组件UI的案件”,参见“UI代表绘制”中有关创建UI代表以及“安装一个UI代表”组件把绘制任务交给它们的UI代表的有关介绍。
通过提供传递方法和通过传送模型事件,组件还使它们的模型对开发人员透明。
1.模型传递方法
Swing组件为它们的模型提供传递方法,以便开发人员不需要直接访问模型来修改或查询状态。
例如,下面列出的JSlider类的方法显示了滑杆是如何传递它们模型的最小值的。
//FromJSlider.java,pass-throughmodelmethods;
publicintgetMinimum(){
returngetModel().getMinimum();
}
publicvoidsetMinimum(intminimum){
intoldMin=getModel().getMinimum();
getModel().setMinimum(minimum);
firePropertyChange("minimum",
newIntger(oldMin),newInteger(minimum));
}
JSlider.setMinimum()在设置最小值后激发一个属性变化事件。
组件模型的所有属性(一个滑杆的最小值和最大值)都应该激发属性变化事件。
2.传送模型事件
Swing组件还把模型事件传送给一个已向组件登记过的监听器。
例如,一个滑杆作为一个变化监听器向其模型登记。
当这个滑杆的模型激发了一个变化事件时,这个滑杆接着把一个变化事件发送给自己的变化监听器。
JSlider类实现一个变化监听器,它只把一个状态变化事件发送给滑杆的变化监听器。
与组件UI一样,JSlider等组件类常常在内部类中封装事件处理。
下面列出了大大简化了的JSlider类进行监听的代码,其中说明了滑杆把状态变化发送给它们的监听器以响应模型状态的变化的方法(与其他Swing组件类的处理方法类似)。
//FromJSlider.java:
publicclassJSliderextendsJComponent
implementsSwingConstants,Accessible{
...
protectedChangeListenerchangeListener=
createChangeListener();
...
publicJSlider(intorientation,intmin,
intmax,intvalue){
...
sliderModel.addChangeListener(changeListener);
...
}
publicvoidaddChangeListener(ChangeListenerl){
listenerList.add(ChangeListener,class,l);
}
publicvoidremoveChangeListener(changeListenerl){
listenerList.remove(ChangeListener.class,l);
}
...
protectedChangeListenercreateChangeListener(){
retrunnewModelListener();
}
...
privateclassModelListener
implementsChangeLister,Serializable{
publicvoidstateChaged(ChangeEvente){
//fireeventtochangelistenerregistered
//withaddChageListener()listedabove
fireStateChanged();
}
}
}
JSlider构造方法把一个JSlider.ModelListener实例添加到滑杆的模型中。
JSlider.ModelListener通过调用JSlider.fireStateChanged()方法来对模型变化做出反应。
JSlider.fireStateChaged()方法把一个变化事件发送给滑杆的监听器。
3.2.2静态认识
轻量Swing组件的实现方式尽量与组成它们的MVC结构的对象的实现方式相类似。
例如:
Swing按钮由JButton类、ButtonUI类及其他对象组成。
其他轻量Swing组件与此相同,以相似的类名实现相似的功能,如:
JLabel与LabelUI、JCheckBox与CheckBoxUI、JTree与TreeUI等等。
图3-2示出了组成Swing滑杆的类的类图。
由于轻量Swing组件的MVC实现的一致性,所以图3-2对总体了解SwingMVC的根本思想是有所帮助的。
与大多数轻量Swing组件一样,JSlider维护对其模型的一个引用。
Swing模型由接口定义滑杆的模型实现BoundedRangeModel接口。
有边界范围的模型跟踪最小值、最大值和当前值(注:
有关滑杆和滑杆模型的更多信息,请参见11.2节“JSlider”)
Swing提供缺省的模型实现,在组件模型没有被显式地指定时,则使用这个缺省模型。
例如,如果一个滑杆的模型没有被显式地指定(通常都是这种情况),则用DefaultBoundeRangeModel的实例来实现滑杆。
所有的Swing轻量组件扩展JComponent类,该类维护一个对组件UI的引用。
ComponentUI类是javax.swing.plaf包中的一个抽象类,javax.swing.plaf包定义UI代表的基本功能。
BasicSliderUI类在javax.swing.plaf.basic包中并且封装了基本的按钮功能。
标准Swing界面样式的滑杆UI类扩展BasicSliderUI类并定制了缺省功能。
BasicSliderUI类实现六个内部类监听器,其中的五个监听JSlider组件;这个滑杆模型中还包含BasicSliderUI.ChangeHandler。
3.2.3动态认识
上一节提供了组成轻量Swing组件的对象之间关系的静态视图。
本节提供一个组件的组成部分之间的相互关系的动态视图。
图3-3示出了一个图表,与图3-1的目的相似,它说明了Swing实现的MVC的信息3-3中示的组件代表一个轻量Swing组件类,如JButton、JLabel、JSlider等等。
因为一个UI代表的监听器几乎总是作为内部类来实现的,所以,图3-3中的监听器包含在UI代表中。
图3-3中的模型代表组件的模型。
例如,一个按钮的模型是ButtioModel接口的一个实现。
“SwingMVC”中曾作过介绍,组件为其模型提供传递方法,不用直接访问一个组件的模型就能操纵模型值。
因此,图3-3示出了组件变化它们的模型。
我们还知道,JButton、JLabel和JSlider等组件监听它们的模型,以便把模型事件传送给组件自己的监听器。
因此,图3-3描述这了种模型,所以,图3-3示了被组件模型更新的监听器。
通常,监听器通过有选择地从模型获得信息并变化组件或UI代表来响应事件。
案例
图3-3图解说明了组成轻量Swing组件的对象相互通信的一般情况。
本节介绍两个具体的例子以进一步阐明轻量Swing的组件通信。
图3-4示出了一个滑杆属性被程序修改时所发生的事件序列。
JSlider的paintTicks属性是滑杆的UI代表的一个属性,不是一个模型属性。
Swing滑杆维护一个boolean属性,该属性决定滑杆是否绘制勾号(tick)标记。
图3-4示出了JSlider.setPaintTicks()被调用时发生的事件序列。
在设置了paintTicks属性后,JSlider.setPaintTicks()调用firePropertyChange(),firePropertyChange()则向滑杆的属性变化监听器报告属性变化事件。
滑杆属性变化监听器之一是BasicSliderUI.PropertyChangeHandler的一个实例,它对这个事件的反映是强迫滑杆的UI代表更新滑杆。
在把变化情况通知给监听器之后,JSlider.setPaintTicks()使滑杆重新生效并重画滑杆。
除了图3-5是为图3-4所示的事件序列定制的以外,图3-5与图3-3类似。
当JSlider.setPaintTicks()被激活后,这个滑杆更新UI代表的一个监听器。
这个监听器便修改这个UI代表,这个UI代表获得这个组件本身的信息。
图3-6示出在滑杆的轨道上按下鼠标后发生的事件序列。
鼠标按下事件派发给滑杆,滑杆响应该事件,把一个事件发送给它的监听器。
它的监听器之一是BasicSliderUI的TrackListener的一个实例。
这个监听器通过调用BasicSliderUI.scrollDueToClickInTrack()来操纵滑杆的UI代表。
这个UI代表然后调用JSlider.setValue()来设置滑杆的值。
JSlider.setValue()方法是DefaultBounderRangeModel.setValue()的参数传递方法。
参见3.2.1节“Swing组件”中对模型属性的组件传递方法的讨论。
当这个模型值设置后,模型激发一个状态已变化事件,该事件由一个BasicSliderUI.ChangeHandler实例处理。
变化监听器通过算滑杆的滑块位置,然后重画滑杆来处理这个事件。
图3-7示出了图3-6中说明的事件序列的组件通信。
滑杆激发一个由轨道监听器处理的事件。
该监听器修改UI代表(它更新滑杆),然后滑杆又更新模型。
模型激发一个状态变化事件,这个事件由一个变化监听器通过重画组件来处理。
3.2.4模型
大多数轻量Swing组件都有这样一个模型,这个模型维护状态信息,并在信息变化时激发事件(有些组件(例如,JSeparater)没有模型),一个按钮的模型跟踪按钮的助记键及按钮是否待按下、按下或选取。
按钮模型在它们的模型改变时将激发变化事件,当模型的选取状态变化时将激发项事件。
Swing提示
谁在监听?
Swing组件由许多对象组成,例如,一个滑杆至少由九个对象组成,参见图3-2“滑杆组件类”。
通过记住谁在监听谁有助于跟踪对象在做什么和了解对象之间是怎么交互的。
组件监听其模型,其主要目的是把事件传送给已向组件登记过的监听器。
组件还为模型属性提供传递方法。
传送模型事件和提供模型属性的传递方法减少了直接访问组件模型的需要。
UI代表监听器主要监听组件,有时也直接监听组件的模型。
UI代表对组件和模型变化的响应通常是更新它们的外观,这通常要访问组件或模型,以便获得有关变化的更多信息。
模型是当作JavaBeans的关联属性来实现的。
如果修改一个属性导致激发一个属性变化事件,则这个属性就是关联的,并且这个属性的访问方法遵循如下名字的约定:
publicvoidsetModel(model)
publicgetModel()
表示定义模型类型的接口名。
1.模型事件
模型具有激发大量事件的潜能。
例如,当一个滑杆的滑块拖动时,该滑杆的模型激发一个连续不断的事件流,指示这个滑杆的值正在改变。
因此,从性能方面来看,模型为每个激发的事件创建一个事件不总是实现的。
为了大大减少由一个模型创建的事件对象的数量,模型激发一个由javax.swing.event.ChangeEvent类定义的特殊事件类型。
变化事件与大多数其他事件不同,因为它们仅包含事件源这一种信息。
这样,每个模型可以对所有的变化通知重复使用一个变化事件。
激发变化事件称为轻量通知,因为很少的信息与事件有关。
激发其他的事件(例如,由按钮激发的动作事件)称为状态通知,因为该事件除包含事件之外还包含许多状态信息。
轻量通知用于要经常修改的模型属性。
监听轻量通知的监听器(指实现ChangeListener接口的监听器)询问从变化事件获得的事件源,以了解与变化有关的更多信息。
对很少变化的模型属性则使用状态通知。
例如,从一个列表模型中删除一个元素产生一个状态通知,该通知包括删除行的索引值。
2.Swing模型
表3-1列出了Swing模型接口以及与模型有关的组件。
该表还指出了模型是否提供了轻量或状态通知以及Swing是否提供了模型接口的一个抽象实现。
表3-1Swing模型
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
模型接口 使用者 通知② 抽象类
─────────────────────────────────
BoundedRangeModel JProgressBar、JSlider LW
ButtonModel JButton、JCheckBox、 LW/ST
JCheckBoxMenuItem、
JMenu、JMenuItem、
JRadioButton、
JRadioButtonMenuItem
JToggleButton
ComboBoxModel JComboBox ST
Document1① JEditorPane、JPasswordField、
JTextArea ST
JTextField、JTextPane
ListModel JList ST
ListSelectionModel JList、JTable ST
SingleSelectionModelJMenuBar、JPopupMenu、
JTabbedPane LW
TableModel JTable ST
TableColumnModel JTable ST
TreeModel JTree ST
TreeSelectionModel JTree ST
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
①Document接口在javax.swing.text包中
②LW=轻量通知 ST=状态通知
所有的Swing模型