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

时间:2014-10-15 10:15:21

标签: java swing event-dispatch-thread

我已经制作了很多不同的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/

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

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

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的单独调用来更新加载屏幕。

这确实有效 - 尽管我不确定这是一件好事,是否会引起更大规模的副作用。此情况下的加载屏幕只会在强制更新时更新,这意味着如果加载屏幕(例如)某些动画正在运行 - 它将无法正常显示,只会与文本一起更新。 / p>

2 个答案:

答案 0 :(得分:3)

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

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

该方法最适用于使用flyweight pattern最小化渲染的视图组件,例如JTableJListJTextAreaProfile找到重新分解的最佳候选人。

答案 1 :(得分:3)

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

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

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

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

使用CardLayoutJTabbedPane之类的内容时,只有在视图实际可见时才会初始化视图的UI和数据,这样做要好得多。

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