如何在使用具有长时间运行流程的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)时;
然后是模型中的PropertyChangeListener或哪里可以根据topicname识别消息。
所以我基本上把系统弯曲成像发布 - 订阅......
我想代替上面的方法,我可以将一个进度字段添加到更新的低级类,然后基于那个firePropertyChange ......这将与它应该如何使用的方式一致。
答案 0 :(得分:4)
我认为发布/进程对是将数据从SwingWorker推送到GUI中。另一种传递信息的方法是使用PropertyChangeSupport和PropertyChangeListeners将GUI或控件拉出信息从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
}