GUI и основные потоки взаимодействуют - PullRequest
0 голосов
/ 15 января 2019

Мне нужен общий совет по разработке программы. Я уже писал об этом, но вопрос был отложен, так как был немного неясен, так что у меня есть еще один вариант. У меня есть свинг-графический интерфейс, но на заднем плане идет интенсивная обработка, в основном, обновление массива объектов, которые я получил. Основываясь на полезном ответе на мой предыдущий пост, я получил что-то вроде:

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 взаимодействует с массивом? Например, там может быть кнопка для удаления 3-го MyObject. Нужно ли в начале передать ссылку на arraylist, вместо:

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

Было бы:

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

Так что тогда GUI сможет удалить из MyList ... ??? Может быть, графический интерфейс должен вызывать метод main, который обрабатывает основной поток?

Заранее спасибо за помощь

Ответы [ 2 ]

0 голосов
/ 16 января 2019

Следующий код реализует шаблон Model-View-Controller. Это один файл SSCCE : его можно скопировать в MVC.java и запустить
Точка зрения это просто так. Он слушает изменения в модели с использованием интерфейса Observer.
Модель инкапсулирует информацию, которая необходима представлению (в данном случае 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;  }
}
0 голосов
/ 15 января 2019

Сначала удалите весь код CompleteableFuture.

Для запусков вы хотите отобразить GUI, который является вашим JFrame, в потоке EventDispatch. Таким образом,

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

Это приведет к отображению вашего JFrame.

Ваш myList является локальной переменной в методе main (). Вместо этого создайте класс модели для хранения ваших переменных. Для простого приложения вам не нужно беспокоиться о синхронизации, если вы выполняете всю свою работу синхронно в потоке диспетчеризации событий, что является поведением по умолчанию.

Наконец, вам нужен класс контроллера, который связывает вашу модель и классы GUI вместе. Этот класс добавляет слушатели к классу GUI и обрабатывает вашу логику для обновления вашей модели и передачи значений из модели обратно в класс GUI.

Как только у вас есть базовые классы MVC и ваше приложение работает, вы можете использовать потоки для выгрузки работы из EDT. Это не обязательно, если выполняемая работа не занимает «много времени», и в этом случае ваш пользовательский интерфейс станет вялым.

Здесь ключ заключается в том, чтобы после выполнения работы предоставить еще один Runnable в EventQueue.invokeLater () для обновления пользовательского интерфейса. Это гарантирует, что вся работа, выполняемая в пользовательском интерфейсе, выполняется одним потоком.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...