如何从no-gui类实时更新Java Jframe控件

时间:2018-10-11 14:09:47

标签: java swing asynchronous model-view-controller jframe

我的Java JFrame项目有一个单调乏味的问题。

我想要做的(并寻找如何做的)是在不冻结我的应用程序的情况下,实时地从无GUI类向ListBox中添加元素,换句话说就是“异步”。这清楚吗?我尝试了SwingWorker和Threads,但没有结果。我所能做的就是在所有过程完成后更新列表框(显然,我的应用程序冻结了,因为我的过程很长)。

这是我的体系结构:

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 bussines.MyBussines;
import util.MyLog;
import view.MyView;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MyController {

    MyLog log;
    MyBussines bussines;
    MyView view;

    public MyController(){
        log = new MyLog();
        bussines = new MyBussines(log.getLog());
        view = new MyView(log.getLog());
    }

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

商务

package bussines;

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

public class MyBussines {

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

    public MyBussines(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 :(得分:1)

首先是一些规则和建议:

  • 您的模型或此处的“业务”代码应该不了解GUI,并且不应依赖于GUI结构
  • 再次在事件线程上调用所有Swing代码,并在后台线程内调用所有长时间运行的代码
  • 如果您需要长时间运行的代码会更改状态,并且此状态需要反映在GUI中,这意味着您将需要某种回调机制,该模型以某种方式通知重要方它的状态已经改变。可以使用PropertyChangeListeners或通过向模型中注入某种回调方法来实现。
  • 使视图变得愚蠢。它从用户那里获取输入并通知控制器,并由控制器进行更新。几乎所有程序“大脑”都驻留在控制器和模型中。例外-某些输入验证通常是在视图中完成的。
  • 不要忽略Java OOP的基本规则-通过将字段保持私有状态来隐藏信息,并允许外部类仅通过完全控制的公共方法来更新状态。
  • 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);
    }
}

答案 1 :(得分:1)

这是一个长过程的过度简化的示例,该过程会生成String值,并使用SwingWorker来更新GUI。
为了简化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();
        MyBussines bussines = new MyBussines(model.getModel());
        MyView view = new MyView(model.getModel());
        view .addButtonListener(e -> bussines.start());
    }
}

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

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

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