Swing 工具包提供各种用于创建用户界面的工具和裂源几乎令人眼花缭乱的选项 这些选项用于在程序生存期间修改界面 小心地使用这些功能可以导致界面能够适应用户的需要并简化交互过程 粗心地使用同样的功能可以导致非常混乱或彻底不可用的程序 本文介绍动态 UI 的技术和体系 并提供有关构建有效的界面的帮助 您将修改随 Sun JDK 一起提供的基于 SwingSet 示例应用程序的源代码此应用程序的 UI 使用许多动态的特性并且可以作为理解它们的极好的起点
禁用小部件
动态 UI 的最简单形闭源盯式是使不可用的菜单项或按钮变灰的 UI 禁用 UI 小部件与禁用所有小部件的方法都是相同的 setEnabled() 函数是 Component 类的一个功能 清单 显示了禁用按钮的代码
清单 禁用按钮
button setEnabled(false)
正如您看到的 十分简单 关键问题是何时应该 启用或禁用一个按钮 通常的设计决策是当按钮不可用时禁用它 例如 当一个文件从上一次保存以来还没有被修改时 很多程序禁用 Save 按钮(以及任何相应的菜单项)
关于禁用按钮的重要警告是要记住在适当的时候重新启用它们 例如 如果在单击按钮和按钮的动作完成之间有一个确认步骤 即使确认失败也应该重新启用按钮
调整范围
有时 应用程序需要动态地调整数值小部件的范围 例如 Spinner 或者 Slider 这可能比它看起来要复杂许多 特别是 Slider 有二级功能 —— 刻度 刻度间隔和标签 —— 这些可能需要随着范围的调整而加以调整以避免灾难发生
SwingSet 示例没有进行任何一项调整 所以您需要通过把 ChangeListener 连接到一个可以修改其他滑块的滑块来修改它 输入新的 SliderChangeListener 类 如清单 所示
清单 更改滑块的范围
class SliderChangeListener implements ChangeListener { JSlider h SliderChangeListener(JSlider h) { this h = h } public void stateChanged(ChangeEvent e) { JSlider js = (JSlider) e getSource() int i = js getValue() h setMaximum(i) h repaint() } }
当创建第三个水平滑块时(最初示例中的滑块在每个单位处带有标记 在 和 等处带有标签) 另外还创建了一个新的 SliderChangeListener 它把滑块作为构造器参数传递 当创建第三个垂直的滑块(范围从 到 )时 新的 SliderChangeListener 作为变更监听器添加到它 这大致按预期的那样工作 调整垂直滑块改变了水平滑块的范围
不幸的是 刻度和标签根本不能很好地工作 当范围变得不是太大时 每五个刻度处的标签能正确地工作 但是刻度 处的额外标签很快成为一个可用性问题 如图 所示
图 一起运行的标签
更新刻度和标签
明显的解决方案是 只轿和要滑块的最大值被更新 就在水平滑块上简单地设置刻度间隔 如清单 所示
清单 设置刻度间隔
// DOES NOT WORK int tickMajor tickMinortickMajor = (i >) ? (i / ) : tickMinor = (tickMajor >) ? (tickMajor / ) : tickMajorh setMajorTickSpacing(tickMajor)h setMinorTickSpacing(tickMinor)h repaint()
目前清单 是正确的 但是它没有引起画在屏幕上的标签的任何变化 必须使用 setLabelTable() 分别设置标签 添加额外一行修复它
h setLabelTable(h createStandardLabels(tickMajor))
这仍然出现在刻度 处存在最初设置的标签这样的错误 当然本来的意图是想在滑块的最右端始终有一个标签 可以通过删除旧的标签(在设置新的最大值之前)然后添加一个新的标签达到这一目的 此代码 几乎 可以工作
清单 替换标签
public void stateChanged(ChangeEvent e) { JSlider js = (JSlider) e getSource() int i = js getValue() // clear old label for top value h getLabelTable() remove(h getMaximum()) h setMaximum(i) int tickMajor tickMinor tickMajor = (i >) ? (i / ) : tickMinor = (tickMajor >) ? (tickMajor / ) : tickMajor h setMajorTickSpacing(tickMajor) h setMinorTickSpacing(tickMinor) h setLabelTable(h createStandardLabels(tickMajor)) h getLabelTable() put(new Integer(i) new JLabel(new Integer(i) toString() JLabel CENTER)) h repaint()}
如果我已经告诉过您一次 那么我就已经告诉您两次了
我使用几乎 的意思是 虽然清单 中的代码删除了刻度 处的标签 但是它没有在刻度 i 处添加新标签相反 只能看到间隔为 tickMajor 的标签 首先此解决方法相当令人讨厌
清单 强迫显示更新
h setLabelTable(h getLabelTable())
这个看起来无意义的 *** 作实际上有重大的作用 每当设置标签表时就生成滑块的标签 没有为了修改对表进行特殊回调 所以添加到表中的新值不必产生效果很显然 清单 中的空 *** 作具有使 Swing 知道它必须更新显示的副作用 (以免您认为这是我自己发明的 请注意最初的 SwingSet 代码包括这样一个调用 )
这只留下了一个问题 标签出现在滑块的末端这个非常合理的期望有时使两个标签互相直接相邻 乃至重叠 如图 所示
图 滑块末端的重叠标签
很多解决此问题的方法都是可行的 编写自己的代码以使用值来填充标签表并停止以前的序列以便序列中的最后标签与滑块的末端有一些隔离 我将把这个作为作业留给您
在许多情况下 为了启用和禁用菜单项而限制菜单修改是很实际的 此方法容易受到用于禁用项的常规警告的影响 避免由于偶然地禁用重要项而使程序处于不可用状态
添加或删除菜单项或子菜单也是可能的 修改 JMenuBar 没有这么容易没有从工具栏上删除和替换单个菜单的接口 如果您想修改工具栏(而不是向最右端添加菜单) 需要制作一个新的工具栏并用它替换旧的工具栏
修改单个菜单会立即生效您不必在将菜单连接到工具栏或另一个菜单之前构建一个菜单 当需要修改菜单选项的选择时 最容易的方法是修改选定的菜单 您可能仍然想添加或删除完整的菜单 并且这么做并不是特别难 清单 显示一个将菜单插入到菜单条中给定索引前的方法的简单示例 此示例假定要替换的 JMenuBar 连接到 JFrame 对象 但是任何能使您获得和设置菜单条的东西的工作方式都是一样的
清单 把一个菜单插入到菜单条中
public void insertMenu(JFrame frame JMenu menu int index) { JMenuBar newBar = new JMenuBar() JMenuBar oldBar = frame getJMenuBar() MenuElement[] oldMenus = oldBar getSubElements() int count = oldBar getMenuCount() int i for (i = i <count++i) { if (i == index) newBar add(menu) newBar add((JMenu) oldMenus[i]) } frame setJMenuBar(newBar)}
上面的代码我不是开始时就试图编成这样这是最终的版本 已经很好地修复过了所以它能够运行并反映一些有趣的怪事 初看起来 实现此功能的明显方法似乎应该是使用 getComponentAtIndex() 但是这种方法已经受到了反对 幸运的是 getSubElements() 已经足够好 为 newBar add() 而进行到 JMenu 的强制类型转换可能是安全的 但是我不喜欢这样 getSubElements() 接口不仅对菜单条而且对菜单进行 *** 作 菜单可能具有几种类型的子元素 JMenu 是可以添加到 JMenuBar 的惟一元素 所以必须把元素强制转换为 JMenu 以把它传递到 JMenuBar add() 方法 不幸的是 如果将来的 API 修订版允许将除 JMenu 类型之外的元素添加到 JMenuBar 就不再需要把返回的元素强制转换 JMenu了
清单 中的代码反映了另外一个相当微妙的界面怪事菜单数必须提前缓存起来 当把菜单添加到新的菜单条时 它们从旧的菜单条中被删除 虽然看起来相似 但是清单 中的代码不能工作 因为循环提前结束了:
清单 循环结束太早
// DOES NOT WORK for (i = i <oldBar getMenuCount()++i) { if (i == index) newBar add(menu) newBar add((JMenu) oldMenus[i])}
清单 中的循环只复制一半数量的菜单 例如 如果开始菜单条上有 个 菜单 它复制前面的两个菜单 复制完第一个菜单以后 i 的值为 并且 getMenuCount() 返回 在复制完第二个菜单以后 i 的值为 并且 getMenuCount() 返回 因此循环结束 我没有找到任何介绍通过向菜单条添加菜单从而从另一个菜单条删除菜单这样的特性的文档 因此可能不是有意这样 但是 它很容易达到这个目的
从菜单条删除菜单稍微容易一些只是把所有其他的菜单从旧的菜单条复制到新的菜单条 就完成了删除菜单 很容易!
如果界面使用了很多动态菜单更新 创建一组菜单条并在它们之间切换而不是一直动态地更新它们也许会更好一些 但是 如果如此快地改变菜单 可能会使用户完全发疯
勘误 在本文的草稿阶段 我忽略了 JMenuBar 类的继承方法的列表 其实 它有 remove 和 add 方法可以用来在指定的索引处进行删除和插入 另外一个教训是 查看继承的方法而不仅仅是特定于类的方法
调整窗口大小
所幸的是对于大多数情况 窗口大小调整是自动进行的 但是需要考虑调整大小产生的一些影响 在非常小的窗口中 按钮条 菜单条和类似功能可能变成有问题的 管理程序自身的图形面板需要响应调整大小事件 让 Swing 处理对 UI 元素的包装 但是要密切注视组件的大小不要获取一次组件的尺寸然后就一直使用这些值
更微妙的地方是 一些设计决策(例如滑块上刻度的密度)可能被适度地更新以响应窗口大小调整事件 像素宽度的滑块不能具有像 像素宽度的滑块那样多的可读标签 您可能想通过添加全新的有用功能来让 UI 更进一步用在大型显示器上
但是 在多数情况下 可以忽略窗口大小调整 您不应该做的是不必要地阻止或重写它 布局代码中的窗口一侧的便捷工具不是必需的 最小的窗口大小可能是无可厚非的 但是要让人们能把窗口调整到他们所需要的大小
一般原则
Swing 工具包在 UI 设计方面提供了很大的灵活性 如果小心地使用 动态更新界面的选项能够极大地简化该界面例如 只有应用菜单的选项时 用户才能容易地显示菜单
不幸的是 一些 API 的特性可能使此方法产生一些离奇的行为 并且副作用和相互影响并不总是像您期望的那样记录下来 如果您有使用动态界面的想法 就要准备在调试上花费一些额外的时间 您可能从 Swing 库的困境中走出来并发现自己需要处理出人意料的行为和/或 bug
不要让缺乏明显的实现让您气馁 像本文的 JMenuBar 示例所显示的 即使当 API 不支持某个任务时 您也能自己实现它 虽然有一点间接
尽量不要走极端 当动态 UI 让用户清楚它们的固有限制时 它们才能最好地发挥作用 理想的情况是 用户甚至可能不会注意到界面变化 如果他们能够使用程序的 Object 菜单的惟一时刻是当他们使某个对象被选择时 那么其余的时间他们将不会介意不存在该菜单
另一方面 如果存在这种可能性 用户不能猜测出一个选项不可用的原因 这时让用户尝试 *** 作并获得包含信息的消息也许会更好 这对于一些 *** 作尤其重要 如果保存选项被禁用 而我想保存数据 那么这不会发生作用 程序可能认为已经保存了数据 但是为什么不让我无论如何都保存它呢?如果存在不能保存文件的特殊原因 我可能想知道是什么原因
lishixinzhi/Article/program/Java/hx/201311/26042Swing初体验 对于想学习Swing编程的朋友 我们特地为大家准备了一些小窍门 首先 下载并阅读代码是极有必要的 由于这是一篇关于Swing的教程 所以 我们只是尽可能讲解一些与Swing有关的内容 与Swing无关的内容一般不会涉及 例如算法部分 其次 受篇幅限制 也不可能在这里将每部分代码都写得完完整整的 所以 大家也需要对照完整代码来看 最后 为了使大家更容易把精力集中在Swing学习上 我们也将游戏开发中所需资源放在下载文件中 大家下载后便能够编译运行 看到执行结果 (下载游戏源文件) 顶层容器 什么是顶层容器?当我们使用Java进行图形编程的时候 图在哪里绘制呢?我们需要一个能够提供图形绘制的容器 这个容器就被称为顶层容器 你也可以把它想象成一个窗口 顶层容器是进行图形编程的基础 一切图形化的东西 都必然包括在顶层容器中 在Swing中 我们有三种可以使用的顶层容器 它们分别是:JFrame:用来设计类似于Windows系统中的窗口形式的应用程序 JDialog:和JFrame类似 只不过JDialog是用来设芦旦计对话框 JApplet:用来设计可以在嵌入在网页中的Java小程序 如果需要使用Swing制作一个窗口类程序 我们的代码看起来应该是这样:import javax swing *public class KyodaiUIextends JFrame {……} 控件 控件是构成应用程序界面的基本元素 按钮 文本框 进度条等 这些都是控件 控件(这里我们只讨论可视化控件)又可以分为容器控件和非容器控件 从字面意义上来理解 容器控件就是能包含其他控件的特殊控件 例如 Java中的JPanel控件就属于容器型控件 我们可以在JPanel中放置按钮 文本框等非容器控件 你甚至可以在JPanel中再放置若干个JPanel控件(值得注意的是 顶层容器也是容器型控件 每一个窗口应用程序中有且只能有一个顶层容器控件 换句话说 顶层容器不能包括在其他的控件中) Java中的容器控件有很多 除刚才提到的JPanel外 还有JTabbedPane JScrollPane等 非容器控件有JButton JLabel JTextField等 如果你需要向某个容器型的控件中添加控件 你可以使用 add(Component p) 方法来实现 如:JPanel panel = new JPanel()JButton button = new JButton()panel add(button) 布局 什么是布局?布局是Java中用来控制控件排列位置的一种界面管理系统 使用过其他可视化编程开发语言的人在初次接触Java界面设计时 总会感觉到Java界面设计很别扭:居然没有提供所见即所得的设置控件坐标的方法!然而 事实证明 Java本身提供的布局管理系统也一样能够出色地完成我们的需要 而且在跨平台时表现得更有优势 常用的布局有:BorderLayout:将界面分割为上下左右以及中间一块区域的管理系统 在BorderLayout布局中 最多你只能放 个控件 如果超过 个控件 建议还是选用其他的布局系统吧 GridLayout:GridLayout是将用户界面切割为棋盘一样的布局管理系统 如果我们要设计一个类似于Windows中自带的计算器软件 GridLayout无疑是最佳选择 FlowLayout:FlowLayout与上述两类布局管理系统不太一样 在FlowLayout中 你不必指定每个控件放在哪 你只需要把控件加入到FlowLayout中 FlowLayout就会根据你添加控件的顺序依次放置控件 如果空间不够 会自动换行 在对这几个布局管理系统有了基本认识后 我们就一起来进入界面设计吧 在仔细观察了QQ游戏中 连连看 的设闭州定后 我们可以发现 整个界面分为三个区 顶部是系统菜单区 占地面积最大的是用户游戏区 另外还有一个用户交互区 每个区域中都由若干控件组成 这么多控件 我们从哪开始入手呢?由于容器控件中可以放置其他控件 因此 我陪态扰们只需要先确定放置的容器控件就可以了 既然已经知道需要使用容器控件的个数 接下来让我们就进入布局管理系统的选择 用GridLayout?似乎有点勉强 用FlowLayout?还有更好的选择吗?对了 我想你一定想到了是BorderLayout吧 如下图 所示 动手之前 大家一定要注意的是 界面的设计要先考虑好尺寸 不管是主程序界面的大小还是每个区域的大小 如果没有设计好合适的尺寸 将来改动起来会十分痛苦 下面便是相应的源程序:import java awt *import javax swing *public class KyodaiUI extends JFrame {public KyodaiUI() {this setSize( )//将窗体的大小设定为 * this setDefaultCloseOperation(JFrame EXIT_ON_CLOSE)this setResizable(false)//窗体不能改变大小this setTitle( 连连看 )//设置标题JPanel toolBar = new JPanel()toolBar setBackground(Color white)toolBar setPreferredSize(new Dimension( ))JPanel actionPanel = new JPanel()//新建JPanel型的控件actionPanel setBackground(Color yellow)//设置背景色actionPanel setPreferredSize(new Dimension( ))//设置大小JPanel contentPanel = new JPanel()contentPanel setBackground(Color blue)contentPanel setPreferredSize(new Dimension( ))this getContentPane() add(toolBar BorderLayout NORTH)this getContentPane() add(actionPanel BorderLayout EAST)this getContentPane() add(contentPanel BorderLayout CENTER)}public static void main(String[] args) throws HeadlessException {KyodaiUI kyodaiUI = new KyodaiUI()kyodaiUI show()}}让我们来看看上面这段程序是如何运行的 首先 extends JFrame表明了这是从JFrame中继承过来的 JFrame是最基本的顶层容器控件 实际上 在JDK中 以字母J打头的控件都是Swing控件 然后设置了容器的属性 其中 setDefaultCloseOperation(JFrame EXIT_ON_CLOSE)是用来告诉Java虚拟机 当用户点击窗体右上角的 关闭 按钮时 关闭该窗口进程 如果不这么做的话 你会发现虽然你可以点将窗口关闭 然而程序却没有退出 在接下来的代码中 我们为顶层容器添加了三个Panel容器 要注意的是 在AWT中 我们可以直接写为add(toolBar BorderLayout NORTH) 而在 Swing 中却一定要写成getContentPane() add(toolBar BorderLayout NORTH) 否则程序就会出错 现在大家可以放在编译运行看看 是不是和我的运行结果一样(见图 )? 边框 虽然我们使用了不同前景色来区别不同的区域 然而却没有层次感 加上边框一定会漂亮许多 在Java中 所有以J打头的Swing控件都可以使用setBorder方法来为自己设置边框 边框有很多种 线型 凸起 凹下 空的 你甚至可以自由组合形成个人风格 所有的Border都必须使用javax swing BorderFactory中提供的静态方法来创建 比如:Border border = BorderFactory createBevelBorder(BevelBorder LOWERED new Color( ) new Color( ) new Color( ) new Color( ))现在 我们将toolBar setBackground(Color white)改为toolBar setBorder(border) 立体效果是不是已经出现了?实战??写上自己的大名现在我们已经有了一个能够运行的界面了 虽然它什么也做不了 但是请你别慌 罗马不是一天建成的 现在让我们在菜单区提供一个 关于 菜单 用来显示程序的信息 难道你不想让别人知道你的大名吗?Swing本身就提供了现成的按钮控件JButton 我们只需要创建一个新的按钮:JButton about = new JButton( 关于 )这个按钮该怎么放到菜单区而不是别的地方呢?我们可以加入下面的代码:toolBar add(about)咦 怎么点按钮没有反应?这是因为你还没有告诉程序点击按钮时要做什么事情呢 要为按钮添加事件响应 首先需要使用about addActionListener(this)来告诉程序监听按钮按下时的事件 由于ActionListener是一个程序接口 因此 我们在类的申明的地方也得做一点小小的修改:public class KyodaiUI extends JFrame implements ActionListener { }实现ActionListener接口是为了告诉程序我要进行事件处理了 当然 最后我们得添加响应事件的代码:public void actionPerformed(ActionEvent e) {if (e getSource() == about) {JOptionPane showMessageDialog(this 我的大名 关于 JOptionPane INFORMATION_MESSAGE)return }}其中 e getSource() 表示当前触发事件的控件 由于我们的程序中往往会有多个以上的控件 这些控件都有可能产生事件 所以我们必须使用这个方法来找到产生事情的控件 小结 让我们一起来回顾一下今天所学的内容:首先我们了解了顶层容器 也知道了控件分为容器控件和非容器控件 同时还知道使用边框 最后 我们还小小的处理了一下按钮的事件 学而时习之 不亦说乎 就让我留点小小的作业 帮助大家巩固一下今天所学的内容:上面我们添加的按钮在菜单栏的中间 并不美观 lishixinzhi/Article/program/Java/hx/201311/25907
在编程语言中的事件就是当某组件的状态发生改变的时候通知其它对象发生了这件事,我们在这里只讨论图形界面的组件,有可能是按钮、文本框、菜单、多选等等。
对于通知其它对象的方法一般有两种模式:
1)推模式:状态改变的对象通知其它对象;
2)拉模式:其他对象不停的查看该对象状态是否发生改变。
例如我去小卖部买烟,要一包红塔山,结果小卖部没货了,如果我把我得电话给小卖部老板,烟如果到了请给我打电话,这就是推模式。如果我每隔5分钟来小卖部看一看烟是否到了,这就是拉模式。
Java采用的是推模式,所有监听事件都基于观察者设计模式,所以我们也可以自己给予观察者开发出专用的监听器。
我们JavaGUI程序开发,会经常使用到监听事件,比如一个小计算器的程序,当我们点击“计算”按钮后,希望程序将两个文本框中的数字相加,那么就必需给这个按钮添加事件:
1)按钮的动作脊御冲触发事件接口是ActionListener接口(不同组件要实现不同功能需要有不同的接口),我们需要写一个事件类,实现ActionListener接口,接口中需要我们实现的拆则方法actionPerformed(ActionEvent e)代表了当按钮事件被触樱歼发后需要程序做些什么,比如在这里用System.out.println("你好")在控制台打印你好,这一步就相当于我把电话写在纸上。
2)调用JButton组件实例的addActionListener(ActionListener al)方法(其它事件也有相应的方法)将上面写的事件类注册到这个按钮上,这就相当于我把写着电话的纸条交给小卖部的老板。
上述两部工作完成后,这个按钮的事件就添加完成了,程序运行,点击按钮,动作事件被触发,控制台显示“你好”。
实例:
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import javax.swing.JButton
import javax.swing.JFrame
/**
* 一个简单的按钮事件教学实例
* @author 米强
*
*/
public class Test extends JFrame {
public Test() {
super("简单的按钮事件实例")
// 一个按钮的实例化对象
JButton button = new JButton("按钮")
// 构造一个事件类,该类实现了ActionListener动作接口
MyActionListener action = new MyActionListener()
// 为这个按钮添加动作事件(匿名类等写法在这里不做讨论)
button.addActionListener(action)
// 将按钮添加到窗体中
getContentPane().add(button)
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
setSize(300, 200)
setLocationRelativeTo(null)
setVisible(true)
}
public static void main(String[] args) {
new Test()
}
}
/**
* 事件类,实现ActionListener接口
* @author 米强
*
*/
class MyActionListener implements ActionListener {
/**
* 动作事件出发后所执行的方法
*/
public void actionPerformed(ActionEvent e) {
// 在控制台打印“你好”
System.out.println("你好")
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)