3

我的项目建立在 Java 的 Swing 库之上。它生成显示我的 GUI(正常工作)的 EDT。

初始化EDT的程序入口:

public final class Main {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Start());
    }

    class Start implements Runnable {
        private Model model = new Model();
        private Controller controller = new Controller(model);
        private View view = new View(controller);

        @Override
        public void run() {
            // Initialize the view and display its JFrame...
        }
    }
}

}

但是,当在我的 GUI 中单击按钮/单选框等时,Controller 类必须对模型执行操作。

我的问题如下:

  • 我应该将控制器的代码包装在一个新的 SwingWorker 中吗?
    • 如果不是,我应该将我的模型代码包装在一个新的 SwingWorker 中吗?
  • 如果我用线程包装控制器的代码,我是否需要在我的模型中同步共享状态变量?
  • 如果我的模型在新线程上运行,通知我的 GUI 更改,这会发生在 EDT 上还是新线程上?

例如:

public class Controller {
    public void updateModel() {
        new SwingWorker<Void, Void>() {
            @Override
            protected Void doInBackground() throws Exception {
                model.somethingSomethingSomething();
            }
        }.execute();
    }
}

public class Model {
    public void somethingSomethingSomething() {
        notifyListeners(); // This is going to notify whichever GUI 
                           // is listening to the model.
                           // Does it have to be wrapped with Swing.invokeLater?
    }
}

public class View {
    // This function is called when the model notifies its listeners.
    public void modelChangedNotifier() {
        button.setText("THE MODEL HAS CHANGED"); // Does this occur on the EDT?
    }
}
4

3 回答 3

4

不是从 更新您的模型doInBackground(),而是从publish()中间结果更新您的模型process(),这在 EDT 上执行。在此示例中,JTable对应于您的ViewTableModel对应于您的Model。注意JTable听它自己的TableModel

于 2013-02-06T01:12:50.550 回答
4

您可以在此处阅读:使用 Java SE 6 中的 SwingWorker 提高应用程序性能。简而言之:所有不影响 UI 的耗时操作必须在另一个线程中完成。要显示操作结果,您必须返回 EDT。例如,如果您进行数据库搜索,您应该显示一个进度条(通常是无限的)并使用 SwingWorker 开始搜索。要在表格中显示搜索结果,您必须在 EDT 中。或者,您可以使用foxtrot lib(它可以让你的代码更方便 Swing 而无需重新设计它)。如果您的控制器代码永久更新了 swing 小部件,您应该在 EDT 中执行它,或者至少在 EDT 中执行这些 UI 更新(使用 SwingUtilities.invokeLater,在 SwingWorker 或 swing.Timer 中进行块处理)。所以你的样本是错误的:模型更新应该在 EDT 中是最新的。

于 2013-02-05T21:44:59.530 回答
-2

一种替代方法,来自 Java Concurrency in Practice 9.4.2,使用“拆分”或“共享数据模型”。您可以在任何您想要的线程上更新您的业务模型,可能是长期运行的非 EDT 线程。但是,无需直接调用 notifyListeners() 并担心您在哪个线程上,只需调用 myComponent.repaint(),它将在 EDT 上排队重绘请求。

然后,在您的方法中的某处paintComponent(),您显式地从模型中获取所有新数据,通常在一个名为modelToView()

   commentTextArea.setText(myModel.getCommentText());
   fooLabel.setText(myModel.getFooText());
   ...

好处是线程不是问题,至少在某些人看来,这“有道理”,并且模型很好地与视图分离。缺点是您每次都在重置所有值。因此,如果您有 100 个 JComponent,则需要设置 100 个东西。此外,视图与模型非常紧密地耦合。


工作代码示例

@MadProgrammer 和@kleopatra 是正确的,如果视图直接包含正在更新的组件,你会得到一个“无限循环的厄运”。证明见

Demo_14716901_Fails

但是,如果视图与组件隔离,则可以避免无限循环。通常,更高级别的视图将包含诸如 JSplitPanes 之类的东西,持有 JScrollPanes,持有 Boxes 或更多 JPanel,持有实际的低级别组件。所以这个要求,IMO,并不是不合理的。

Demo_14716901_Works的工作代码

为反对者添加了一些评论

有些人想打败 Swing。他们正在编写仪器控制代码或算法,只想完成他们的工作,而不用担心 EDT、无尽的 SwingWorkers 和 invokeLaters。这种技术可以让他们完成工作。注意到一个重要的警告,它有效。(就我个人而言,我理解并且通常喜欢 Swing,但很多人不理解)。

虽然 Swing 组件是很好的 MVC,但它们通常处于过于微观的层面。“真实”模型不是单个字符串,而是几十个值。“真实”的视图不是一个单一的 JLabel,它是很多 JPanel,每个都有很多组件,结合滚动条、拆分器等。这种技术通常更适合现实世界,让程序员在更高的层次上自然地思考。

至于“不好的做法”,请与 Brian Goetz、Josh Bloch 等一起讨论。好吧,那是“诉诸权威”,但它对我有用。:-)

于 2013-02-05T23:28:20.083 回答