GUI和主线程交互

时间:2019-01-14 21:55:35

标签: java multithreading swing concurrency

我正在寻求有关程序设计的一些常规建议。我已经对此发表了意见,但是这个问题由于不清楚而被搁置了,所以我再说一遍。我有一个摆动的GUI,但在后台正在进行大量处理,基本上是在更新我拥有的对象的数组列表。根据对我以前的帖子的有帮助的回复,我得到了类似的东西:

class GUI extends JFrame {
    menu, various displays, buttons…

    public GUI(CompletableFuture<GUI> result) {
        this.add(jButton, null);
        …
        result.complete(this);
    }

    public void updateGraphics(MyObj o) {
        update graphics with updated object…
    }

}

public static void main (String[]args) throws Exception {
    ArrayList<MyObject> MyList = new ArrayList<>();
    CompletableFuture<GUI> result = new CompletableFuture();
    EventQueue.invokeLater(() -> new GUI(result));
    GUI gui = result.get();
    // main processing, can go on for hours
    // continually updating arraylist of MyObjects
    // pass computed values to GUI as they are updated
    EventQueue.invokeLater(() -> gui.updateGraphics (MyList.get(1)));
}

我希望这是有道理的。我的问题是,GUI如何与arraylist交互?例如,可能有一个删除第三个MyObject的按钮。我是否需要在开始时传递对arraylist的引用,而不是:

EventQueue.invokeLater(() -> new GUI(result));

应该是:

EventQueue.invokeLater(() -> new GUI(result, MyList));

因此,GUI可以从MyList中删除... ???也许GUI需要在main中调用在主线程上处理的方法?

预先感谢您的帮助

2 个答案:

答案 0 :(得分:0)

首先,删除所有CompleteableFuture代码。

首先,您想在EventDispatch线程上显示GUI,即JFrame。所以,

Runnable runnable = new Runnable() {
    public void run() {
        new GUI().setVisible(true);
    }
}
EventQueue.invokeLater(runnable);

这将导致您的JFrame显示。

您的myList是main()方法中的局部变量。而是创建一个模型类来保存您的变量。对于简单的应用程序,您无需担心同步,只要您在事件分发线程上同步完成所有工作即可,这是默认行为。

最后,您需要一个将类和GUI类联系在一起的控制器类。此类将侦听器添加到GUI类,并处理逻辑以更新模型并将模型中的值传递回GUI类。

一旦您有了基本的MVC类并且您的应用程序正常运行,您就可以使用线程从EDT上卸载工作。除非完成工作需要“很长时间”,否则这不是必不可少的,在这种情况下,您的UI会变得呆滞。

这里的关键是在完成工作之后,向EventQueue.invokeLater()提供另一个Runnable来更新UI。这样可以确保UI中完成的所有工作都由一个线程完成。

答案 1 :(得分:0)

以下代码实现了Model-View-Controller模式。它是一个文件SSCCE:可以复制粘贴到MVC.java中并运行
看法就是这样。它使用Observer界面侦听Model中的更改。
该模型封装视图所需的信息(在这种情况下为ArrayList),并在信息更改时通知它。
Worker类使用线程来更改Model中的信息。
Controller协调各种成员:初始化它们,并将视图链接到模型:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagLayout;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class MVC {

    public static void main(String[] args) {
        new Controller();
    }
}

//Controller of the MVC pattern."wires" model and view (and in this case also worker)
class Controller{

    public Controller() {

        Model model = new Model();
        View view = new View(model);
        model.registerObserver(view); //register view as an observer to model
        intitialize(model);
        Worker worker = new Worker(model);

        view.getClearBtn().addActionListener(e -> model.fill(0.));
        view.getStopBtn().addActionListener(e -> worker.cancel());
    }

    //add some initial values to model
    private void intitialize(Model model) {
        IntStream.range(0, 1000).forEach(i-> model.addElement(0));
    }
}

//view of the MVC pattern. Implements observer to respond to model changes
class View implements Observer{

    private final Model model;
    private final SumPane pane;
    private final JButton clearBtn, stopBtn;

    public View(Model model) {

        this.model = model;
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //used to simulate doing some changes in the array list
        clearBtn = new JButton("Set Values to 0");
        frame.add(clearBtn, BorderLayout.NORTH);

        pane = new SumPane();
        frame.add(pane, BorderLayout.CENTER);

        stopBtn = new JButton("Stop");
        frame.add(stopBtn, BorderLayout.SOUTH);

        frame.pack();
        frame.setVisible(true);
    }

    JButton getClearBtn() { return clearBtn;}
    JButton getStopBtn()  { return stopBtn; }

    @Override
    public void onObservableChanged() { //update text in response to change in model
        pane.setText(String.format("%.2f",model.sum()));
    }

    class SumPane extends JPanel {

        private final JLabel label;
        SumPane() {
            setPreferredSize(new Dimension(200, 100));
            setLayout(new GridBagLayout());
            label = new JLabel(" ");
            add(label);
        }

        void setText(String text){  label.setText(text); }
    }
}

//Model of the MVC pattern. Holds the information view needs
//Notifies observers (in this case View) when model changes
class Model { //you can make it generic Model<T>

    //Collection of the info that need to be processed
    private final List<Double> list;

    // thread safe set for observers
    private final Set<Observer> mObservers = Collections.newSetFromMap(
                                        new ConcurrentHashMap<Observer, Boolean>(0));
    Model() {

        //thread safe list. alternatively use FXCollections.observableArrayList
        //and synchronize
        list = new CopyOnWriteArrayList<>();
    }

    Iterator<Double> getIterator(){
        return list.iterator();
    }

    //set all elements to value
    void fill(Double value){
        IntStream.range(0, getLength()).forEach(i ->setElement(i,value));
        notifyObservers();
    }

    //return sum of all values
    double sum(){
        return list.stream().mapToDouble(d -> d.doubleValue()).sum();
    }

    int getLength(){return list.size(); }

    //May throw IndexOutOfBoundsException
    Double getElement(int index){
        Double element = list.get(index);
        notifyObservers();
        return element;
    }

    void setElement(int index, double element){
        list.set(index, element);
        notifyObservers();
    }

    void addElement(double element){
        list.add(element);
        notifyObservers();
    }

    //Returns:the element previously at index.
    //May throw IndexOutOfBoundsException
    Double removeElement(int index){
        Double element = list.remove(index);
        notifyObservers();
        return element;
    }

    //-- handle observers

    // add new Observer - it will be notified when Observable changes
    public void registerObserver(Observer observer) {
        if (observer != null) {
            mObservers.add(observer);
        }
    }

    //remove an Observer
    public void unregisterObserver(Observer observer) {
        if (observer != null) {
            mObservers.remove(observer);
        }
    }

    //notifies registered observers
    private void notifyObservers() {
        for (Observer observer : mObservers) {
            observer.onObservableChanged();
        }
    }
}

//Interface implemented by View and used by Model
interface Observer {
    void onObservableChanged();
}

//Encapsulates thread that does some work on model
class Worker implements Runnable{

    private final Model model;
    private boolean cancel = false;
    private final Random rnd = new Random();

    public Worker(Model model) {
        this.model = model;
        new Thread(this).start();
    }

    @Override
    public void run() {
        while(! cancel){
            model.fill(rnd.nextDouble()* Math.PI);
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException ex) { ex.printStackTrace();   }
        }
    }

    void cancel() { cancel = true;  }
}