【问题标题】:MVC - SwingWorker with a long running process that should update the viewMVC - SwingWorker 具有应更新视图的长时间运行进程
【发布时间】:2012-10-06 12:04:21
【问题描述】:

当使用 SwingWorker 时,如何将视图与模型分离,该进程需要将更新发送回控制器?

  • 我可以使用 SwingWorkers doInBackground() 来保持 EDT 响应,例如从那里调用 model.doLongProcess() 很棒!

  • 我遇到的问题是尝试在进程完成之前取回数据,以更新视图的进度..

  • 我知道我可以使用 SwingWorkers publish() 方法取回数据,但我认为这迫使我在 doInBackground() 中编写 doLongProcess() 方法的代码。


为了参考 MVC 实现,我看起来有点像这样:

http://www.leepoint.net/notes-java/GUI/structure/40mvc.html

/ structure/calc-mvc/CalcMVC.java -- Calculator in MVC pattern.
// Fred Swartz -- December 2004

import javax.swing.*;

public class CalcMVC {
    //... Create model, view, and controller.  They are
    //    created once here and passed to the parts that
    //    need them so there is only one copy of each.
    public static void main(String[] args) {

        CalcModel      model      = new CalcModel();
        CalcView       view       = new CalcView(model);
        CalcController controller = new CalcController(model, view);

        view.setVisible(true);
    }
}

我有一个模型类,它将许多其他类包装在一起形成一个简单的控制器接口。

我真的不想将所有/部分/任何代码从这些类移到控制器中 - 它不属于那里。


更新:

这是我正在采用的方法 - 它不是最干净的解决方案,它可能被视为在语义层面上滥用 PropertyChangeSupport..。

基本上所有具有长时间运行方法的低级类都会有一个propertyChangeSupport 字段。长时间运行的方法会定期调用firePropertyChange() 来更新方法的状态,而不必报告属性的变化——这就是我所说的语义滥用!

然后包装低级类的模型类捕获这些事件并发出自己的高级firePropertyChange .. controller 可以监听...

编辑:

澄清一下,当我调用 firePropertyChange(propertyName, oldValue, newValue);

  • propertyName ---> 我滥用propertyName 来表示主题名
  • oldValue =null
  • newValue = 我要广播的消息

然后是模型中的 PropertyChangeListener 或任何可以根据主题名称识别消息的地方。

所以 Iv 基本上将系统弯曲为像发布-订阅一样使用它......


我猜想代替上述方法,我可以向要更新的低级类添加一个进度字段,然后基于它的 firePropertyChange.. 这将符合它的预期使用方式。

【问题讨论】:

  • 请查看对我的回答的编辑以响应您的更新。

标签: java swing model-view-controller design-patterns swingworker


【解决方案1】:

我将发布/进程对视为将数据从 SwingWorker 推送到 GUI 中。另一种传递信息的方法是通过使用 PropertyChangeSupport 和 PropertyChangeListener 让 GUI 或控制 SwingWorker 中的信息。考虑

  • 为您的模型提供一个 PropertyChangeSupport 字段,
  • 为其添加和删除 PropertyChangeListener 方法
  • 让它通知支持对象状态的变化。
  • 让 SwingWorker 将 PropertyChangeListener 添加到模型中。
  • 然后让 SwingWorker 通知控件或视图模型状态的变化。
  • SwingWorker 甚至可以对模型中更改的信息使用发布/处理。

编辑
关于您的更新:

基本上所有具有长时间运行方法的低级类都会有一个 propertyChangeSupport 字段。长时间运行的方法会定期调用 firePropertyChange() 来更新方法的状态,而不必报告属性的变化——这就是我所说的语义滥用!。

我不建议您这样做。理解如果被监听的绑定属性没有改变,即使调用 firePC() 也不会通知任何 PropertyChangeListeners (PCL)。如果您需要轮询属性,那么我不会使用 PCL 来执行此操作。我会简单地对其进行轮询,可能来自被轮询的班级之外。

【讨论】:

  • +1 我喜欢这种模式。但我想知道将 PropertyChangeSupport 字段添加到模型包装的低级模块是否令人讨厌。例如,如果我有一个模型中使用的用于编码 mp3 的类,是否可以将 PropertyChangeSupport 字段添加到 Mp3Encoder 类?我知道我可以在模型中添加一个方法来添加监听器。我想只要 PropertyChangeSupport .. 得到全面支持,即 swing、swt、android 应该没问题。
  • hmmmm .. 关于您的更新 - 我已经尝试过我的方法并且它有效。我没有绑定到属性名称 - 我已经声明了一些代表主题的静态字符串,当我触发属性更改时,我为 propertyName 传递主题名称,为 oldValue 传递 null,为 newValue 参数传递一条消息。从我所见,没有什么可以强迫您使用 propertyName 它只是一个字符串。我想按照它应该使用的方式使用这个系统,我可以只拥有一个更新的进度属性..
  • @volting 不太明白谁在监听 propertyChanges
  • @kleopatra - 模型监听低级对象中的方法产生的事件。模型将根据控制器将要监听的事件生成事件。希望这是有道理的。 - 查看我更新的答案 - 我希望它“可能”现在更有意义。
  • @volting hmmm ... SwingWorker 在哪里发挥作用?在监听模型或根本没有工作人员的控制器中,控制器只是在接收事件时拉取数据并使用 invokeLater 将其推送到视图?
【解决方案2】:

就个人而言,在我的SwingWorker 中,我会创建一个公共publish 方法,并将我的SwingWorker 的实例传递给长期运行的模型方法。这样,模型会将更新推送到控件 (SwingWorker),然后再推送到视图。

这是一个例子 - 我把所有东西都放在一个文件中(为了运行简单),但我想通常你会有单独的文件/包来存放这些东西。

编辑

要将模型与控件分离,您必须拥有模型的观察者。我会实现一个ProgressListener 继承ActionListener。该模型只是通知所有注册的ProgressListener 已经取得了进展。

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

public class MVCSwingWorkerExample {

    public static void main(String[] args) {
        CalcModel      model      = new CalcModel();
        CalcView       view       = new CalcView();
        CalcController controller = new CalcController(model, view);
    }

    //Model class - contains long running methods ;)
    public static class CalcModel{

        //Contains registered progress listeners
        ArrayList<ActionListener> progressListeners = new ArrayList<ActionListener>();
        //Contains model's current progress
        public int status;

        //Takes in an instance of my control's Swing Worker
        public boolean longRunningProcess(MVCSwingWorkerExample.CalcController.Worker w){
            for(int i = 0; i < 60; i++){
                try {
                    //Silly calculation to publish some values
                    reportProgress( i==0 ? 0 : i*100/60);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("Whowsa!");
                    e.printStackTrace();
                }
            }
            return true;
        }

        //Notify all listeners that progress was made
        private void reportProgress(int i){
            status = i;
            ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_FIRST, null);
            for(ActionListener l : progressListeners){
                l.actionPerformed(e);
            }
        }

        //Standard registering of the listeners
        public void addProgressListener(ActionListener l){
            progressListeners.add(l);
        }

        //Standard de-registering of the listeners
        public void removeProgressListener(ActionListener l){
            progressListeners.remove(l);
        }
    }

    //View Class - pretty bare bones (only contains view stuff)
    public static class CalcView{
        Box display;
        JButton actionButton;
        JLabel progress;

        public void buildDisplay(){
            display = Box.createVerticalBox();
            actionButton = new JButton("Press me!");
            display.add(actionButton);

            progress = new JLabel("Progress:");
            display.add(progress);
        }

        public void start(){
            final JFrame frame = new JFrame();
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(display);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    }

    public static class CalcController{
        CalcModel model;
        CalcView view;

        public CalcController(CalcModel model, CalcView view){
            this.model = model;
            this.view = view;

            //Build the view
            view.buildDisplay();

            //Create an action to add to our view's button (running the swing worker)
            ActionListener buttonAction = new ActionListener(){
                @Override
                public void actionPerformed(ActionEvent e) {
                    Worker w = new Worker();
                    w.execute();
                }
            };
            view.actionButton.addActionListener(buttonAction);

            //Start up the view
            view.start();

        }

        //Notified when the Model updates it's status
        public class ProgressListener implements ActionListener{
            Worker w;

            public ProgressListener(Worker w){
                this.w = w;
            }

            @Override
            public void actionPerformed(ActionEvent e) {
                CalcModel model = (CalcModel)e.getSource();
                w.publishValue(model.status);
            }

        }


        //The worker - usually part of the control
        public class Worker extends SwingWorker<Boolean, Integer>{

            public Worker(){
                //Register a listener to pay attention to the model's status
                CalcController.this.model.addProgressListener(new ProgressListener(this));
            }

            @Override
            protected Boolean doInBackground() throws Exception {
                //Call the model, and pass in this swing worker (so the model can publish updates)
                return model.longRunningProcess(this);
            }

            //Expose a method to publish results
            public void publishValue(int i){
                publish(i);
            }

              @Override
              protected void process(java.util.List<Integer> chunks){
                  view.progress.setText("Progress:" + chunks.get(chunks.size()-1) + "%");
              }

             @Override
               protected void done() {
                   try {
                       view.progress.setText("Done");
                   } catch (Exception ignore) {
                   }
               }
        }
    }

}

【讨论】:

  • 我已经认为这是一种不这样做的方法。首先,它将模型耦合到控制器 - 其次 - 如果 model.doLongProcess() 调用一个名为 foo.doLongProcess() 的方法会怎样 - 我们必须将一个对 swingWorker 的引用传递到堆栈中......
  • @volting ... 很公平。为了将模型与控件解耦,您需要为模型创建一个观察者。我建议使用ActionListener 的扩展。我已更新示例以反映您的请求。
  • 为什么还要回调?为什么不让模型在 doLongRunningTask 中返回中间块,让工作人员坐下来等待下一个 - 类似于 jdk7 中的 nio WatchService? @volting 感觉我还是错过了什么……
【解决方案3】:

对于在 Swing 下长时间运行的进程,您必须为此创建一个新线程,因此当此过程完成后,您必须在“Swing 线程”中更新您的 MVC,记住每个应用程序只有一个。

尝试找到一种方法让您的应用程序正在处理的用户知道,并且在完成之前不要让他再次“倍增”。

public class CalcController {

////////////////////////////////////////// inner class MultiplyListener
/**
 * When a mulitplication is requested. 1. Get the user input number from the
 * View. 2. Call the model to mulitply by this number. 3. Get the result
 * from the Model. 4. Tell the View to display the result. If there was an
 * error, tell the View to display it.
 */
class MultiplyListener implements ActionListener {

    public void actionPerformed(ActionEvent e) {
        final String userInput = m_view.getUserInput();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    m_model.multiplyBy(userInput);
                } catch (NumberFormatException nfex) {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            m_view.showError("Bad input: '" + userInput + "'");
                        }
                    });
                }
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        m_view.setTotal(m_model.getValue());
                    }
                });
            }
        }).start();

    }
}//end inner class MultiplyListener

}

【讨论】:

    猜你喜欢
    • 2012-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-18
    • 2016-02-15
    相关资源
    最近更新 更多