【IT168 技术文章】Swing 工具包提供各种用于创建用户界面的工具和几乎令人眼花缭乱的选项,这些选项用于在程序生存期间修改界面。小心地使用这些功能可以导致界面能够适应用户的需要并简化交互过程。粗心地使用同样的功能可以导致非常混乱或彻底不可用的程序。本文介绍动态 UI 的技术和体系,并提供有关构建有效的界面的帮助。您将修改随 Sun JDK 一起提供的基于 SwingSet2 示例应用程序的源代码;此应用程序的 UI 使用许多动态的特性并且可以作为理解它们的极好的起点。
禁用小部件
动态 UI 的最简单形式是使不可用的菜单项或按钮变灰的 UI。禁用 UI 小部件与禁用所有小部件的方法都是相同的。setEnabled() 函数是 Component 类的一个功能。清单 1 显示了禁用按钮的代码:
清单 1. 禁用按钮
2
正如您看到的,十分简单。关键问题是何时应该 启用或禁用一个按钮。通常的设计决策是当按钮不可用时禁用它。例如,当一个文件从上一次保存以来还没有被修改时,很多程序禁用 Save 按钮(以及任何相应的菜单项)。
关于禁用按钮的重要警告是要记住在适当的时候重新启用它们。例如,如果在单击按钮和按钮的动作完成之间有一个确认步骤,即使确认失败也应该重新启用按钮。
调整范围
有时,应用程序需要动态地调整数值小部件的范围,例如 Spinner 或者 Slider。这可能比它看起来要复杂许多。特别是 Slider 有二级功能 —— 刻度、刻度间隔和标签 —— 这些可能需要随着范围的调整而加以调整以避免灾难发生。
SwingSet2 示例没有进行任何一项调整,所以您需要通过把 ChangeListener 连接到一个可以修改其他滑块的滑块来修改它。输入新的 SliderChangeListener 类, 如清单 2 所示:
清单 2. 更改滑块的范围
2 JSlider h;
3
4 SliderChangeListener(JSlider h) {
5 this.h = h;
6 }
7
8 public void stateChanged(ChangeEvent e) {
9 JSlider js = (JSlider) e.getSource();
10 int i = js.getValue();
11 h.setMaximum(i);
12 h.repaint();
13 }
14 }
15
当创建第三个水平滑块时(最初示例中的滑块在每个单位处带有标记,在 5、10 和 11 等处带有标签), 另外还创建了一个新的 SliderChangeListener,它把滑块作为构造器参数传递。当创建第三个垂直的滑块(范围从 0 到 100)时,新的 SliderChangeListener 作为变更监听器添加到它。这大致按预期的那样工作:调整垂直滑块改变了水平滑块的范围。
不幸的是,刻度和标签根本不能很好地工作。当范围变得不是太大时,每五个刻度处的标签能正确地工作,但是刻度 11 处的额外标签很快成为一个可用性问题,如图 1 所示:
图 1. 一起运行的标签
更新刻度和标签
明显的解决方案是,只要滑块的最大值被更新,就在水平滑块上简单地设置刻度间隔,如清单 3 所示:
清单 3. 设置刻度间隔
2 int tickMajor, tickMinor;
3 tickMajor = (i > 5) ? (i / 5) : 1;
4 tickMinor = (tickMajor > 2) ? (tickMajor / 2) : tickMajor;
5 h.setMajorTickSpacing(tickMajor);
6 h.setMinorTickSpacing(tickMinor);
7 h.repaint();
8
目前清单 3 是正确的,但是它没有引起画在屏幕上的标签的任何变化。必须使用 setLabelTable() 分别设置标签。 添加额外一行修复它:
这仍然出现在刻度 11 处存在最初设置的标签这样的错误。当然本来的意图是想在滑块的最右端始终有一个标签。可以通过删除旧的标签(在设置新的最大值之前)然后添加一个新的标签达到这一目的。此代码 “几乎” 可以工作:
清单 4. 替换标签
2 JSlider js = (JSlider) e.getSource();
3 int i = js.getValue();
4
5 // clear old label for top value
6 h.getLabelTable().remove(h.getMaximum());
7
8 h.setMaximum(i);
9
10 int tickMajor, tickMinor;
11 tickMajor = (i > 5) ? (i / 5) : 1;
12 tickMinor = (tickMajor > 2) ? (tickMajor / 2) : tickMajor;
13 h.setMajorTickSpacing(tickMajor);
14 h.setMinorTickSpacing(tickMinor);
15 h.setLabelTable(h.createStandardLabels(tickMajor));
16 h.getLabelTable().put(new Integer(i),
17 new JLabel(new Integer(i).toString(), JLabel.CENTER));
18 h.repaint();
19 }
20
如果我已经告诉过您一次,那么我就已经告诉您两次了。
我使用几乎 的意思是,虽然清单 4 中的代码删除了刻度 11 处的标签,但是它没有在刻度 i 处添加新标签;相反,只能看到间隔为 tickMajor 的标签。首先此解决方法相当令人讨厌:
清单 5. 强迫显示更新
2
这个看起来无意义的操作实际上有重大的作用。每当设置标签表时就生成滑块的标签。没有为了修改对表进行特殊回调,所以添加到表中的新值不必产生效果;很显然,清单 5 中的空操作具有使 Swing 知道它必须更新显示的副作用。(以免您认为这是我自己发明的,请注意最初的 SwingSet 代码包括这样一个调用。)
这只留下了一个问题。标签出现在滑块的末端这个非常合理的期望有时使两个标签互相直接相邻,乃至重叠,如图 2 所示:
图 2. 滑块末端的重叠标签
很多解决此问题的方法都是可行的。编写自己的代码以使用值来填充标签表并停止以前的序列以便序列中的最后标签与滑块的末端有一些隔离。我将把这个作为作业留给您。