MVC - 具有长时间运行进程的SwingWorker,应该更新视图

时间:2012-10-06 12:04:22

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

如何在使用具有长时间运行流程的SwingWorker时将View与模型分离,该流程应将更新发送回控制器?

  • 我可以使用SwingWorkers doInBackground()通过调用例如model.doLongProcess()来保持EDT响应!

  • 我遇到的问题是在流程完成之前尝试获取数据,以便根据进度更新视图。

  • 我知道我可以通过使用SwingWorkers publish()方法来获取数据,但我想这会迫使我在doLongProcess()方法中编写doInBackground()方法的代码{1}}。


作为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);
    }
}

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

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


更新:

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

基本上所有具有长时间运行方法的低级类都将具有propertyChangeSupport字段。长时间运行的方法定期调用firePropertyChange()以更新方法的状态,而不一定报告属性的更改 - 这就是语义滥用的意思!。

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

修改:

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

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

然后是模型中的PropertyChangeListener或哪里可以根据topicname识别消息。

所以我基本上把系统弯曲成像发布 - 订阅......


我想代替上面的方法,我可以将一个进度字段添加到更新的低级类,然后基于那个firePropertyChange ......这将与它应该如何使用的方式一致。

3 个答案:

答案 0 :(得分:4)

我认为发布/进程对是将数据从SwingWorker推送到GUI中。另一种传递信息的方法是使用PropertyChangeSupport和PropertyChangeListeners将GUI或控件拉出信息从SwingWorker中提取出来。考虑

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

修改
关于您的更新:

  

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

我不建议您这样做。理解如果被侦听的绑定属性没有改变,即使调用了firePC(),也不会通知任何PropertyChangeListeners(PCL)。如果您需要轮询属性,那么我不会使用PCL来执行此操作。我只是轮询它,可能来自被轮询的类之外。

答案 1 :(得分:1)

就我个人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) {
                   }
               }
        }
    }

}

答案 2 :(得分:0)

对于Swing下的长时间运行进程,你必须为此创建一个新的Thread,所以当这个进程完成后,你必须在“Swing thread”中更新你的MVC,记住每个应用程序只有一个。 / p>

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

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

}