布局、模型和事件
既然您已经知道了大多数(肯定不是全部)可以用来制作 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 开发人员,而模型就是实现这项任务的工具。