How to update Java Jframe controls from no-gui class on real time

半城伤御伤魂 提交于 2019-12-02 18:15:31

问题


I have a tedious problem with my Java JFrame project.

What I want to do (and looking for how to) is add elements to my ListBox from a no-GUI class in REAL TIME, or in other words "asynchronous", with out freezing my app. Is this clear? I tried SwingWorker and Threads but without results. All I can do is update the listbox after all process finish (obviously with my app froze because my process is long).

This is my architecture:

And here is my code (not functional, just for understanding)

EDITTED

View (Generated with 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                   
}

Controller

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

Business

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

}

Log (model)

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

}

回答1:


This is an over-simplified example of a long-process generating String values that update GUI using a SwingWorker.

It is over simplified in an attempt to make the basic use of a SwingWorker somewhat easier to follow.

This is a one-file mcve, meaning you can copy-paste the entire code into one file (MyView.java) and execute :

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



回答2:


First a few rules and suggestions:

  • Your model or here your "business" code should have no knowledge of the GUI, and should not depend on the GUI structure
  • Again all Swing code should be called on the event thread, and all long-running code within a background thread
  • If you have long running code that changes state, and if this state needs to be reflected in the GUI, this means that you'll need a call-back mechanism of some sort, some way for the model to notify important parties that its state has changed. This can be done using PropertyChangeListeners, or by injecting some type of callback method into the model.
  • Make the view dumb. It gets input from user and notifies the controller, and it is updated by the controller. Almost all program "brains" resides within the controller and the model. Exceptions -- some input verification is often done in the view.
  • Don't ignore basic Java OOPs rules -- Hide information by keeping fields private, and allowing outside classes to update state only through public methods that you fully control.
  • The MCVE structure is a good one to learn and use, even if not asking a question here. Learn to simplify your code and to isolate your problem in order to best solve it.

For example this code can be copied and pasted into a single file within the IDE of choice and then run:

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

}


Another way to do the same thing is to use a Swing-compliant PropertyChangeSupport and PropertyChangeListeners. For example:

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


来源:https://stackoverflow.com/questions/52762220/how-to-update-java-jframe-controls-from-no-gui-class-on-real-time

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!