【问题标题】:How to create "please wait" Swing dialog while component paints如何在组件绘制时创建“请稍候”Swing 对话框
【发布时间】:2012-07-29 14:52:32
【问题描述】:

对于 Swing 来说还是比较新的,但是经过几个小时的搜索后,我在网上找不到答案,因此发了这篇文章(抱歉,如果已经回答但我忽略了它)。

我在 Swing 应用程序中使用 JFreeChart。一些图表相对较重(180k 数据点),JFreeChart 的 ChartPanel 需要大约 6 秒来完成它的第一个 paintComponent()。

因此,我想在组件绘制时在对话框中显示“请稍候”消息(无需使用 SwingWorker 显示进度)。我试图覆盖paintComponent方法,但不幸的是消息从未出现在屏幕上(我猜线程直接进入绘制图表,没有花时间绘制对话框)。

我的代码如下所示:

public class CustomizedChartPanel extends ChartPanel{

private static final long serialVersionUID = 1L;
private JDialog dialog = null;
boolean isPainted = false;

public CustomizedChartPanel(JFreeChart chart) { super(chart); }

@Override
public void paintComponent(Graphics g) {
    //At first paint (which can be lengthy for large charts), show "please wait" message
    if (! isPainted){
        dialog = new JDialog();
        dialog.setUndecorated(true);
        JPanel panel = new JPanel();
        panel.add(new JLabel("Please wait"));
        dialog.add(panel);
        dialog.pack();
        GuiHelper.centerDialog(dialog); //Custom code to center the dialog on the screen
        dialog.setVisible(true);
        dialog.repaint();
    }

    super.paintComponent(g);

    if (! isPainted){
        isPainted = true;
        dialog.dispose();
            super.repaint();
        }
}
}

非常感谢任何有关如何解决此问题/最佳实践的建议!

谢谢, 托马斯


更新:

感谢您的提示和辩论:非常有帮助。

我开始使用 invokeLater() 实现建议的解决方案,因为我担心 JLayer 解决方案无法工作,因为它也在 EDT 上运行。

不幸的是,invokeLater() 调用paintComponent() 时出现空指针异常。

我的代码如下所示:

    @Override
public void paintComponent(Graphics graph) {
    //At first paint (which can be lengthy for large charts), show "please wait" message
    if (! isPainted){
        isPainted = true;
        dialog = new JDialog();
        dialog.setUndecorated(true);
        JPanel panel = new JPanel();
        panel.add(new JLabel("Please wait"));
        panel.add(new JLabel("Please wait !!!!!!!!!!!!!!!!!!!!!!!!!!!!!"));
        dialog.add(panel);
        dialog.pack();
        GuiHelper.centerDialog(dialog); //Custom code to center the dialog on the screen
        dialog.setVisible(true);
        dialog.repaint();
        RunnableRepaintCaller r = new RunnableRepaintCaller(this, graph, dialog);
        SwingUtilities.invokeLater(r);
    }
    else super.paintComponent(graph); //NULL POINTER EXCEPTION HERE (invoked by runnable class)
}

而可运行的类是:

public class RunnableRepaintCaller implements Runnable{
private ChartPanel target;
private Graphics g;
private JDialog dialog;

public RunnableRepaintCaller(ChartPanel target, Graphics g, JDialog dialog){
    this.target = target;
    this.g = g;
    this.dialog = dialog;
}

@Override
public void run() {
    System.out.println(g);
    target.paintComponent(g);
    dialog.dispose();
}
}

再次感谢任何指针!

托马斯

【问题讨论】:

  • 最简单的做法是设置一个繁忙的光标并在完成后重置为正常...
  • @user1235867 看看我下面的回答。它使用 SwingWorker,但这是你唯一的出路。我不能有更好的建议。此外,在 paintComponent 中,您应该只执行“绘画”操作,而不是修改 UI 或调用 invokeLater。

标签: java swing jfreechart


【解决方案1】:

这是一个示例和/但它使用 SwingWorker。您应该认真考虑使用它,因为如果操作系统以某种方式使您的框架无效并且您的 JFreeChart 的加载是在 EDT(事件调度线程)上完成的,那么您的 GUI 将看起来冻结。

它还允许您在处理数据时提供更好的用户反馈。 (对不起,如果代码有点长,但大多数有趣的代码都在 initUI 和 SwingWorker 中)。

注意:您可以使用 JLayer(如果您使用 Java 7)代替对话框,但在我的示例中这是不必要的。

代码灵感来自http://www.vogella.com/articles/JFreeChart/article.html

/**
 * This code was directly taken from: http://www.vogella.com/articles/JFreeChart/article.html
 * All credits goes to him for this code.
 * 
 * Thanks to him.
 */

import java.util.List;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PiePlot3D;
import org.jfree.data.general.DefaultPieDataset;
import org.jfree.data.general.PieDataset;
import org.jfree.util.Rotation;

public class PieChart extends JFrame {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                initUI();
            }
        });
    }

    protected static void initUI() {
        // First we create the frame and make it visible
        final PieChart demo = new PieChart("Comparison");
        demo.setSize(500, 270);
        demo.setVisible(true);
        // Then we display the dialog on that frame
        final JDialog dialog = new JDialog(demo);
        dialog.setUndecorated(true);
        JPanel panel = new JPanel();
        final JLabel label = new JLabel("Please wait...");
        panel.add(label);
        dialog.add(panel);
        dialog.pack();
        // Public method to center the dialog after calling pack()
        dialog.setLocationRelativeTo(demo);

        // allowing the frame and the dialog to be displayed and, later, refreshed
        SwingWorker<JFreeChart, String> worker = new SwingWorker<JFreeChart, String>() {

            @Override
            protected JFreeChart doInBackground() throws Exception {
                publish("Loading dataset");
                // simulating the loading of the Dataset
                try {
                    System.out.println("Loading dataset");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // This will create the dataset 
                PieDataset dataset = demo.createDataset();
                publish("Loading JFreeChart");
                // simulating the loading of the JFreeChart
                try {
                    System.out.println("Loading JFreeChart");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // based on the dataset we create the chart
                JFreeChart chart = demo.createChart(dataset, "Which operating system are you using?");
                // we put the chart into a panel
                return chart;
            }

            @Override
            protected void process(List<String> chunks) {
                label.setText(chunks.get(0));
                dialog.pack();
                dialog.setLocationRelativeTo(demo);
                dialog.repaint();
            }

            @Override
            protected void done() {
                try {
                    // Retrieve the created chart and put it in a ChartPanel
                    ChartPanel chartPanel = new ChartPanel(this.get());
                    // add it to our frame
                    demo.setContentPane(chartPanel);
                    // Dispose the dialog.
                    dialog.dispose();
                    // We revalidate to trigger the layout
                    demo.revalidate();
                    // Repaint, just to be sure
                    demo.repaint();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        };
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                     worker.execute();
            }
        });
        dialog.setVisible(true);
    }

    public PieChart(String applicationTitle) {
        super(applicationTitle);
    }

    /** * Creates a sample dataset */

    private PieDataset createDataset() {
        DefaultPieDataset result = new DefaultPieDataset();
        result.setValue("Linux", 29);
        result.setValue("Mac", 20);
        result.setValue("Windows", 51);
        return result;

    }

    /** * Creates a chart */

    private JFreeChart createChart(PieDataset dataset, String title) {

        JFreeChart chart = ChartFactory.createPieChart3D(title, // chart title
                dataset, // data
                true, // include legend
                true, false);
        PiePlot3D plot = (PiePlot3D) chart.getPlot();
        plot.setStartAngle(290);
        plot.setDirection(Rotation.CLOCKWISE);
        plot.setForegroundAlpha(0.5f);
        return chart;

    }

}

【讨论】:

  • 如果对话框是模态的,这将不起作用,因为执行将在 'dialog.setVisible(true);' 处等待。我想使用模式来防止用户在加载时与其他小部件进行交互。
  • @Ryan 正确发现。只需在调用worker.execute() 后移动 setVisible(true)。
  • @"Guillaume Polet" 现在你已经引入了竞争条件。您无法保证工作线程在 dialog.setVisible(true) 甚至被调用之前不会运行完成。如果发生这种情况,可能会发生各种奇怪的事情,例如在工作完成后弹出模态对话框,现在程序卡住了。
  • @Ryan 虽然可能,但它不太可能发生,因为本质上你在 Swing Worker 中调度长时间运行的任务,而不是在短时间内发生的事情。为了完全完成并确保不发生竞争条件,我们可以将execute()call 移动到invokeLater 块中,确保在worker 执行之前首先显示对话框。
  • @"Guillaume Polet" 我认为您的解决方法也不正确。我认为您需要将 dialog.setVisible(true) 移动到 doInBackground 方法中的 invokeLater 以建立正确的先发生关系。哦,说它不太可能发生并不能激发人们对程序的信心。我宁愿它永远不会发生。
【解决方案2】:

您需要在一个新线程中启动等待对话框。我不知道您是如何创建图表的,但这里有一个示例

SwingUtilities.invokeLater(new Runnable() {
        public void run() {
             dialog = new JDialog();
             dialog.setUndecorated(true);
             JPanel panel = new JPanel();
             panel.add(new JLabel("Please wait"));
             dialog.add(panel);                
             GuiHelper.centerDialog(dialog); 
             dialog.setVisible(true);

            Thread performer = new Thread(new Runnable() {
                public void run() {
                    dialog.setVisible(false); 
                    //Here the code that prepare the chart                              
                }
        });
        performer.start();
    }
});     

【讨论】:

  • -1 您的代码没有意义,在 EDT 之外的另一个线程中执行 GUI 操作是非常糟糕的主意。
  • 我想这很容易说是一个非常糟糕的主意而不提供任何参考...对我来说没有意义的是尝试同时执行两个单独的操作:加载面板的显示以及在同一个线程中加载实际图表。
  • docs.oracle.com/javase/tutorial/uiswing/concurrency/… Swing 事件处理代码在称为事件调度线程的特殊线程上运行。这是必要的,因为大多数 Swing 对象方法都不是“线程安全的”:从多个线程调用它们可能会导致线程干扰或内存一致性错误。一些 Swing 组件方法在 API 规范中被标记为“线程安全”;这些可以从任何线程安全地调用。忽略此规则的程序可能大部分时间都能正常运行,但会出现难以重现的不可预测错误。
【解决方案3】:

我已经很久没有用 Java 做任何事情了,但据我所知,repaint() 方法实际上并没有导致任何绘图发生。它只是将控件标记为需要尽快重绘。如果要立即绘制组件,则需要直接调用paint()方法。

【讨论】:

    【解决方案4】:

    您可以按照here 的说明使用JLayer。 这专门用于您想要的繁忙指示器。

    您还可以将JPanelsetEnabled(false) 保持在一起,直到您的数据完全加载。这可以防止对JPanel 进行不必要的点击。

    【讨论】:

    • +1 通知 JLayer 是关于 Java7 的,对于 Java6 必须看 JXLayer,JLayer 是基于 JXLayer,作为替代是 GlassPane,注意 Glasspane 是做 AWT 组件,需要使用 Swing JComponents
    • @GuillaumePolet 在尝试之前可能无法确定。但它不会被冻结,而是看起来比平时移动得慢。在大多数应用程序中都可以看到这种行为。如果将LayerUI 设为私有类,则可以使用标志来指示终止线程,CustomizedChartPanel 可用于显示所有图表。这也增强了 GUI。
    • @NitinChhajer 不,如果在 EDT 上加载了 JFreeChart,这将不起作用,因为任何重绘事件(即使从 javax.swing.Timer 触发,因为它们也依赖于 InvocationEvent)都不会在 JFreechart 完成加载之前发生。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-19
    • 1970-01-01
    相关资源
    最近更新 更多