2014-12-23 57 views
0

我有一个JAVA6 GUI处理数据导入到我们的数据库。我已经实现了一个工作JProgressBar。我明白,对GUI所做的更改必须通过事件派发线程完成 - 我认为我没有(正确/完全)。Swing进度条通过Worker更新到EventDispatch线程

后台工作线程,UploadWorker,是通过将在主程序中创建一个的JProgressBar构造,并设置改变直接一旦完成进度条的值:

// when constructed, this gets set to the main program's JProgressBar. 
JProgressBar progress; 



protected Void doInBackground() throws Exception { 
    write("<!-- Import starting at " + getCurrentTime() + " -->\n"); 
    boolean chunked = false; 
    switch (importMethod) { 

      //do some importing 

    } 

    write("<!-- Import attempt completed at " + getCurrentTime() + "-->\n"); 

    //here changes to the GUI are made 
    progress.setMaximum(0); 
    progress.setIndeterminate(false); 
    progress.setString("Finished Working"); 
    return null; 
} 

这工作正常,但有时(并不总是)抛出好几个NPE在性病出来,用户抱怨:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException 
at javax.swing.plaf.basic.BasicProgressBarUI.updateSizes(Unknown Source) 
...etc... 

不管怎样,我相信有件事我需要做的就是正确的线程上执行这些更新,正确?怎么样?

+0

刚刚发现SwingUtilities.isEventDispatchThread,非常有用,因为我怀疑返回false。 – tenwest

+0

由于你正在与doInBackground方法中的进度条进行交互,那么我会说你当然违反了Swing的单线程规则 – MadProgrammer

+0

我总是喜欢这个[swing worker example](http://www.javacreed.com/swing-worker-example /) – nachokk

回答

0

您可以直接创建一个新的Runnable执行GUI更新和使用SwingUtilities.invokeLater

调用它的GUI线程
+0

从声音上看,如果是这样,你建议OP创建一个'Runnable'来执行那里的长时间运行/阻塞操作,并且使用'invokeLater'调用它,如果是的话,这会把他们放在一个最糟糕的地方,然后他们现在... – MadProgrammer

+0

@MadProgrammer是不是很清楚,我建议创建一个Runnable只执行GUI更新(而不是计算/阻塞操作)并使用invokeLater调用它? – kraskevich

+0

否,否则我不会评论(自330am以来我一直在上升,所以我的大脑可能不会中断响应) - 此外,“SwingWorker”提供了用于发送更新的“publish”/“process”方法EDT以及通过“PropertyChange”支持的内置进度支持和通知... – MadProgrammer

3

有很多的,你可以做到这一点的方式,你可以使用SwingWorkerprocess方法还要更新进度条,但对于我来说,这会将您的工作人员连接到用户界面,而这并不总是令人满意的。

更好的解决方案是利用的SwingWorker进步和PropertyChange的支持,例如....

worker.addPropertyChangeListener(new PropertyChangeListener() { 
    @Override 
    public void propertyChange(PropertyChangeEvent evt) { 
     if ("state".equalsIgnoreCase(evt.getPropertyName())) { 
      SwingWorker worker = (SwingWorker) evt.getSource(); 
      switch (worker.getState()) { 
       case DONE: 
        // Clean up here... 
        break; 
      } 
     } else if ("progress".equalsIgnoreCase(evt.getPropertyName())) { 
      // You could get the SwingWorker and use getProgress, but I'm lazy... 
      pb.setIndeterminate(false); 
      pb.setValue((Integer)evt.getNewValue()); 
     } 
    } 
}); 
worker.execute(); 

这意味着你可以为任何SwingWorker做到这一点,只要它是工人打电话setProgress内部...

public static class ProgressWorker extends SwingWorker { 

    public static final int MAX = 1000; 

    @Override 
    protected Object doInBackground() throws Exception { 
     for (int index = 0; index < MAX; index++) { 
      Thread.sleep(250); 
      setProgress(Math.round((index/(float)MAX) * 100f)); 
     } 
     return null; 
    } 

} 

这样做的好处是,PropertyChange事件通知从事件的调度线程的上下文中调用,从内部更新UI是安全的。

并充分运行的例子...

import java.awt.Dimension; 
import java.awt.EventQueue; 
import java.awt.Graphics; 
import java.awt.Graphics2D; 
import java.awt.GridBagLayout; 
import java.beans.PropertyChangeEvent; 
import java.beans.PropertyChangeListener; 
import javax.swing.JFrame; 
import javax.swing.JPanel; 
import javax.swing.JProgressBar; 
import javax.swing.SwingWorker; 
import javax.swing.UIManager; 
import javax.swing.UnsupportedLookAndFeelException; 

public class SwingWorkerProgressExample { 

    public static void main(String[] args) { 
     new SwingWorkerProgressExample(); 
    } 

    public SwingWorkerProgressExample() { 
     EventQueue.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       try { 
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 
       } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) { 
        ex.printStackTrace(); 
       } 

       JFrame frame = new JFrame("Testing"); 
       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
       frame.add(new TestPane()); 
       frame.pack(); 
       frame.setLocationRelativeTo(null); 
       frame.setVisible(true); 
      } 
     }); 
    } 

    public class TestPane extends JPanel { 

     private JProgressBar pb; 

     public TestPane() { 

      setLayout(new GridBagLayout()); 
      pb = new JProgressBar(0, 100); 
      pb.setIndeterminate(true); 
      add(pb); 

      ProgressWorker worker = new ProgressWorker(); 
      worker.addPropertyChangeListener(new PropertyChangeListener() { 
       @Override 
       public void propertyChange(PropertyChangeEvent evt) { 
        if ("state".equalsIgnoreCase(evt.getPropertyName())) { 
         SwingWorker worker = (SwingWorker) evt.getSource(); 
         switch (worker.getState()) { 
          case DONE: 
           // Clean up here... 
           break; 
         } 
        } else if ("progress".equalsIgnoreCase(evt.getPropertyName())) { 
         // You could get the SwingWorker and use getProgress, but I'm lazy... 
         System.out.println(EventQueue.isDispatchThread()); 
         pb.setIndeterminate(false); 
         pb.setValue((Integer) evt.getNewValue()); 
        } 
       } 
      }); 
      worker.execute(); 

     } 

     @Override 
     public Dimension getPreferredSize() { 
      return new Dimension(200, 200); 
     } 

    } 

    public static class ProgressWorker extends SwingWorker { 

     public static final int MAX = 1000; 

     @Override 
     protected Object doInBackground() throws Exception { 
      for (int index = 0; index < MAX; index++) { 
       Thread.sleep(250); 
       setProgress(Math.round((index/(float) MAX) * 100f)); 
      } 
      return null; 
     } 

    } 

} 
+0

我认为这非常有用!我忘记提到进度条的实际更新是通过ContainerListener完成的,但如果我正确理解此代码,工作人员自己就可以完成它,这可能是更好的方法。 – tenwest

+0

那么,在这个例子中,工人正在计算进度,这会触发一个属性更改事件,然后允许'TestPane'更新它的进度条...试图保持它的解耦:P – MadProgrammer

+0

哇!好的。终于有时间坐下来研究你的答案。我以前没见过'PropertyChangeListener's。对于从'Event.getPropertyName'返回的不同字符串有点困惑(直到我尝试它)。仅供参考,我使用Jersey'ContainerListener'来监视进度,因为它处理HTTP上传。操作程序反映了您提到的“清理”栏的最后一步。 – tenwest