【问题标题】:How to update Java Jframe controls from no-gui class on real time如何从无 gui 类实时更新 Java Jframe 控件
【发布时间】:2018-10-11 14:09:47
【问题描述】:

我的 Java JFrame 项目遇到了一个繁琐的问题。

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

这是我的架构:

这是我的代码(不是功能性的,只是为了理解)

已编辑

视图(使用 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;
    }

}

【问题讨论】:

  • "I tried SwingWorker and Threads..." -- 实际上 正确的解决方案。 "... but without results. All I can do is update the listbox after all process finish"——这意味着你做错了什么,你的代码中有一个错误,很可能你的代码没有适当地遵守 Swing 线程规则。让我们充分了解您的问题的最佳方法是,如果我们能够重现您的问题,并提供有效的minimal reproducible example 代码帖子,其中包括您的 SwingWorker 尝试。
  • 我已经编辑了你的标签——最好使用更具体的swing标签,而不是过于宽泛的user-interface标签。
  • 所以我现在可以给你的唯一答案是——是的,使用你提到的 SwingWorker,并努力在其 doInBackground 方法中进行所有长时间运行的调用,并且没有您从该方法调用的 Swing(或从该方法调用的代码)。这解决您的问题。如果您需要更具体的解决方案,请阅读上述minimal reproducible example 链接。
  • 您希望将日志添加到JList 吗?
  • 如果是这样,如果您尝试执行@c0der 上面要求的操作,那么您可能需要使用 SwingWorker 的发布/处理方法对来更新列表的模型。 再次,看到您的 mcve 将非常有帮助,其中包括您的 SwingWorker 实现。

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


【解决方案1】:

这是一个使用SwingWorker更新GUI的长过程生成String值的过度简化示例。

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

【讨论】:

  • 鉴于到目前为止的问题,我们可以提供一个很好的答案。 1+
  • 这个想法是 OP 从答案中获得一些基础知识,而不是更多 detailed and advanced 员工(0:
  • 抱歉耽搁了,我很忙....尝试重现我收到此错误:lambda expressions are not supported in -source 1.7 on view.addButtonListener(e -&gt; bussines.start()); 我使用 java 1.7,另一种方式?
  • 是的。替换为view .addButtonListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { bussines.start(); } });
  • @c0der 好的,我试图重现您的解决方案,但是当 Windows 应用程序启动时它看起来又空又短(如大小 0,0)。也许 NetBeans 自动生成的代码有问题。跨度>
【解决方案2】:

先说几条规则和建议:

  • 您的模型或此处的“业务”代码应该不了解 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 和 PropertyChangeListener。例如:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-18
    • 2012-11-12
    • 2023-04-02
    • 1970-01-01
    • 2015-09-17
    • 2015-02-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多