Сначала несколько правил и предложений:
- Ваша модель или здесь ваш "бизнес" код не должны знать 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);
}
}