Swing线程安全中的MVC模式

16

我正在尝试在Swing中触摸MVC架构的极限,但是我尝试了所有方法(从 SwingWorker Runnable#Thread ),都是在EDT上完成的。

我的问题:

  • 是否存在某些限制或严格依赖于实现的顺序(包装在 SwingWorker Runnable#Thread 中)?

  • 如果JComponent#method是线程安全的或不安全的,则有限制吗?

  • Swing中MVC架构的基本特征是什么?

  • 包括容器重新布局吗?

注意:对于我的 SSCCE ,我采用了 HFOE 的一个示例,也许通过严格遵守这些原则,不可能创建任何EDT缺乏或GUI冻结

enter image description here enter image description here enter image description here enter image description here

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;
    }
}

4
很难理解你的问题,我建议你对其进行一些梳理,但简短的答案是不,按设计而言Swing不是线程安全的。我认为你已经知道这一点,所以我不确定实际问题是什么。 - Abdullah Jibaly
1
@mKorbel:您的问题不是很清楚:它执行了什么不正确的操作?我们如何看到应该看到的“滞后”(是否应该)?我运行了代码,发现有些问题(在前两次运行期间,我可以单击按钮,然后在进度正在运行时它会变为灰色,但最终我可以单击并启动进度,但仍然可以访问按钮[Debian/Linux]),但我怀疑这不是您抱怨的问题。 - TacticalCoder
1
@mKorbel:你在不应该的时候修改了EDT上的东西。当你写下:“因为我尝试了所有(从SwingWorker或Runnable#Thread)都在EDT上完成”时,难道你没有搞反吗?SwingWorker 的重点是在EDT之外执行长时间操作。我对你的那句话感到困惑,需要更多信息 :) - TacticalCoder
你是否在询问为什么这个(故意)不正确的程序没有更可靠地失败,对比String[] petStringsQueue<Icon> - trashgod
1个回答

15

这段内容太长了,不适合用评论来表达...

首先,这与本回答的其余部分无关:有许多不同的MVC实现,而您在此处发布的代码片段所使用的并不是http://www.oracle.com/technetwork/articles/javase/mvc-136693.html文章中使用的那种。

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

这并没有什么不对:实际上有很多不同类型的MVC实现(*)。

(另一个小注意点...您的示例中视图知道控制器这一点有点奇怪:有其他方法可以做到您正在做的事情,而不需要像您在中使用那样向视图“喂”控制器。)

总之...您基本上几乎总是从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 worker的重点)。

我更愿意从EDT更新GUI,可以像这样进行操作:

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

HVMC/PAC 是我最喜欢的 MVC 类型,但我岔开话题了 ;) - TacticalCoder
+1,1)同意还有其他MVC模型,我遵循了我看到的更好的(由Swing Guru - HFOE提供),2)正如我所说 - 但是我尝试了所有东西都在EDT上完成,3)从Java1.6.021开始,SwingWorker更加稳定,但(所有E.T.错误仍然在BugsDB中http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6826514和另一个),如果您覆盖https://dev59.com/COo6XIcBkEYKwwoYSClJ,则done()的输出始终在EDT中,那么没有理由进行测试 :-) - mKorbel
@user988052 嗯,需要将 Substance L&F 包装输出到 invokeLater 中。 - mKorbel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接