Как обновить элементы управления Java Jframe из класса no-gui в режиме реального времени - PullRequest
0 голосов
/ 11 октября 2018

У меня утомительная проблема с моим проектом Java JFrame.

Что я хочу сделать (и ищу, как это сделать), это добавить элементы в мой ListBox из класса без GUI в РЕАЛЬНОМ ВРЕМЕНИ,или, другими словами, «асинхронный», без остановки моего приложения.Это понятно?Я пробовал SwingWorker и темы, но безрезультатно.Все, что я могу сделать, это обновить список после завершения всего процесса (очевидно, с моим приложением, потому что мой процесс длинный).

Это моя архитектура:

Project Architecture

А вот и мой код (не функциональный, просто для понимания)

РЕДАКТИРОВАННЫЙ

Вид (сгенерированный с NetBeans)

package view;

import com.everis.ingesta.controller.MyController;
import java.awt.event.ActionListener;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;

public class MyView extends javax.swing.JFrame {

    public MyView(DefaultListModel<String> model) {
        setVisible(true);
    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        btnRun = new javax.swing.JButton();
        jscrlLog = new javax.swing.JScrollPane();
        jlstLog = new javax.swing.JList();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        btnRun.setText("Run");

        jscrlLog.setViewportView(jlstLog);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(159, 159, 159)
                .addComponent(btnRun)
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(jscrlLog, javax.swing.GroupLayout.DEFAULT_SIZE, 376, Short.MAX_VALUE)
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(btnRun)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jscrlLog, javax.swing.GroupLayout.DEFAULT_SIZE, 242, Short.MAX_VALUE)
                .addContainerGap())
        );

        pack();
    }// </editor-fold>                        

    public void addButtonListener(ActionListener listener) {
        btnRun.addActionListener(listener);
    }

    public static void main(String args[]) {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
         */
        try {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException ex) {
            java.util.logging.Logger.getLogger(MyView.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (InstantiationException ex) {
            java.util.logging.Logger.getLogger(MyView.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (IllegalAccessException ex) {
            java.util.logging.Logger.getLogger(MyView.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        } catch (javax.swing.UnsupportedLookAndFeelException ex) {
            java.util.logging.Logger.getLogger(MyView.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>
        //</editor-fold>
        //</editor-fold>
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
                new MyController();
            }
        });


    }

    // Variables declaration - do not modify                     
    private javax.swing.JButton btnRun;
    private javax.swing.JList jlstLog;
    private javax.swing.JScrollPane jscrlLog;
    // End of variables declaration                   
}

Контроллер

package controller;

import business.MyBusiness;
import util.MyLog;
import view.MyView;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MyController {

    MyLog log;
    MyBusiness business;
    MyView view;

    public MyController(){
        log = new MyLog();
        business = new MyBusiness(log.getLog());
        view = new MyView(log.getLog());
    }

    public void runProcess() {
        view.addButtonListener(new ActionListener() { 
            @Override 
            public void actionPerformed(ActionEvent e) { 
                business.runProcess();
            }}
        );
    }
}

Бизнес

package business;

import MyLog;
import java.util.List;
import javax.swing.DefaultListModel;
import javax.swing.SwingWorker;

public class MyBusiness {

    private int counter = 0;
    private DefaultListModel<String> model;
    private MyLog log;

    public MyBusiness(DefaultListModel<String> model) {
        this.model = model;a
    }

    public void runProcess() {
        SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
            @Override
            protected Void doInBackground() throws Exception {
                for (int i = 0; i < 10; i++) {
                    publish("log message number " + counter++);
                    Thread.sleep(2000);
                }

                return null;
            }

            @Override
            protected void process(List<String> chunks) {
                // this is called on the Swing event thread
                for (String text : chunks) {
                    model.addElement("");
                }
            }
        };
        worker.execute();
    }

}

Журнал (модель)

package util;

import javax.swing.DefaultListModel;

public class MyLog {

    private DefaultListModel<String> model;

    public MyLog() {
        model = new DefaultListModel<String>();
    }

    public DefaultListModel<String> getLog(){
        return model;
    }

}

Ответы [ 2 ]

0 голосов
/ 11 октября 2018

Это слишком упрощенный пример длинного процесса, генерирующего String значения, которые обновляют графический интерфейс с использованием SwingWorker.

. Это слишком упрощено в попытке сделать базовое использование SwingWorker несколько легче следовать.

Это один файл mcve , то есть вы можете скопировать и вставить весь код в один файл (MyView.java) и выполнить:

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionListener;
import java.util.List;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.SwingWorker;

//gui only, unaware of logic 
public class MyView extends JFrame {

    private JList<String> loglist;
    private JButton log;

    public MyView(DefaultListModel<String> model) {

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        log = new JButton("Start Process");
        add(log, BorderLayout.PAGE_START);
        loglist = new JList<>(model);
        loglist.setPreferredSize(new Dimension(200,300));

        add(loglist, BorderLayout.PAGE_END);
        pack();
        setVisible(true);
    }

    void addButtonListener(ActionListener listener) {
        log.addActionListener(listener);
    }

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

//represents the data (and some times logic) used by GUI
class Model {

    private DefaultListModel<String> model;

    Model() {

        model = new DefaultListModel<>();
        model.addElement("No logs yet");
    }

    DefaultListModel<String> getModel(){
        return model;
    }
}

//"wires" the GUI, model and business process 
class MyController {

    MyController(){
        Model model = new Model();
        MyBusiness business = new MyBusiness(model.getModel());
        MyView view = new MyView(model.getModel());
        view .addButtonListener(e -> business.start());
    }
}

//represents long process that needs to update GUI
class MyBusiness extends SwingWorker<Void, String>{

    private static final int NUMBER_OF_LOGS = 9;
    private int counter = 0;
    private DefaultListModel<String> model;

    public MyBusiness(DefaultListModel<String> model) {
        this.model= model;
    }

    @Override  //simulate long process 
    protected Void doInBackground() throws Exception {

        for(int i = 0; i < NUMBER_OF_LOGS; i++) {

            //Successive calls to publish are coalesced into a java.util.List, 
            //which is by process.
            publish("log message number " + counter++);
            Thread.sleep(1000);
        }

        return null;
    }

    @Override
    protected void process(List<String> logsList) {
        //process the list received from publish
        for(String element : logsList) {
            model.addElement(element);
        }
    }

    void start() { 
        model.clear(); //clear initial model content 
        super.execute();
    }
}
0 голосов
/ 11 октября 2018

Сначала несколько правил и предложений:

  • Ваша модель или здесь ваш "бизнес" код не должны знать GUI и не должны зависеть от структуры GUI
  • Снова весь код Swing должен быть вызван в потоке событий, а также весь долгосрочный код в фоновом потоке
  • Если у вас есть долгосрочный код, который изменяет состояние, и если это состояние необходимо отразить в GUI,это означает, что вам понадобится какой-то механизм обратного вызова, чтобы модель могла уведомить важные стороны об изменении своего состояния.Это может быть сделано с помощью PropertyChangeListeners или путем введения некоторого типа метода обратного вызова в модель.
  • Сделайте представление немым.Он получает ввод от пользователя и уведомляет контроллер, и он обновляется контроллером.Почти все программные «мозги» находятся внутри контроллера и модели.Исключения - некоторая проверка ввода часто выполняется в представлении.
  • Не игнорируйте основные правила ООП Java - скрывайте информацию, оставляя поля закрытыми и позволяя внешним классам обновлять состояние только с помощью открытых методов, которые вы полностьюконтроль.
  • Структура MCVE удобна для изучения и использования, даже если вы не задаете здесь вопрос.Научитесь упростить свой код и изолировать свою проблему, чтобы лучше ее решить.

Например, этот код можно скопировать и вставить в один файл в выбранной IDE, а затем выполнить:

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.swing.*;

public class Mcve1 {
    private static void createAndShowGui() {
        // create your model/view/controller and hook them together
        MyBusiness1 model = new MyBusiness1();
        MyView1 myView = new MyView1();
        new MyController1(model, myView);  // the "hooking" occurs here

        // create and start the GUI
        JFrame frame = new JFrame("MCVE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(myView);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        // start GUI on Swing thread
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

@SuppressWarnings("serial")
class MyView1 extends JPanel {
    private MyController1 controller;
    private DefaultListModel<String> logListModel = new DefaultListModel<>();
    private JList<String> logList = new JList<>(logListModel);

    public MyView1() {
        logList.setFocusable(false);
        logList.setPrototypeCellValue("abcdefghijklabcdefghijklabcdefghijklabcdefghijkl");
        logList.setVisibleRowCount(15);
        add(new JScrollPane(logList, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED));

        // my view's buttons just notify the controller that they've been pushed
        // that's it
        add(new JButton(new AbstractAction("Do stuff") {
            {
                putValue(MNEMONIC_KEY, KeyEvent.VK_D);
            }

            @Override
            public void actionPerformed(ActionEvent evt) {
                if (controller != null) {
                    controller.doStuff(); // notification done here
                }
            }
        }));
    }

    public void setController(MyController1 controller) {
        this.controller = controller;
    }

    // public method to allow controller to update state
    public void updateList(String newValue) {
        logListModel.addElement(newValue);
    }
}

class MyController1 {
    private MyBusiness1 myBusiness;
    private MyView1 myView;

    // hook up concerns
    public MyController1(MyBusiness1 myBusiness, MyView1 myView) {
        this.myBusiness = myBusiness;
        this.myView = myView;
        myView.setController(this);
    }

    public void doStuff() {
        // long running code called within the worker's doInBackground method
        SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
            @Override
            protected Void doInBackground() throws Exception {
                // pass a call-back method into the method
                // so that this worker is notified of changes
                myBusiness.longRunningCode(new Consumer<String>() {                    
                    // call back code
                    @Override
                    public void accept(String text) {
                        publish(text); // publish to the process method
                    }
                });
                return null;
            }

            @Override
            protected void process(List<String> chunks) {
                // this is called on the Swing event thread
                for (String text : chunks) {
                    myView.updateList(text);
                }
            }
        };
        worker.execute();

    }

}

class MyBusiness1 {
    private Random random = new Random();
    private String text;

    public void longRunningCode(Consumer<String> consumer) throws InterruptedException {
        consumer.accept("Starting");
        // mimic long-running code
        int sleepTime = 500 + random.nextInt(2 * 3000);
        TimeUnit.MILLISECONDS.sleep(sleepTime);
        consumer.accept("This is message for initial process");

        // ...
        // Doing another long process and then print log
        // ...
        sleepTime = 500 + random.nextInt(2 * 3000);
        TimeUnit.MILLISECONDS.sleep(sleepTime);
        consumer.accept("This is not complete. Review");

        // ...
        // Doing another long process and then print log
        // ...
        sleepTime = 500 + random.nextInt(2 * 3000);
        TimeUnit.MILLISECONDS.sleep(sleepTime);
        consumer.accept("Ok this works. Have fun");
    }

    public String getText() {
        return text;
    }

}


Другой способ сделать то же самое - использовать Swing-совместимые PropertyChangeSupport и PropertyChangeListeners.Например:

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.*;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;

public class Mcve2 {
    private static void createAndShowGui() {
        MyBusiness2 myBusiness = new MyBusiness2();
        MyView2 myView = new MyView2();
        new MyController2(myBusiness, myView);

        JFrame frame = new JFrame("MCVE");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(myView);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

@SuppressWarnings("serial")
class MyView2 extends JPanel {
    private MyController2 controller;
    private DefaultListModel<String> logListModel = new DefaultListModel<>();
    private JList<String> logList = new JList<>(logListModel);

    public MyView2() {
        logList.setFocusable(false);
        logList.setPrototypeCellValue("abcdefghijklabcdefghijklabcdefghijklabcdefghijkl");
        logList.setVisibleRowCount(15);
        add(new JScrollPane(logList, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED));
        add(new JButton(new AbstractAction("Do stuff") {
            {
                putValue(MNEMONIC_KEY, KeyEvent.VK_D);
            }

            @Override
            public void actionPerformed(ActionEvent evt) {
                if (controller != null) {
                    controller.doStuff();
                }
            }
        }));
    }
    public void setController(MyController2 controller) {
        this.controller = controller;
    }
    public void updateList(String newValue) {
        logListModel.addElement(newValue);
    }
}

class MyController2 {

    private MyBusiness2 myBusiness;
    private MyView2 myView;

    public MyController2(MyBusiness2 myBusiness, MyView2 myView) {
        this.myBusiness = myBusiness;
        this.myView = myView;
        myView.setController(this);

        myBusiness.addPropertyChangeListener(MyBusiness2.TEXT, new TextListener());
    }

    public void doStuff() {
        new Thread(() -> {
            try {
                myBusiness.longRunningCode();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }) .start();
    }

    private class TextListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            String newValue = (String) evt.getNewValue();
            myView.updateList(newValue);
        }
    }

}

class MyBusiness2 {
    public static final String TEXT = "text";
    private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
    private Random random = new Random();
    private String text;

    public void longRunningCode() throws InterruptedException {
        setText("Starting");
        // mimic long-running code
        int sleepTime = 500 + random.nextInt(2 * 3000);
        TimeUnit.MILLISECONDS.sleep(sleepTime);
        setText("This is message for initial process");

        // ...
        // Doing another long process and then print log
        // ...
        sleepTime = 500 + random.nextInt(2 * 3000);
        TimeUnit.MILLISECONDS.sleep(sleepTime);
        setText("This is not complete. Review");

        // ...
        // Doing another long process and then print log
        // ...
        sleepTime = 500 + random.nextInt(2 * 3000);
        TimeUnit.MILLISECONDS.sleep(sleepTime);
        setText("Ok this works. Have fun");
    }

    public void setText(String text) {
        String oldValue = this.text;
        String newValue = text;
        this.text = text;
        pcSupport.firePropertyChange(TEXT, oldValue, newValue);
    }

    public String getText() {
        return text;
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        pcSupport.removePropertyChangeListener(listener);
    }

    public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
        pcSupport.addPropertyChangeListener(name, listener);
    }

    public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
        pcSupport.removePropertyChangeListener(name, listener);
    }
}
...