——关于Swing 布局、事件和模型。
既然您已经知道了大多数(肯定不是全部)可以用来制作 UI 的组件,那么就必须实际用它们做些什么。您不能只是随意地把它们放在屏幕上,然后就指望它们立即就能工作。您必须把它们放在特定的点上,对它们的交互作出反应,然后根据交互更新它们,用数据填充它们。要填满 UI 知识的这片空白,还需要更多地学习 UI 的其他重要部分。
所以,让我们来研究以下内容:
- 布局:Swing 包括许多布局,布局也是类,负责处理组件在应用程序中的摆放位置,以及在应用程序改变尺寸或者删除、添加组件时对组件进行相应处理。
- 事件:您需要对按下按钮、单击鼠标和用户在 UI 上能做的每件事进行响应。想像一下,如果不能响应会发生什么 —— 用户单击之后,什么变化也没有。
- 模型: 对于更高级的组件(列表、表格和树),以及一些像 JComboBox 这样的更容易的组件来说,模型是处理数据最有效的途径。它们把大部分处理数据的工作从实际的组件本身撤出来(请回想一下前面讨论的 MVC),并提供了一个公共数据对象类(例如
Vector
和ArrayList
)的包装器。
简单布局
就像在前面提到过的,布局替您处理组件在应用程序中的摆放。您的第一个问题可能是“为什么不能用像素告诉它应当在什么地方呢?”是的,您可以这样做,但是在窗口改变大小的时候,或者更糟一些情况,即用户改变其屏幕的分辨率的时候,亦或在有人想在其他操作系统上试用应用程序的时候,您立刻就会遇到麻烦。布局管理器把这些担心一扫而空。不是每个人都用相同的设置,所以布局管理器会创建“相对”布局,允许您指定组件相对于其他组件的摆放方式,决定事物改变尺寸的方式。这是好的部分:比听起来更容易。只要调用setLayout(yourLayout)
设置布局管理器即可。后面对add()
的调用可以将组件添加到容器中,并让布局管理器负责将它放在应当的位置上。
目前在 Swing 中包含了大量布局;看起来好象每次发布都会有一个新布局负责不同的目的。但是,有些经过实践检验的布局一直存在,而且会永远存在,我指的是永远 —— 因为从 1995 年 Java 语言的第一个发行版开始,就有这些布局。这些布局是:FlowLayout、GridLayout 和 BorderLayout。
FlowLayout 从左到右安排组件。当空间不足时,就移到下一行。它是使用起来最简单的布局,因此,也就是能力最弱的布局:
setLayout(new FlowLayout()); add(new JButton("Button1")); add(new JButton("Button2")); add(new JButton("Button3"));
FlowLayout 实例
GridLayout 就像您想像的那样工作:它允许指定行和列的数量,然后在添加组件时把组件放在这些单元格中:
setLayout(new GridLayout(1,2)); add(new JButton("Button1")); add(new JButton("Button2")); add(new JButton("Button3"));
GridLayout 实例
即使 Swing 中添加了许多新的布局管理器,BorderLayout 仍然是其中非常有用的一个。即使有经验的 UI 开发人员也经常使用 BorderLayout。它使用东、南、西、北、中的概念在屏幕上放置组件:
setLayout(new BorderLayout()); add(new JButton("Button1"), "North"); add(new JButton("Button2"), "Center"); add(new JButton("Button3"), "West");
BorderLayout 实例
GridBagLayout
虽然上面的示例对于简单的布局来说很好,但是更高级的 UI 需要更高级的布局管理器。这是 GridBagLayout 发挥作用的地方。不幸的是,使用它的时候极易混淆、极为困难,每个曾经用过它的人都会同意这点。我也不能反对;但是除了它的困难之外,它可能是用 Swing 内置的布局管理器创建漂亮 UI 的最好方式。
以下是我的第一个小建议:在最新版的 Eclipse 中,有内置的可视化构建器,这个个小建议可以自动根据每个屏幕的需要来构建必需的 GridBagLayout 代码。请使用这个功能!它会节约无数为了让数字正确而浪费的时间。所以在我用这一节解释 GridBagLayout 如何工作、如何调整它才能让它做得最好时,建议您去找一个可视化构建器并生成代码。它会节约您的工作时间
事件
最后,我们来到 Swing 最重要的一部分:处理事件,对 UI 的交互作出反应。Swing 用事件/侦听器模型处理事件。这个模型的工作方式是:允许某个类登记到某个组件的某个事件上。登记到事件的这个类叫做侦听器,因为它等候组件的事件发生,而且在事件发生时采取行动。组件本身知道如何“激活”事件(即,知道它能生成的交互类型,以及如何让侦听器知道这个交互什么时候发生)。组件与包含有关交互信息的事件和类针对交互进行通信。
把技术方面的空谈放在一边,我们来看几个 Swing 中事件的实例。首先从最简单的示例开始,即一个 JButton,按下它的时候,会在控制台上输出“Hello”。
JButton 知道它什么时候被按下;这是在内部处理的,不需要代码处理它。但是,侦听器需要进行登记,以接收来自 JButton 的事件,这样您才能输出“Hello”。listener
类通过实现listener
接口然后调用 JButton 上的addActionListener()
做到这一点:
// Create the JButton JButton b = new JButton("Button"); // Register as a listener b.addActionListener(new HelloListener()); class HelloListener implements ActionListener { // The interface method to receive button clicks public void actionPerformed(ActionEvent e) { System.out.println("Hello"); } }
JList 也用类似的方式工作。当有人在 JList 中选中什么时,您可能想把选中的对象输出到控制台上:
// myList is a JList populate with data myList.addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { Object o = myList.getSelectedItem(); System.out.println(o.toString()); } } );
从这两个示例,您应当能够理解事件/侦听器模型在 Swing 中如何工作了。实际上,Swing 中的每个交互都是以这种方式处理的,所以通过理解这个模型,您就立即能够理解在 Swing 中如何处理每个事件,以及如何对用户可能抛给您的任何交互做反应了。
模型
现在,您应当了解了 Java 的集合(Collection),这是一组处理数据的 Java 类。这些类包括 ArrayList
、HashMap
和Set
。大多数应用程序在反复处理数据时,经常用这些类。但是,当需要在 UI 中使用这些数据类时,出现了一个限制。UI 不知道如何显示它们。请先想一分钟。如果有一个JList
和一个某种数据对象(例如Person
对象)的ArrayList
,JList
怎样才能知道要显示什么?它是要显示某个人的名字,还是连名带姓一起显示?
这就是模型的概念发挥作用的地方了。虽然模型这个术语表达的范围更大,但是在本教程的示例中,我用 UI 模型这个术语描述组件用来显示数据的类。
在 Swing 中每个处理集合数据的的组件都采用模型的概念,而且这也是使用和操纵数据的首选方法。它清晰地把 UI 的工作与底层数据分开(请回想 MVC 示例)。模型工作的机制是向组件描述如何显示集合数据。我说的“描述”指的是什么呢?每个组件需要的描述略有不同:
- JComboBox 要求其模型告诉它把什么文本作为选项显示,以及有多少选项。
- JSpinner 要求其模型告诉它显示什么文本,前一个和下一个选择是什么。
- JList 也要求其模型告诉它把什么文本作为选项显示,存在多少选项。
- JTable 要求的更多:它要求模型告诉它存在多少列和多少行,列名称、每列的类以及在每个单元格中显示什么文本。
- JTree 要求它的模型告诉它整个树的根节点、父节点和子节点。
您可能会问:为什么要做这么些工作?为什么要把这些功能分开?请想像以下场景:您有一个复杂的 JTable,有许多列数据,您在许多不同的屏幕上使用这个表格。如果您突然决定删除某个列,那么怎么做会更容易呢?修改您使用的每个 JTable 实例中的代码?还是创建一个可以在每个 JTable 实例中使用的模型类,然后只修改这一个模型类呢?显然,所做的修改越少越好。
模型示例
我们来看看模型如何工作,在一个简单的 JComboBox 示例中使用模型。在 JComboBox 前面的演示中,我介绍了如何调用 setItem()
向数据中添加项目。虽然对于简单的演示,这样做可以接受,但是在实际的应用程序中很少这么用。毕竟,在有 25 个选项,而且选项不断变化的时候,您还真的想每次都调用addItem()
25 次对这些选项进行迭代吗?当然不是。
JComboBox 包含一个方法调用 setModel()
,它接受ComboBoxModel
类的实例。应当用这个方法代替addItem()
方法来创建 JComboBox 中的数据。
假设有一个 ArrayList
,其中使用字母表作为其数据(“A”、“B”、“C” ,等等):
MyComboModel model = new MyComboModel(alphaList); myComboBox.setModel(model); public class MyComboModel implements ComboBoxModel { private List data = new ArrayList(); private int selected = 0; public MyComboModel(List list) { data = list; } public void setSelectedItem(Object o) { selected = data.indexOf(o); } public Object getSelectedItem() { return data.get(selected); } public int getSize() { return data.size(); } public Object getElementAt(int i) { return data.get(i); } }
采用模型时更好的地方是:您可以反复重用它。例如,假设 JComboBox 的数据需要从字母表变成 1 到27 的数字。那么只用一行就可以实现这个变化:用新的数据List
添加 JComboBox,不需要使用额外的代码:
myComboBox.setModel(new MyComboModel(numberList));
模型在 Swing 中是非常有好处的特性,因为它们提供了代码重用功能,而且使数据处理更加容易。更常见的应用是在大型应用程序中,服务器端开发人员创建和检索数据,并把数据传递给 UI 开发人员。如何处理这些数据和正确地显示它们,取决于 UI 开发人员,而模型就是实现这项任务的工具。