На шаблоне MVC, который является лучшим вариантом для Модели, чтобы уведомить Представление (если это правильный подход в первую очередь), где из всех полей данных, которые хранит Модель, обновляется только пара из них. В частности, когда мы хотим обновить только определенные поля представления.
В настоящее время я использую шаблон MVC с наблюдателем/подписчиком (JAVA Swing), как описано здесь: https://stackoverflow.com/a/6963529, но когда модель обновляется, она меняет все в представлении при вызове функции update(), невозможно определить, какое поле из модели изменилось в чтобы обновить только необходимое поле в представлении.
Я прочитал эту тему: https://softwareengineering.stackexchange.com/a/359008, а также это: https://stackoverflow.com/a/9815189, который я считаю полезным, но в дальнейшем я не очень хорошо понимаю, как я могу установить свойствоChangeListener для переменной (int, float и т. д.). Также относится к этому: https://stackoverflow.com/a/9815189
Основной класс, в котором запускается программное обеспечение:
public class Main {
public static void main(String[] args) {
Model m = new Model();
View v = new View(m);
Controller c = new Controller(m, v);
c.initController();
}
}
Итак, код, который у меня есть в модели, таков:
public class Model extends Observable {
//...
private float speed;
private int batteryPercentage;
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
this.speed = speed;
setChanged();
notifyObservers();
}
public int getBatteryPercentage() {
return batteryPercentage;
}
public void setBatteryPercentage(int batteryPercentage) {
this.batteryPercentage = batteryPercentage;
setChanged();
notifyObservers();
}
}
Представление знает Модель:
public class View implements Observer {
private Model model;
private JTextField txtFldSpeed;
private JTextField txtFldBattery;
private JFrame mainWindow;
public View(Model m) {
this.model = m;
initialize();
}
private void initialize() {
mainWindow = new JFrame();
mainWindow.setTitle("New Window");
mainWindow.setMinimumSize(new Dimension(1280, 720));
mainWindow.setBounds(100, 100, 1280, 720);
mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel tPanel1 = new JPanel();
tPanel1.setBorder(new LineBorder(new Color(0, 0, 0)));
tPanel1.setLayout(null);
mainWindow.getContentPane().add(tPanel1);
mainWindow.getContentPane().add(tPanel1);
txtFldSpeed = new JTextField();
txtFldSpeed.setEditable(false);
txtFldSpeed.setBounds(182, 11, 116, 22);
tPanel1.add(txtFldSpeed);
txtFldBattery = new JTextField();
txtFldBattery.setEditable(false);
txtFldBattery.setBounds(182, 43, 116, 22);
tPanel1.add(txtFldBattery);
mainWindow.setVisible(true);
}
@Override
public void update(Observable o, Object arg) {
txtFldSpeed.setText(Float.toString(model.getSpeed()) + " kn");
txtFldBattery.setText(Integer.toString(model.getBatteryPercentage()) + " %");
}
}
Контроллер добавляет представление в качестве наблюдателя модели:
public class Controller {
private Model model;
private View view;
public Controller(Model m, View v) {
this.model = m;
this.view = v;
}
public void initController() {
model.addObserver(view);
model.setSpeed(10);
}
}
Я ожидаю, что когда модель обновляется, скажем, вызывается функция setSpeed(), представлению сообщается, что ей нужно обновить себя в этом конкретном поле, а не в каждом «изменяемом» поле (например, в txtFldBattery.
Я хочу сделать это, потому что в представлении есть поля, которые обновляются пару раз в секунду, и поскольку мне нужно обновить все в представлении, JComboBox, который не нужно обновлять так часто, продолжает закрываться при попытке выберите параметр.
Спасибо за помощь @HovercraftFullOfEels. Я обновлю код. Я не думал, что это будет полезно.
Я не думаю, что раньше меня называли "Хо", но меня называли хуже (серьезно). Опять же, пожалуйста, улучшите свой вопрос, если это возможно.
Не ваш голосующий (пока), ожидающий вашего обновления
@HovercraftFullOfEels, извините! :-D Я пытался сослаться на вас. Я обновил пост. Я опустил некоторый код, чтобы его было легче читать, поскольку переменные довольно просты, как и имена. Спасибо.
Прочтите или перечитайте ссылку минимальный воспроизводимый пример, чтобы увидеть код, который вы должны опубликовать. Если у вас нет ответа в ближайшее время, пожалуйста, не стесняйтесь комментировать мне, как только вы разместили код, который соответствует стандартам MCVE, включая то, что он должен быть небольшим (вписывается в ваш вопрос без изменений), может быть скопирован и вставляется в нашу IDE, а затем компилируется и запускается для нас. Ссылка объяснит детали и почему эти детали важны.
@HovercraftFullOfEels, готово. Я пытался очистить код и сделать его максимально простым и компилируемым. Я думаю, что это ясно показывает, чего я пытаюсь достичь.
Спасибо за обновление, но оно по-прежнему не компилируется из-за отсутствия символов: messages параметр для вызова конструктора модели, gbc_tPanel1 поле в вызове метода и метод модели getBatteryPercentage(), вызываемый в методе обновления. Опять же, код должен компилироваться для нас из коробки и без необходимости его модификации. Лучше всего было бы иметь один файл, файл Main.java, с одним общедоступным классом Main, а другие классы были бы модификатором доступа по умолчанию и в файле Main.java (но не вложены в сам класс.
Кроме того, конструктор модели не определен, но вы вызываете тот, который принимает неизвестный параметр сообщений.
Кроме того, ваш код бесполезен, не создает JFrame/GUI, не показывает и не воспроизводит проблему для нас,...
@HovercraftFullOfEels Упс. Я пропустил некоторые инструкции. Теперь он компилируется, по крайней мере, на моей стороне. Он создает JFrame. Трудно воспроизвести проблему, не написав много кода. Но я честно думаю, что идея есть, у вас есть контроллер, который изменяет одно поле, и цель состоит в том, чтобы представление вместо обновления всех полей обновляло только поле, которое было изменено. Дайте мне знать, что я могу сделать, чтобы сделать ответ лучше. Спасибо.
Я бы использовал SwingPropertyChangeSupport, сделал каждое из полей состояния модели «привязанным свойством», чтобы каждое поле состояния можно было прослушивать отдельно.




В реализации метода update вы можете определить с помощью первого аргумента o, какой Observable изменился, а со вторым аргументом arg какое значение изменилось при вызове: notifyObservers(this.speed);
Обратите внимание, что подпись notifyObservers принимает Object, а примитив float не является подклассом Object.
Привет @Сал. В моей реализации у меня есть только один Observable, так что первый аргумент не нужен. Но что касается второго аргумента, я не совсем понял, поэтому я знаю, что float не является подклассом Object, но в этом случае он бесполезен, потому что на самом деле он не сообщает вам, какое поле/значение было изменено, но это класс/экземпляр (?) если это применимо. Одним из возможных решений может быть объект с атрибутом String, который определяется перед каждым вызовом notifyObservers и передает этот объект в Observer, который считывает строку и идентифицирует поле. Но это кажется чертовски жестко запрограммированным.
Просто используйте оператор равенства ==, он сравнивает ссылки на объекты. if (this.model.speed == arg)
хорошо продумано! Я рассмотрю это и сообщу вам как можно скорее. Спасибо.
Привет еще раз, учитывая тот факт, что шаблон Observer устарел в Java 9, хотя я благодарю вас за вашу помощь, я буду изучать ответ от @Hovercraft Full of Eels. У вас есть мой голос! :-)
Я бы использовал SwingPropertyChangeSupport, сделал каждое из полей состояния модели «привязанным свойством», чтобы каждое поле состояния можно было прослушивать отдельно.
Например, скажем, у вас есть модель, которая выглядит так:
public class MvcModel {
public static final String SPEED = "speed";
public static final String BATTERY = "battery";
public static final int MAX_SPEED = 40;
private float speed;
private int batteryPercentage;
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
public float getSpeed() {
return speed;
}
public void setSpeed(float speed) {
float oldValue = this.speed;
float newValue = speed;
this.speed = speed;
pcSupport.firePropertyChange(SPEED, oldValue, newValue);
}
public int getBatteryPercentage() {
return batteryPercentage;
}
public void setBatteryPercentage(int batteryPercentage) {
int oldValue = this.batteryPercentage;
int newValue = batteryPercentage;
this.batteryPercentage = batteryPercentage;
pcSupport.firePropertyChange(BATTERY, oldValue, newValue);
}
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);
}
}
И скорость, и поле batteryPercent являются «связанными полями» в том смысле, что любые изменения в этих полях инициируют объект поддержки изменения свойства, чтобы отправить сообщение уведомления всем слушателям, которые зарегистрировались в объекте поддержки, как это отражено в методах public void setXxxx(...).
Таким образом, контроллер может регистрировать слушателей в модели для любых свойств, которые он хочет прослушивать, а затем уведомлять представление о любых изменениях. Например:
class SpeedListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
float speed = model.getSpeed();
view.setSpeed(speed);
}
}
Настройка может выглядеть примерно так:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
public class MVC2 {
private static void createAndShowGui() {
MvcModel model = new MvcModel();
MvcView view = new MvcView();
MvcController controller = new MvcController(model, view);
controller.init();
JFrame frame = new JFrame("MVC2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(view.getMainDisplay());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class MvcView {
private JPanel mainPanel = new JPanel();
private JSlider speedSlider = new JSlider(0, MvcModel.MAX_SPEED);
private JSlider batterySlider = new JSlider(0, 100);
private JProgressBar speedBar = new JProgressBar(0, MvcModel.MAX_SPEED);
private JProgressBar batteryPercentBar = new JProgressBar(0, 100);
public MvcView() {
speedSlider.setMajorTickSpacing(5);
speedSlider.setMinorTickSpacing(1);
speedSlider.setPaintTicks(true);
speedSlider.setPaintLabels(true);
speedSlider.setPaintTrack(true);
batterySlider.setMajorTickSpacing(20);
batterySlider.setMinorTickSpacing(5);
batterySlider.setPaintTicks(true);
batterySlider.setPaintLabels(true);
batterySlider.setPaintTrack(true);
speedBar.setStringPainted(true);
batteryPercentBar.setStringPainted(true);
JPanel inputPanel = new JPanel(new GridLayout(0, 1));
inputPanel.add(createTitledPanel("Speed", speedSlider));
inputPanel.add(createTitledPanel("Battery %", batterySlider));
JPanel displayPanel = new JPanel(new GridLayout(0, 1));
displayPanel.add(createTitledPanel("Speed", speedBar));
displayPanel.add(createTitledPanel("Battery %", batteryPercentBar));
mainPanel.setLayout(new GridLayout(1, 0));
mainPanel.add(createTitledPanel("Input", inputPanel));
mainPanel.add(createTitledPanel("Display", displayPanel));
}
private JComponent createTitledPanel(String title, JComponent component) {
JPanel titledPanel = new JPanel(new BorderLayout());
titledPanel.setBorder(BorderFactory.createTitledBorder(title));
titledPanel.add(component);
return titledPanel;
}
public JComponent getMainDisplay() {
return mainPanel;
}
public void setSpeed(float speed) {
speedBar.setValue((int) speed);
}
public void setBatteryPercent(int batteryPercent) {
batteryPercentBar.setValue(batteryPercent);
}
public JSlider getSpeedSlider() {
return speedSlider;
}
public JSlider getBatterySlider() {
return batterySlider;
}
}
class MvcController {
private MvcModel model;
private MvcView view;
public MvcController(MvcModel model, MvcView view) {
this.model = model;
this.view = view;
model.addPropertyChangeListener(MvcModel.BATTERY, new BatteryListener());
model.addPropertyChangeListener(MvcModel.SPEED, new SpeedListener());
view.getSpeedSlider().addChangeListener(chngEvent -> {
int value = view.getSpeedSlider().getValue();
model.setSpeed(value);
});
view.getBatterySlider().addChangeListener(chngEvent -> {
int value = view.getBatterySlider().getValue();
model.setBatteryPercentage(value);
});
}
public void init() {
view.getSpeedSlider().setValue(10);
view.getBatterySlider().setValue(100);
model.setSpeed(10);
model.setBatteryPercentage(100);
}
class SpeedListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
float speed = model.getSpeed();
view.setSpeed(speed);
}
}
class BatteryListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
int batteryPercent = model.getBatteryPercentage();
view.setBatteryPercent(batteryPercent);
}
}
}
Боковое примечание: Observer и Observable устарели в самой последней версии Java, поэтому их использования, вероятно, следует избегать.
Привет! Это на самом деле довольно близко к тому, что я понял позже. Я подробно рассмотрю ваш ответ, проверю его и свяжусь с вами как можно скорее. Я также обновлю пост соответственно. Проголосовал ;-) Спасибо!
Привет еще раз! Я принял этот ответ. Только один вопрос, хотя я понимаю разницу между SwingPropertyChangeSupport и PropertyChangeSupport, почему и когда я должен использовать первый?
@Fred: SwingPropertyChangeSupport гарантированно запускает уведомления в потоке отправки событий и только в этом потоке, поэтому его следует использовать для уведомлений Swing GUI. Обратите внимание, что все компоненты Swing уже содержат один из этих объектов в качестве поля класса.
Большое спасибо! Хорошей Пасхи (если вы ее празднуете)!
Пожалуйста, уточните свой вопрос. Кроме того, чем конкретнее вопрос и чем лучше код минимальный воспроизводимый пример, который вы публикуете, обычно тем выше качество вопроса и тем лучше ответ. Боковое примечание: обычно к модели добавляются прослушиватели, которые запускаются при изменении модели. Вы можете использовать один слушатель и просто обновлять все представление при изменении модели, или вы можете использовать несколько слушателей и обновлять вещи более выборочно, что, похоже, вы и пытаетесь сделать. Детали любого решения будут зависеть от деталей вашего кода и вашей проблемы.