6

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

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

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

  • 我知道我可以通过使用该方法来取回数据,SwingWorkers publish()但这我认为这迫使我doLongProcess()doInBackground().


作为参考 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 来表示一个 topicname
  • 旧值 =null
  • newValue = 我要广播的消息

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

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


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

4

3 回答 3

4

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

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

编辑
关于您的更新:

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

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

于 2012-10-06T12:15:47.560 回答
1

就我个人而言,SwingWorker我会创建一个公共publish方法,并将我的实例传递SwingWorker给长期运行的 Model 方法。这样,模型将更新推送到控件 ( 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) {
                   }
               }
        }
    }

}
于 2012-10-08T22:20:28.147 回答
0

对于 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-10-15T16:07:34.147 回答