我已经制作了很多不同的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>
答案 0 :(得分:3)
如图here所示,您可以使用SwingWorker
,publish()
中间结果在后台初始化任意大量数据,并更新您的视图组件模型EDT在process()
。使用PropertyChangeListener
来调整任何进度动画。禁用在加载完成之前不应使用的控件; StateValue
为DONE
时启用它们。以这种方式,
该方法最适用于使用flyweight pattern最小化渲染的视图组件,例如JTable
,JList
,JTextArea
等Profile找到重新分解的最佳候选人。
答案 1 :(得分:3)
如果您正在加载数据或必须连接到远程服务或其他冗长的应用程序,那么SwingWorker
就更容易使用可用的approches,因为它通过setProgress
提供进度支持方法和PropertyChangeListener
支持。
它通过它的process
/ publish
方法提供UI同步,允许您安全地更新用户界面和done
方法和/或PropertyChangeListener
可用于确定工人何时完成
如果您正在尝试加载UI元素,那么您应该考虑使用延迟加载方法。
所以不要一次加载所有UI元素,特别是当用户可能实际上没有看到UI的各个部分时,只有当你必须加载这些元素并节省时间时才更好。
使用CardLayout
或JTabbedPane
之类的内容时,只有在视图实际可见时才会初始化视图的UI和数据,这样做要好得多。
如果你真的想要,你也可以在视图不再可见时卸载它,断开监听器与数据源的连接,停止它以响应用户实际看不到的数据源的变化...... / p>