Swing应用程序初始化和加载屏幕方法

我已经制作了很多不同的Swing应用程序,它们的加载时间通常在几秒到几分钟之间变化,具体取决于应用程序UI /数据大小。 在某些情况下,应用程序数据加载与UI加载混合在一起。

几秒钟的加载时间不是问题,但是当它比10秒钟更长时 – 显然应该显示某种加载屏幕,直到UI /数据完全初始化为止。

你通常会做什么 – 首先创建某种加载屏幕(例如带有徽标的窗口和一些在加载应用程序时正在更新的标签),并从应用程序中的各种加载“点”更新它。

问题是 – 应用程序通常在排队到EDT的单个调用中加载,很难将其分成多个调用EDT,而不会使应用程序的代码复杂化。 因此,由于应用程序加载是在排队到EDT的单个调用中执行的,因此您无法正确更新加载屏幕 – 直到应用程序初始化之后才会显示更新,因为EDT正忙于加载应用程序。

因此,为了在某些情况下实现加载屏幕,我已经在EDT之外移动了应用程序UI初始化,并且设计它们的方式是在执行加载时不会执行任何UI更新。 应用程序的框架显示和所有应用程序UI操作仍将在EDT中执行。 这通常不太好,但经过大量测试并查看Swing代码后,我确信它不会导致任何问题,即使在大型应用程序上也是如此。 尽管如此,即使不引起任何问题,这也不是一件好事。

所以问题是: 在EDT中保持应用程序初始化的同时,可以使用哪些方法正确显示和更新应用程序加载屏幕?

希望它不是太宽泛。

这是一个“虚拟”应用程序,它展示了一种“坏”方法:

import javax.swing.*; import java.awt.*; public class DummyApplication extends JFrame { private static JDialog loadingDialog; private static JLabel loadingProgress; public DummyApplication () { super ( "Dummy application" ); dummyProgressUpdate ( "Loading content...", 3000 ); final JLabel label = new JLabel ( "Custom content" ); label.setBorder ( BorderFactory.createEmptyBorder ( 100, 100, 100, 100 ) ); getContentPane ().add ( label ); dummyProgressUpdate ( "Loading settings...", 3000 ); setDefaultCloseOperation ( WindowConstants.EXIT_ON_CLOSE ); pack (); setLocationRelativeTo ( null ); dummyProgressUpdate ( "Opening application...", 1000 ); } private static void dummyProgressUpdate ( final String status, final int time ) { SwingUtilities.invokeLater ( () -> loadingProgress.setText ( status ) ); dummyLoadTime ( time ); } private static void dummyLoadTime ( final long time ) { try { Thread.sleep ( time ); } catch ( final InterruptedException e ) { e.printStackTrace (); } } public static void main ( final String[] args ) throws Exception { // Displaying loading screen from EDT first SwingUtilities.invokeAndWait ( () -> { loadingDialog = new JDialog ( ( Window ) null, "Loading screen" ); loadingProgress = new JLabel ( "Initializing application...", JLabel.CENTER ); loadingProgress.setBorder ( BorderFactory.createLineBorder ( Color.LIGHT_GRAY ) ); loadingDialog.getContentPane ().setLayout ( new BorderLayout () ); loadingDialog.getContentPane ().add ( loadingProgress ); loadingDialog.setUndecorated ( true ); loadingDialog.setAlwaysOnTop ( true ); loadingDialog.setModal ( false ); loadingDialog.setSize ( 400, 100 ); loadingDialog.setLocationRelativeTo ( null ); loadingDialog.setVisible ( true ); } ); // Initializing application outside of the EDT final DummyApplication applicationFrame = new DummyApplication (); // Displaying application from the EDT SwingUtilities.invokeLater ( () -> { loadingDialog.setVisible ( false ); applicationFrame.setVisible ( true ); } ); } } 

前段时间我发现在JDK7中实现了这个有趣的东西:
http://sellmic.com/blog/2012/02/29/hidden-java-7-features-secondaryloop/

SecondaryLoopfunction允许在EDT线程中阻止更多代码执行,而不会导致UI卡住。 它与在EDT中打开时的模态JDialog基本相同。

因此,通过此function,我发现可能是解决此问题的更好方法:

 import javax.swing.*; import java.awt.*; public class DummyApplication extends JFrame { private static JDialog loadingDialog; private static JLabel loadingProgress; public DummyApplication () { super ( "Dummy application" ); dummyProgressUpdate ( "Loading content...", 3000 ); final JLabel label = new JLabel ( "Custom content" ); label.setBorder ( BorderFactory.createEmptyBorder ( 100, 100, 100, 100 ) ); getContentPane ().add ( label ); dummyProgressUpdate ( "Loading settings...", 3000 ); setDefaultCloseOperation ( WindowConstants.EXIT_ON_CLOSE ); pack (); setLocationRelativeTo ( null ); dummyProgressUpdate ( "Displaying application...", 1000 ); } private static void dummyProgressUpdate ( final String status, final int time ) { // Use SecondaryLoop to block execution and force loading screen update final SecondaryLoop loop = Toolkit.getDefaultToolkit ().getSystemEventQueue ().createSecondaryLoop (); SwingUtilities.invokeLater ( () -> { loadingProgress.setText ( status ); loop.exit (); } ); loop.enter (); // Perform dummy heavy operation dummyLoadTime ( time ); } private static void dummyLoadTime ( final long time ) { try { Thread.sleep ( time ); } catch ( final InterruptedException e ) { e.printStackTrace (); } } public static void main ( final String[] args ) throws Exception { // Displaying loading screen from EDT first SwingUtilities.invokeAndWait ( () -> { loadingDialog = new JDialog ( ( Window ) null, "Loading screen" ); loadingProgress = new JLabel ( "Initializing application...", JLabel.CENTER ); loadingProgress.setBorder ( BorderFactory.createLineBorder ( Color.LIGHT_GRAY ) ); loadingDialog.getContentPane ().setLayout ( new BorderLayout () ); loadingDialog.getContentPane ().add ( loadingProgress ); loadingDialog.setUndecorated ( true ); loadingDialog.setAlwaysOnTop ( true ); loadingDialog.setModal ( false ); loadingDialog.setSize ( 400, 100 ); loadingDialog.setLocationRelativeTo ( null ); loadingDialog.setVisible ( true ); } ); // Initializing and displaying application from the EDT SwingUtilities.invokeLater ( () -> { final DummyApplication applicationFrame = new DummyApplication (); loadingDialog.setVisible ( false ); applicationFrame.setVisible ( true ); } ); } } 

正如你所看到的 – 在dummyProgressUpdate方法中,我为我的情况做了一些棘手的解决方法 – 基本上我阻止在EDT中执行的执行并等待排队到EDT的单独调用将更新加载屏幕。

这实际上是有效的 – 虽然我不确定这是一件好事,是否会引起更大规模的副作用。 此外,只有在进行强制更新时才会更新加载屏幕,这意味着如果加载屏幕(例如)某些动画正在运行 – 它将无法正常显示,并且只会与文本一起更新。

如果您正在加载数据或必须连接到远程服务或其他冗长的应用程序,那么SwingWorker就是可用的approches的简单方法,因为它通过其setProgress方法和PropertyChangeListener支持提供进度支持。

它通过它的process / publish方法提供UI同步,允许您安全地更新UI和done方法和/或PropertyChangeListener可用于确定何时完成工作

如果您正在尝试加载UI元素,那么您应该考虑延迟加载方法。

所以不要一次加载所有的UI元素,特别是当用户可能实际上没有看到UI的各个部分时,只有当你必须加载这些元素并且节省时间时才更好。

使用CardLayoutJTabbedPane的东西时,只有在视图实际可见时才初始化视图的UI和数据要好得多。

如果你真的想要,你也可以在视图不再可见时卸载它,断开监听器与数据源的连接,停止它以响应用户实际上看不到的数据源的变化……

如此处所示,您可以使用SwingWorkerpublish()中间结果在后台初始化任意大量的数据,并在process()的EDT上更新视图组件的模型。 使用PropertyChangeListener来调整任何进度动画。 禁用在加载完成之前不应使用的控件; 在StateValueDONE时启用它们。 以这种方式,

  • 结果将立即开始出现。
  • 感知延迟会减少。
  • GUI将保持响应。

该方法最适用于使用flyweight模式最小化渲染的视图组件,例如JTableJListJTextArea等。 配置文件以找到重新分解的最佳候选者。