我的Java JFrame项目有一个单调乏味的问题。
我想要做的(并寻找如何做的)是在不冻结我的应用程序的情况下,实时地从无GUI类向ListBox
中添加元素,换句话说就是“异步”。这清楚吗?我尝试了SwingWorker
和Threads,但没有结果。我所能做的就是在所有过程完成后更新列表框(显然,我的应用程序冻结了,因为我的过程很长)。
这是我的体系结构:
这是我的代码(不起作用,只是为了理解)
已编辑
视图(由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;
}
}
答案 0 :(得分:1)
首先是一些规则和建议:
例如,可以将此代码复制并粘贴到所选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();
}
}