我整理了这篇文章,使开始使用Swing的人们意识到创建Swing应用程序时需要考虑的问题。 我看过的大多数Swing教程都只是帮助人们快速显示一些漂亮的小部件,而没有解释一些需要开发完整应用程序时需要解决的问题。 本文不涉及完整的实现细节,因为可以通过阅读相关文档来找到这些细节,因此无需在此处重新定义它们。 鼓励读者查找遇到的任何新术语。
无论软件的域应用程序如何不同,Swing程序员都面临相同的问题。 值得庆幸的是,这些问题如此普遍,以至于我们站在自己肩膀上的巨人已经想出了一些通用的想法和解决方案。
不要不尊重EDT通常有两种类型的工具箱,即多线程工具箱和单线程工具箱。 当前认为,多线程图形工具包要么实施起来太困难,要么实施起来太不自然,程序员不正确地使用它而不创建与线程相关的bug。
在单线程GUI工具箱中,所有GUI活动都在一个线程(事件调度线程)上运行。 接口的任何更新(包括重绘)和对这些更新的响应(事件处理)都在此EDT上作为排队的离散事件运行。与多线程模型相比,该模型易于实现,并且程序员更容易正确使用。
Swing使用单线程模型。 这是关于挥杆的第一件事。 Swing程序中有许多错误可归因于(或不知道)这一点。 通常有两种不尊重EDT的方式:
a)在EDT上运行长任务如上所述,事件处理代码在EDT上运行。 由于EDT依次运行任务(单线程),因此在它上面运行的任何任务都会阻止其他任务(在EDT上运行)执行任何操作。 这意味着在运行事件处理代码时,用户无法接受任何输入,并且界面冻结。 因此,事件处理代码必须非常短,也许不超过半秒。 如果侦听器代码需要更长的时间,则需要在其自己的线程中启动它。 此类线程通常称为工作线程或后台线程。
注意事项摇摆工人
•有时,您可能实际上希望阻止用户执行任何操作,直到完成特定任务。 正确的方法是在某处调用disableInput方法,在其自己的线程中启动任务,并显示进度条。
•在确定任务是否长时需要一些洞察力,因为某些任务在测试过程中可能看起来很短,但是当考虑到实际数据时在生产中可能变得很长。例如,某些数据库操作的时间取决于数据库的大小。
•如果侦听器代码在其自己的线程中运行,但是需要更新接口,那么我们将遇到一个小问题,因为所有接口更新都需要在EDT上完成。 一种解决方案是简单地根据需要使用SwingUtilities.invokeLater或SwingUtilities.invokeAndWait在EDT上对此类更新进行排队,这仅将Runnable的run方法中的任务排队到EDT上。
•辅助线程的另一个考虑因素是允许用户控制后台任务的运行方式。 通常,可能要求用户能够暂停,取消,重新启动或停止后台进程。 这需要特别注意,因为当被控制的任务在单独的(工作人员)线程中运行时,应在EDT上使任务控制可用。
事实证明,对于从工作线程更新GUI并为用户提供控制后台任务的能力的问题,可以有一个通用的解决方案。 解决方案是javax.swing.SwingWorker类。 从Java SE 6开始,该类可作为javax.swing包下JDK的一部分使用。 在此之前,程序员必须编写自己的SwingWorker类。 这两个令人惊讶的完全不同的类的文档很容易获得。
SwingWorker类提供了一种从后台任务返回中间结果的方法,还提供了一种用于使用这些中间结果更新接口的方法。 它还解决了一些可能的内存不一致性错误,这些错误可能是由EDT线程和辅助线程访问同一对象引起的。
b)从除EDT之外的其他线程更新GUI。
上面已经提到了这一点。 编写任何更新GUI的代码时都需要小心,以确保始终从EDT运行该代码,因为大多数swing方法都不是线程安全的。 如果不确定某些代码是否可以在EDT上运行,则可以在执行与GUI相关的任务之前使用SwingUtilities.isEventDispatchThread方法。 如果调用返回false,则需要使用SwingUtilities.invokeLater在EDT上将GUI任务排队。
该规则的一个小但有用的例外是在javax.swing.text.JComponet中定义的setText方法,该方法被指定为线程安全的。 一个常见的错误是从初始线程(即从main方法)创建GUI元素。 在main方法中运行的代码由一个特殊线程运行,该线程在本讨论中唯一的特殊属性是与EDT不同。 相反,所有GUI初始化代码都应捆绑在一个方法上,该方法从在EDT上排队的线程的run方法调用。 如Sun的Java教程中所述,SwingUtilities的invokeAndWait是执行此操作的常用方法。
SwingUtilities.invokeLater(new Runnable()) {
public void run() {
createAndShowGUI();
}
}
您会发现,除了上面的代码行之外,main方法通常无事可做。
切勿混用AWT和Swing。
请勿以从未将柠檬和牛奶一起放入咖啡的方式混合使用这两种方式。 AWT组件不是用Java实现的,而是使用本机对等组件。 这意味着AWT组件在不同平台上看起来会有所不同。 但是,它们对平台的依赖也意味着它们比Swing更快,尽管只是部分地并且越来越不那么引人注目。 相比之下,Swing组件是用Java编写的(因此,组件名称前用J表示),并且可以使其在所有平台上看起来都一样。
极小的速度差异和不同的外观不是永远不要混用的主要原因。 两者的z排序方案不同,因此在混合其成分时会发生许多绘画问题。 基本上,事物不会显示在您期望它们显示的位置,并且某些组件会在没有明显原因的情况下隐藏在其他组件之后。 无论如何,在使用Swing时都不需要使用AWT组件,因为Swing是AWT的组件明智的超集。
设计时要考虑i18n和L10N
Swing程序包含许多对语言环境敏感的对象。 这些对象需要通过与源代码分开来进行本地化。 不应使用诸如new JButton(“ OK”)之类的东西,因为可能需要将标签“ OK”更改为另一种语言(或另一种英语单词)。 图标也可能在不同的环境中具有不同的含义。 基本上,用户可见的任何文本都不得在代码中进行硬编码,以便甚至在没有程序员或无需重新部署应用程序的情况下,甚至可由用户自己更改。 Java SE中有几个类可以帮助程序的国际化和本地化。
在设计时考虑到可访问性
•消息必须始终来自ResourceBundles。 ResourceBundle本身需要仔细设计,以免不必要地重复条目。
•避免串联用于用户显示的字符串。 有些语言是从右到左阅读的,而其他语言是从左到右阅读的。 您可以轻松了解为什么串联会带来问题。
•消息处理操作必须始终使用Collaters完成。 如果要进行大量比较,则应使用CollationKeys来提高性能。
•必须始终以对语言环境敏感的方式来格式化可显示对象。
无障碍访问不应仅考虑残疾人。 不论身体残障与否,人们都不同。 它们具有不同的颜色和字体首选项,有些则比其他人更喜欢不同的输入设备。 更糟糕的是,由于计算机现在已集成到各种小工具中,因此计算机的输入接口越来越异国。 编写一个满足所有这些不同的人和环境的软件程序听起来像是一项艰巨的任务。 两条通用原则将使您的软件易于访问。
Standard Java附带了一个小的可访问性API,您可以使用它来使软件更易于访问。 该API通常允许您对某些未知的输入和输出设备进行编程,而无需假定这些接口的特定实现。 这意味着您的代码将在实现这些接口的任何奇特的输入设备上运行。
•为特定输入设备编写支持时,请尝试使支持尽可能完整。 例如,键盘支持应该如此全面,以至于仅使用键盘就可以实现所有系统功能。
•花更多的时间为用户可配置的组件编写代码,而不是为配置组件编写代码。
Sun的教程列出了一些
支持可访问性的规则。 。 数据绑定通常,您将需要在swing应用程序中使用持久性数据。 您可能需要使用文件或(更常见的)数据库中的数据。 使用JDBC API可以轻松完成数据库访问。 在数据访问代码和接口构造代码之间需要有明确的区分。 特别是,绝不能在任何使用swing组件的类中找到SQL语句。
通常,通过实现某种模式或更实际地将模式组合在一起,可以最好地实现关注点分离。 一些模式可以在不同的数据源类型之间进行切换,例如,不同的RDBM,例如
道 通常,Swing组件(观察者)可以观察到您的DAO,以便它们可以在模型更改时做出适当的响应。 这种情况很自然地适用于流行的Model View Controller体系结构。 请注意,尽管Swing与纯MVC紧密结合,但它并未使用纯MVC ,并且在大多数实践中可以将其视为MVC。 大多数Swing组件都具有模型,您可以在其中通过将数据设置为那些组件模型而不是组件本身来轻松地实施纯MVC,或者更好的是,自己实现(或更普遍地扩展)那些模型。 所需要做的就是满足这些模型接口的约定。 这样做的好处是,当更改其(通用)基础模型时,可以正确更新同一模型的不同表示形式。 事件处理优先使用Actions和AbstractAction类,而不是到处都有actionListeners。 使用动作的优点是它们可以使功能和状态完全分开。 使用动作还可以轻松启用/禁用功能,而无需为使用相同动作的组件复制代码。 动作类对于长期持久性机制也是可见的,因此可以将持久性更轻松地集成到程序中。 轻微的缺点是,由于操作使用命令模式,因此每个操作都需要一个类。
以智能方式构造接口组件有些事情只需要一点思考过程。 拥有一组实用程序类,这些类具有一些可以为您完成常见任务的方法。 Sun对SwingUtilities类使用了相同的技巧。 与该班同学结识,以便您不必不断地重新发明轮子。 避免编写代码来装饰每个组件,而要使用装饰器来装饰某些类型的组件。
仅在绝对必要时才构造组件,并且在可能需要显示相似组件时不愿完全破坏大型组件。
尼尔森的启发法GUI开发仍具有不可忽视的艺术方面。 遗憾的是,大多数程序员在艺术素质方面都不是天才。 我们大多数都是事实驱动的极客,他们不喜欢蒙娜丽莎和挂在墙上的祖母画像之间的区别(当然,对祖母没有任何不敬意,上帝保佑他们的灵魂)。 如果您像我一样,并且在高中的大部分时间里都在别人参观美术馆时摆弄着自己,那么您可能没有GUI设计所需的额外艺术优势。 我发现
Nielsen的启发式作为设计准则特别有用。 它们不是一成不变的规则,在某些方面确实可以认为是矛盾的,但是它们有助于避免一些基本的设计失误。 结论由于所创建应用程序的性质,本文中的某些建议可能不可行,您可能希望使用与所建议的方法相反的方法。 重要的是,至少您了解这些建议并证明有必要采用其他方法。 Swing程序员可以使用的选择太多了,我认为太多了,因为其中有些只是促进了肮脏的解决方案。 我希望这篇文章有意义,并且对您有所帮助。
From: https://bytes.com/topic/java/insights/853297-doing-swing-right