簡體   English   中英

Swing:我如何從AWT線程運行作業,但是在布置窗口之后?

[英]Swing: How do I run a job from AWT thread, but after a window was layed out?

我完整的GUI在AWT線程中運行,因為我使用SwingUtilities.invokeAndWait(...)啟動了主窗口。

現在,我有了一個JDialog,它僅顯示一個JLabel ,它指示某個作業正在進行中,並在作業完成后關閉該對話框。

問題是:未顯示標簽。 這項工作似乎在JDialog完全布局之前就開始了。

當我只是讓對話,而無需等待工作,關閉打開的, 顯示的標簽。

對話框在其ctor中所做的最后一件事是setVisible(true)
諸如revalidate()repaint() ……之類的方法也無濟於事。

即使我為受監控的作業啟動線程,並使用someThread.join()等待它,也無濟於事,因為我猜想當前線程(即AWT線程)被join阻塞了。

JFrame替換JDialog也無濟於事。

那么,這個概念通常是錯誤的嗎? 或者,在確保JDialog (或JFrame )已完全布局 ,我可以管理它完成某些工作嗎?

我要實現的簡化算法:

  • 創建JDialog的子類
  • 確保其及其內容完全布置
  • 啟動一個進程並等待它完成(沒有線程,沒關系)
  • 關閉對話框

我設法寫了一個可重現的測試用例:

編輯現在解決了一個答案的問題:該用例確實顯示了標簽,但是由於對話框的模態,它在“模擬過程”之后無法關閉。

import java.awt.*;
import javax.swing.*;

public class _DialogTest2 {
    public static void main(String[] args) throws Exception {
        SwingUtilities.invokeAndWait(new Runnable() {
            final JLabel jLabel = new JLabel("Please wait...");
            @Override
            public void run() {
                JFrame myFrame = new JFrame("Main frame");
                myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                myFrame.setSize(750, 500);
                myFrame.setLocationRelativeTo(null);
                myFrame.setVisible(true);

                JDialog d = new JDialog(myFrame, "I'm waiting");
                d.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);

                d.add(jLabel);
                d.setSize(300, 200);
                d.setLocationRelativeTo(null);
                d.setVisible(true);

                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(3000); // simulate process
                            jLabel.setText("Done");
                        } catch (InterruptedException ex) {
                        }
                    }
                });

                d.setVisible(false);
                d.dispose();

                myFrame.setVisible(false);
                myFrame.dispose();
            }
        });
    }
}

嘗試這個:

package javaapplication3;

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

public class Main {

public static void main(String[] args)
        throws Exception {
    SwingUtilities.invokeAndWait(new Runnable() {

        final JLabel jLabel = new JLabel("Please wait...");

        @Override
        public void run() {
            JFrame myFrame = new JFrame("Main frame");
            myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            myFrame.setSize(750, 500);
            myFrame.setLocationRelativeTo(null);
            myFrame.setVisible(true);

            JDialog d = new JDialog(myFrame, "I'm waiting");

            d.add(jLabel);
            d.setSize(300, 200);
            d.setLocationRelativeTo(null);
            d.setVisible(true);

            new Thread(new Runnable() {

                @Override
                public void run() {

                public void run() {
                    try {
                        Thread.sleep(3000); // simulate process
                        jLabel.setText("Done");   // HERE: should be done on EDT!
                    } catch (InterruptedException ex) {
                    }
                }
            }).start();


        }
    });
}
}

這是可行的,但這是不正確的。 我會解釋發生了什么。

您的main()方法從“ main”線程開始。 所有與Swing相關的代碼都應在EDT線程上完成。 這就是為什么您(正確)使用SwingUtilities.invokeAndWait(...) 到現在為止還挺好。

但是,EDT上不應有長期運行的任務。 由於Swing是單線程的,因此任何長時間運行的進程都會阻塞EDT。 因此,永遠不要在EDT上執行您的代碼Thread.wait(...) 這是我的修改。 我將調用包裝在另一個線程中。 因此,這是Swing慣用的長期運行任務處理。 為了簡潔起見,我使用Thread類,但我確實建議您使用SwingWorker線程。

非常重要:在前面的示例中,我犯了一個錯誤。 看到帶有“ HERE”注釋的行? 這是另一個Swing單線程規則沖突。 線程內的代碼在EDT 外部運行,因此永遠不要觸摸Swing。 因此,此代碼對於Swing單線程規則不正確。 凍結GUI是不安全的。

如何糾正呢? 簡單。 您應該將調用包裝在另一個線程中,並將其放在EDT隊列中。 因此正確的代碼應如下所示:

    SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                jLabel.setText("Done");
            }
        });

編輯 :這個問題觸及很多Swing相關問題。 無法一次全部解釋...但是下面是另一個片段,它可以滿足您的需求:

public static void main(String[] args)
        throws Exception {
    SwingUtilities.invokeAndWait(new Runnable() {

        final JFrame myFrame = new JFrame("Main frame");
        final JLabel jLabel = new JLabel("Please wait...");
        final JDialog d = new JDialog(myFrame, "I'm waiting");

        @Override
        public void run() {
            myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            myFrame.setSize(750, 500);
            myFrame.setLocationRelativeTo(null);
            myFrame.setVisible(true);

            d.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);

            d.add(jLabel);
            d.setSize(300, 200);
            d.setLocationRelativeTo(null);
            new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        Thread.sleep(3000); // simulate process
                        System.out.println("After");
                        SwingUtilities.invokeLater(new Runnable() {

                            public void run() {


                                d.setVisible(false);
                                d.dispose();

                                myFrame.setVisible(false);
                                myFrame.dispose();
                            }
                        });
                    } catch (InterruptedException ex) {
                    }
                }
            }).start();
            d.setVisible(true);

        }
    });
}

總結一下:

  • 所有與Swing相關的代碼都必須在EDT上運行
  • 所有長時間運行的代碼都不得在EDT上運行
  • 可以使用SwingUtilities.在EDT上運行代碼SwingUtilities. ...
    • invokeAndWait() -顧名思義,調用是同步的,
    • invokeLater() -調用代碼“有時”,但立即返回
  • 如果您正在使用EDT,並且想在另一個線程上調用代碼,則可以:
    • 創建一個新Thread (將Runnable傳遞給新線程或覆蓋它的start()方法)並啟動它,
    • 創建一個新的帶有一些其他功能的SwingWorker線程。
    • 可能使用任何其他線程機制(例如Executor線程)。

典型的GUI場景涉及:

  1. 創建GUI組件,
  2. 連接屬性更改偵聽器,
  3. 執行與用戶操作(即運行屬性更改偵聽器)相關的代碼,
  4. 運行可能耗時的任務,
  5. 更新GUI狀態,

1.,2、3和4.在EDT上運行。 4.不應該。 有許多方法可以編寫適當的線程代碼。 最麻煩的是使用Java早期版本附帶的Thread類。 如果天真地這樣做,則會浪費資源(一次運行的線程太多)。 另外,更新GUI也很麻煩。 使用SwingWorker可以稍微減輕問題。 在啟動,運​​行和更新GUI時,可以保證其行為正常(每個都有專用的方法,您可以重寫並確保它在正確的線程上運行)。

如前面的回答所述,您必須在與EDT不同的線程中運行冗長的作業。

最簡單的恕我直言是使用SwingWorker並添加一個偵聽器以在完成后放置對話框。 這是一個示例:

SwingWorkerCompletionWaiter.java

public class SwingWorkerCompletionWaiter implements PropertyChangeListener {
  private JDialog dialog;

  public SwingWorkerCompletionWaiter(JDialog dialog) {
      this.dialog = dialog;
  }

  @Override
  public void propertyChange(PropertyChangeEvent event) {
      if ("state".equals(event.getPropertyName())
              && SwingWorker.StateValue.DONE == event.getNewValue()) {
          dialog.setVisible(false);
          dialog.dispose();
      }
  }
}

以下代碼將運行一些冗長的任務:

SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

  @Override
  protected Void doInBackground() throws Exception {
    // do something long
  }
};
JDialog dialog = new JDialog();
    // initialize the dialog here
worker.addPropertyChangeListener(new SwingWorkerCompletionWaiter(dialog));
worker.execute();
dialog.setVisible(true);

而且,如果您想創建某種“請稍候”對話框,則只需擴展JDialog並在需要的地方使用擴展的類可能會更容易。

首先,很抱歉對工作有所猜測,在發布代碼之前,我並沒有真正理解您要執行的操作。

盡管如此,我對創建對話框,窗口和常規對話框的方式並不陌生,因此建議您為JDialog創建一個單獨的類。 只是一個簡單的類,就像這樣:

public class MyDialog extends JDialog
{
    public MyDialog(JFrame owner, String title)
    {
        super(owner, title);

        d.setSize(300, 200);           
        d.setLocationRelativeTo(null);           
        d.setVisible(true);
        d.setDefaultCloseOperation(DISPOSE_ON_CLOSE );

        Container c = getContentPane();
        c.setLayout(new FlowLayout());

        c.add(new JLabel("Please wait..."));  
    }
}

並以這種方式啟動對話框:

new MyDialog(this, "Your title");

它可能看起來像狗屎,但可能有效(我尚未測試)。

希望這可以幫助。

您需要在后台線程或主應用程序線程中運行作業。 應該使用SwingUtilities.invokeLater();在事件調度線程(EDT)上創建JDialog和JFrame SwingUtilities.invokeLater(); 然后等待作業完成,然后關閉(EDT)中的對話框。 由於布局和作業位於兩個單獨的線程上,因此應該沒有問題。 實際上,對示例的以下修改有效:

public static void main(String[] args) throws Exception {
    final JFrame myFrame = new JFrame("Main frame");
    final JDialog d = new JDialog(myFrame, "I'm waiting");

    final Thread backgroundJob = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000); // simulate process
                }
                catch (InterruptedException ex) {
                    Logger.getLogger(NewClass.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
    });
    backgroundJob.start();

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            myFrame.setSize(750, 500);
            myFrame.setLocationRelativeTo(null);
            myFrame.setVisible(true);

            d.add(new JLabel("Please wait..."));
            d.setSize(300, 200);
            d.setLocationRelativeTo(null);
            d.setVisible(true);
        }
    });

    backgroundJob.join();

    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            d.setVisible(false);
        }
    });
}

您應該使用invokeLater代替InvokeAndWait。

public class DialogTest {
    public static void main(String[] args) throws Exception {
        final JLabel comp =  new JLabel("the job has started");;
        final JFrame myFrame = new JFrame("Main frame");
        final JDialog d = new JDialog(myFrame, "I'm waiting");
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {

                myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                myFrame.setSize(750, 500);
                myFrame.setLocationRelativeTo(null);
                myFrame.setVisible(true);

                 d.setModalityType(JDialog.ModalityType.APPLICATION_MODAL); 
                d.add(comp);
                d.setSize(300, 200);
                d.setLocationRelativeTo(null);
                d.setVisible(true);


            }
        });
        try {
            Thread.sleep(3000);
            // d.setVisible(false);     
            SwingUtilities.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    comp.setText("the job has finished");
                     d.setVisible(false);         
                     d.dispose();         
                     myFrame.setVisible(false);         
                     myFrame.dispose(); 
                }
            });

        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM