【问题标题】:Is MVC in Swing Thread SafeSwing 线程中的 MVC 是否安全
【发布时间】:2011-12-31 11:33:44
【问题描述】:

我正在尝试在 Swing 中触及 MVC architecture 的限制,但是当我尝试所有(来自 SwingWorkerRunnable#Thread)时,都在 EDT 上完成

我的问题:

  • 是否有一些限制或严格取决于实现的顺序 (包裹在SwingWorkerRunnable#Thread 中)?

  • 受限于 JComponent#method 线程是否安全?

  • Swing 中 MVC 架构的基本特征,?

  • 公司。容器重新布局?

注意:对于我的 SSCCE,我举了 HFOE 的一个很好的例子,也许严格遵守这个原则是不可能造成任何 EDT 缺乏或 GUI 冻结

import java.awt.BorderLayout;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.LinkedList;
import java.util.Queue;
import javax.swing.*;

public class MVC_ProgressBarThread {

    private MVC_ProgressBarThread() {
        MVC_View view = new MVC_View();
        MVC_Model model = new MVC_Model();
        MVC_Control control = new MVC_Control(view, model);
        view.setControl(control);
        JFrame frame = new JFrame("MVC_ProgressBarThread");
        frame.getContentPane().add(view);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MVC_ProgressBarThread mVC_ProgressBarThread = new MVC_ProgressBarThread();
            }
        });
    }
}

class MVC_View extends JPanel {

    private static final long serialVersionUID = 1L;
    private MVC_Control control;
    private JProgressBar progressBar = new JProgressBar();
    private JButton startActionButton = new JButton("Press Me and Run this Madness");
    private JLabel myLabel = new JLabel("Nothing Special");

    public MVC_View() {
        startActionButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                buttonActionPerformed();
            }
        });
        JPanel buttonPanel = new JPanel();
        startActionButton.setFocusPainted(false);
        buttonPanel.add(startActionButton);
        setLayout(new BorderLayout(10, 10));
        add(buttonPanel, BorderLayout.NORTH);
        progressBar.setStringPainted(true);
        add(progressBar, BorderLayout.CENTER);
        myLabel.setIcon(UIManager.getIcon("OptionPane.questionIcon"));
        myLabel.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        add(myLabel, BorderLayout.SOUTH);
    }

    public void setControl(MVC_Control control) {
        this.control = control;
    }

    private void buttonActionPerformed() {
        if (control != null) {
            control.doButtonAction();
        }
    }

    public void setProgress(int progress) {
        progressBar.setValue(progress);
    }

    public void setProgressLabel(String label) {
        progressBar.setString(label);
    }

    public void setIconLabel(Icon icon) {
        myLabel.setIcon(icon);
    }

    public void start() {
        startActionButton.setEnabled(false);
    }

    public void done() {
        startActionButton.setEnabled(true);
        setProgress(100);
        setProgressLabel("   Done !!!   ");
        setIconLabel(null);
    }
}

class MVC_Control {

    private MVC_View view;
    private MVC_Model model;

    public MVC_Control(final MVC_View view, final MVC_Model model) {
        this.view = view;
        this.model = model;
        model.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            public void propertyChange(PropertyChangeEvent pce) {
                if (MVC_Model.PROGRESS.equals(pce.getPropertyName())) {
                    view.setProgress((Integer) pce.getNewValue());
                }
                if (MVC_Model.PROGRESS1.equals(pce.getPropertyName())) {
                    view.setProgressLabel((String) pce.getNewValue());
                }
                if (MVC_Model.PROGRESS2.equals(pce.getPropertyName())) {
                    view.setIconLabel((Icon) pce.getNewValue());
                }
            }
        });
    }

    public void doButtonAction() {
        view.start();
        SwingWorker<Void, Void> swingworker = new SwingWorker<Void, Void>() {

            @Override
            protected Void doInBackground() throws Exception {
                model.reset();
                model.startSearch();
                return null;
            }

            @Override
            protected void done() {
                view.done();
            }
        };
        swingworker.execute();
    }
}

class MVC_Model {

    public static final String PROGRESS = "progress";
    public static final String PROGRESS1 = "progress1";
    public static final String PROGRESS2 = "progress2";
    private static final int MAX = 11;
    private static final long SLEEP_DELAY = 1000;
    private int progress = 0;
    private String label = "Start";
    private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    private PropertyChangeSupport pcs1 = new PropertyChangeSupport(this);
    private PropertyChangeSupport pcs2 = new PropertyChangeSupport(this);
    private final String[] petStrings = {"Bird", "Cat", "Dog",
        "Rabbit", "Pig", "Fish", "Horse", "Cow", "Bee", "Skunk"};
    private int index = 1;
    private Queue<Icon> iconQueue = new LinkedList<Icon>();
    private Icon icon = (UIManager.getIcon("OptionPane.questionIcon"));

    public void setProgress(int progress) {
        int oldProgress = this.progress;
        this.progress = progress;
        PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS,
                oldProgress, progress);
        pcs.firePropertyChange(evt);
    }

    public void setProgressLabel(String label) {
        String oldString = this.label;
        this.label = label;
        PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS1,
                oldString, label);
        pcs1.firePropertyChange(evt);
    }

    public void setIconLabel(Icon icon) {
        Icon oldIcon = this.icon;
        this.icon = icon;
        PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS2,
                oldIcon, icon);
        pcs2.firePropertyChange(evt);
    }

    public void reset() {
        setProgress(0);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcs.addPropertyChangeListener(listener);
        pcs1.addPropertyChangeListener(listener);
        pcs2.addPropertyChangeListener(listener);
    }

    public void startSearch() {
        iconQueue.add(UIManager.getIcon("OptionPane.errorIcon"));
        iconQueue.add(UIManager.getIcon("OptionPane.informationIcon"));
        iconQueue.add(UIManager.getIcon("OptionPane.warningIcon"));
        iconQueue.add(UIManager.getIcon("OptionPane.questionIcon"));
        for (int i = 0; i < MAX; i++) {
            int newValue = (100 * i) / MAX;
            setProgress(newValue);
            setProgressLabel(petStrings[index]);
            index = (index + 1) % petStrings.length;
            setIconLabel(nextIcon());
            try {
                Thread.sleep(SLEEP_DELAY);
            } catch (InterruptedException e) {
            }
        }
    }

    private Icon nextIcon() {
        Icon icon1 = iconQueue.peek();
        iconQueue.add(iconQueue.remove());
        return icon1;
    }
}

【问题讨论】:

  • 很难理解你的问题,我建议稍微清理一下,但简短的回答是否定的,Swing 的设计不是线程安全的。我想你已经知道了,所以我不确定实际的问题是什么。
  • @mKorbel:你的问题不是很清楚:它做了什么不正确的事情?我们如何才能看到我们应该看到的“滞后”(我们应该看到吗?)?我运行了代码,有些东西看起来坏了(在前两次运行期间,我可以单击按钮,然后在进度运行时它变灰,但最终我可以单击并启动进度,但可以访问按钮 [Debian/Linux] ) 但我怀疑这是您抱怨的问题。
  • @mKorbel:您正在修改 EDT 上的内容,而您不应该这样做。当你写的时候你没有把它倒过来:“因为我尝试了所有的事情(来自 SwingWorker 或 Runnable#Thread)都是在 EDT 上完成的” SwingWorker 的重点是在EDT之外执行冗长的操作。我对你的那句话感到困惑,需要更多信息:)
  • +1 用于对比 String[] petStringsQueue&lt;Icon&gt;。您是在问为什么这个(故意)不正确的程序不会更可靠地失败?

标签: java model-view-controller swing layout-manager jcomponent


【解决方案1】:

评论太长了...

首先,这与此答案的其余部分无关:那里有许多不同的 MVC,而您在此处发布的那段代码中使用的 MVC 与 相同您链接到的文章中使用的那个:http://www.oracle.com/technetwork/articles/javase/mvc-136693.html

文章正确地指出它只是“一种常见的 MVC 实现”(视图注册一个监听模型变化的监听器)。您的实现是一种不同类型的 MVC,其中控制器注册一个侦听器以侦听模型更改,然后更新视图。

并不是说这有什么问题:那里有 很多 不同类型的 MVC (*)。

(另一个小警告......您的视图知道您的示例中的控制器,这有点奇怪:还有其他方法可以做您正在做的事情,而无需将控制器“提供”给视图,例如您在 MVCView 中使用您的 setControl(...)。)

但无论如何...您基本上几乎总是从 EDT 外部修改 GUI(您不应该这样做):

public void setIconLabel(final Icon icon) {
   myLabel.setIcon(icon);
}

您可以通过添加以下内容来检查它:

System.out.println("Are we on the EDT? " + SwingUtilities.isEventDispatchThread());

这是因为您最终会从 SwingWorker 线程执行这些更新(SwingWorker 线程在 EDT 之外运行:它基本上是 Swing 工作线程的重点)。

我宁愿从 EDT 更新 GUI,做这样的事情:

public void setIconLabel(final Icon icon) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            myLabel.setIcon(icon);
        }
    });
}

【讨论】:

  • (*) HVMC/PAC 是我最喜欢的 MVC 类型,但我离题了; )
  • +1 表示回答,1) 同意还有其他 MVC 模型,我遵循的比我看到的更好(由 Swing Guru - HFOE 提供),2) 正如我所说 - 但是当我尝试了所有在 EDT 上完成,3) 从 Java1.6.021 开始,SwingWorker 更稳定,但是(所有 ET 错误仍然在 BugsDB bugs.sun.com/bugdatabase/view_bug.do?bug_id=6826514 和另一个中),如果你将覆盖 stackoverflow.com/questions/7053865/… ,那么 done() 的输出总是在EDT,那么没有理由进行测试:-)
  • @user988052 hmmm Substance L&F 需要将输出包装到 invokeLater,
猜你喜欢
  • 2013-08-21
  • 1970-01-01
  • 1970-01-01
  • 2014-03-31
  • 2012-08-21
  • 2010-12-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多