Mik*_*rin 6 java swing event-dispatch-thread
我制作了很多不同的 Swing 应用程序,它们的加载时间通常在几秒到几分钟之间变化,具体取决于应用程序 UI/数据大小。此外,在某些情况下,应用程序数据加载与 UI 加载混合。
几秒钟的加载时间不是问题,但是当它超过 10 秒时 - 很明显应该显示某种加载屏幕,直到 UI/数据完全初始化。
您通常会做什么 - 首先创建某种加载屏幕(例如,带有徽标的窗口和一些在应用程序加载时正在更新的标签),然后从应用程序中的各种加载“点”更新它。
问题是 - 应用程序通常在排队进入 EDT 的单个调用中加载,并且很难将其分成对 EDT 的多次调用而不会使应用程序的代码复杂化。因此,由于应用程序加载是在排队到 EDT 的单个调用中执行的,因此您根本无法正确更新加载屏幕 - 由于 EDT 忙于加载应用程序,因此在应用程序初始化之前不会显示更新。
因此,为了在某些情况下实现加载屏幕,我已将应用程序 UI 初始化移到 EDT 之外,并将它们设计为在执行加载时不会执行任何 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 );
} );
}
}
Run Code Online (Sandbox Code Playgroud)
前段时间我发现在 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 );
} );
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的 - 在dummyProgressUpdate方法中,我已经为我的案例做了一些棘手的解决方法 - 基本上我正在阻止在 EDT 中执行的执行并等待一个单独的调用排队进入 EDT 以更新加载屏幕。
这确实有效——尽管我不确定这是一件好事,也不确定它是否会在更大范围内造成任何副作用。而且在这种情况下加载屏幕只会在进行强制更新时更新,这意味着如果加载屏幕(例如)正在运行一些动画 - 它不会正确显示并且只会与文本一起更新。
如果您正在加载数据或必须连接到远程服务或其他冗长的应用程序,那么 aSwingWorker是可用方法中更简单的方法,因为它通过其setProgress方法和PropertyChangeListener支持提供进度支持。
它通过它的process/publish方法提供UI同步,允许您安全地更新UI和done方法和/或PropertyChangeListener可用于确定工作人员何时完成
如果您所做的只是尝试加载 UI 元素,那么您应该考虑采用延迟加载方法。
因此,与其一次性加载所有 UI 元素,特别是当用户可能实际上没有查看 UI 的各个部分时,最好仅在绝对必要时加载这些元素,这样可以节省时间。
CardLayout当使用诸如或 之类的东西时JTabbedPane,最好只在视图实际变得可见时初始化视图的 UI 和数据。
如果您确实想要,您还可以在视图不再可见时卸载它,断开侦听器与数据源的连接,停止响应用户实际上不会看到的数据源的更改...