在 JComponent 和模型对象之间传输数据
Transit data between JComponent and a model Object
我需要使用 UI 组件更新模型 class 的数据,同时使用数据对象的更改更新 UI 组件。具体来说,大量数据依赖于其他数据。 e.a.: A 和 B 的 SUM。SUM 需要显示在 UI 并存储在模型 Class.
在真实情况下,我有大约 58 个可编辑字段,混合了文本和数字。以及一半的计算字段。
思来想去,我想出了很多解决方案。我的问题是我没有经验来决定或判断什么是最好的方法,如果有的话。两位主要候选人是:
- 首先是将 DocumentListeners 添加到所有可编辑的 UI 字段。更改时,它们会更新模型中的数据并调用一个方法来更新 UI 中的所有字段。缺点 - 我的粗略看法 - 是我有 50 多个字段。如果不为每个 UI 组件编写特定的侦听器,我不知道如何编写代码。这也可能使以后处理代码更改变得困难。
- 创建一个 class 数组,用于注册每个可编辑或计算的 UI 组件。 class 不仅会注册 UI 组件,还会使用反射注册要调用的方法,以从模型对象设置或检索信息。文档列表器仍将处理更改,但现在所有 UI 组件都可以相同,因为数组可以处理更改。一个好处是模型和 UI 之间的所有转换都可以编码在一个 class 中。缺点是反射,人们似乎总是建议避免它。
处理这种情况的最佳或良好方法是什么?
我用来测试的代码:
public class Comunication {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
//Main Window
JFrame frame = new JFrame();
frame.setTitle("SumTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setMinimumSize(new Dimension(500,200));
frame.setVisible(true);
//Main Panel
JPanel pane = new JPanel();
frame.setContentPane(pane);
//Component
JTextField valueA = new JTextField("VALUE A");
JTextField valueB = new JTextField("VALUE B");
JTextField valueSum = new JTextField("VALUE SUM");
pane.add(valueA);
pane.add(valueB);
pane.add(valueSum);
}
});
}
}
class Data {
private int a;
private int b;
private int sum;
public Data() {
a = 1;
b = 2;
Calculate();
}
public void Calculate() {
sum = a + b;
}
public int getA() { return a; }
public int getB() { return b; }
public int getSUM() { return sum; }
public void setA(int i) { a = i; }
public void setB(int i) { b = i; }
}
第 2 部分:
通过用户提供的信息进行试验,我自由地尝试了其他东西。
一种解决方案是创建一个侦听器 class 来 link 视图和模型。每次将此侦听器添加到字段(视图)时都应稍作更改,这是在不使用反射的情况下找到 link 模型中具有方法的字段的唯一方法。
因此,更新算法是:当更改时,视图更新模型。
之后:gobal 控制器使用模型的新信息更新所有视图。
我发现的问题,可能是由于缺乏经验:
1) 由于所有视图都有文档更改侦听器:当全局控制器更新所有 views/fields 他们再次调用侦听器。我发现的解决方法是向监听器添加一个 "silent" 标志。
2) 当一个视图更新模型并调用全局控制器更新所有其他视图时,它不能更新自己。我还没有找到原因,我认为可能导致循环。解决方法是告诉调用它的全局控制器,并更新除调用者之外的每个视图。
包含解决方案和解决方法的完整工作代码如下。
非常欢迎提出意见,我想做得对,或者更好。
import java.awt.*;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class Comunication {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
//Main Window
JFrame frame = new JFrame();
frame.setTitle("SumTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setMinimumSize(new Dimension(500,200));
frame.setVisible(true);
//Main Panel
JPanel pane = new JPanel();
frame.setContentPane(pane);
//Data Model
DataModel model = new DataModel();
GlobalUpdateController viewUpdateController = new GlobalUpdateController();
//Component
JTextField valueA = new JTextField("");
JTextField valueB = new JTextField("");
JTextField valueSum = new JTextField("");
valueA.setPreferredSize(new Dimension(30, 20));
valueB.setPreferredSize(new Dimension(30, 20));
valueSum.setPreferredSize(new Dimension(30, 20));
pane.add(valueA);
pane.add(valueB);
pane.add(valueSum);
//Listeners
valueA.getDocument().addDocumentListener(new StealthListener(valueA , viewUpdateController) {
@Override
public void updateView() {
this.view.setText( Integer.toString( model.getA() ) );
}
@Override
public void updateModel() {
model.setA( Integer.parseInt( this.view.getText() ) );
}
});
valueB.getDocument().addDocumentListener(new StealthListener(valueB , viewUpdateController) {
@Override
public void updateView() {
this.view.setText( Integer.toString( model.getB() ) );
}
@Override
public void updateModel() {
model.setB( Integer.parseInt( this.view.getText() ) );
}
});
valueSum.getDocument().addDocumentListener(new StealthListener(valueSum , viewUpdateController) {
@Override
public void updateView() {
this.view.setText( Integer.toString( model.getSUM() ) );
}
@Override
public void updateModel() {
//Do nothing
}
});
//Initial Update
viewUpdateController.updateAllViews(null);
}
});
}
}
class DataModel {
private int a;
private int b;
private int sum;
public DataModel() {
a = 3;
b = 5;
Calculate();
}
public void Calculate() {
sum = a + b;
}
public int getA() { return a; }
public int getB() { return b; }
public int getSUM() { return sum; }
public void setA(int i) { a = i; Calculate(); }
public void setB(int i) { b = i; Calculate(); }
}
class StealthListener implements DocumentListener {
JTextField view;
GlobalUpdateController viewList;
private boolean silent;
public StealthListener(JTextField view, GlobalUpdateController viewList) {
this.view = view;
this.viewList = viewList;
this.silent = false;
this.viewList.add(this);
}
public void setSilent(boolean val) {
this.silent = val;
}
public void updateView() {
// Unique to each view, to be Overriden
}
public void updateModel() {
// Unique to each view, to be Overriden
}
public void update() {
//The silent flag is meant to avoid ListenerLoop when changing the document.
//When the silent is true is meant to listen to internal changes.
if(this.silent == false) {
updateModel();
this.viewList.updateAllViews(this);
}
}
@Override
public void insertUpdate(DocumentEvent e) {
update();
}
@Override
public void removeUpdate(DocumentEvent e) {
update();
}
@Override
public void changedUpdate(DocumentEvent e) {
update();
}
}
class GlobalUpdateController {
private ArrayList<StealthListener> viewList;
public GlobalUpdateController() {
this.viewList = new ArrayList<StealthListener>();
}
public void add(StealthListener control) {
this.viewList.add(control);
}
public void updateAllViews(StealthListener caller) {
for( StealthListener view : viewList) {
if( caller==null || view != caller ) {
view.setSilent(true);
view.updateView();
view.setSilent(false);
}
}
}
}
在此相关 example 中,任意数量的可编辑文本字段中的每一个都添加了 PropertyChangeListener
和 FocusListener
的实例。每个侦听器调用一个通用的 update()
方法来重新计算派生的 sum
。在下面的变体中,字段共享 UpdateListener
的 single 实例,即 both a FocusListener
和一个PropertyChangeListener
。让 UpdateListener
也实现 DocumentListener
只有当你每次击键都需要 update()
时。
实际上,模型是List<JFormattedTextField>
中的数据,控制器是普通的[=13] =] 方法强制输入字段之间的关系。这种方法的可扩展性取决于 update()
的复杂性。该示例使用 JFormattedTextField
以便于对相关字段进行格式化。
每当我考虑使用反射时,我也会考虑strategy pattern in conjunction with enum
as an alternative. Examples are seen here and here.
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* @see
* @see
* @see
*/
public class Adder extends JPanel {
private static final int MAX = 3;
private final List<JFormattedTextField> fields = new ArrayList<>();
private final NumberFormat format = NumberFormat.getNumberInstance();
private final JFormattedTextField sum = new JFormattedTextField(format);
private final UpdateListener listener = new UpdateListener();
private class UpdateListener extends FocusAdapter implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent e) {
update();
}
@Override
public void focusLost(FocusEvent e) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
update();
}
});
}
}
public Adder() {
this.setLayout(new GridLayout(0, 1));
for (int i = 0; i < MAX; i++) {
JFormattedTextField tf = init();
fields.add(tf);
this.add(tf);
}
sum.setHorizontalAlignment(JFormattedTextField.RIGHT);
sum.setEditable(false);
sum.setFocusable(false);
this.add(sum);
}
private JFormattedTextField init() {
JFormattedTextField jtf = new JFormattedTextField(format);
jtf.setValue(0);
jtf.setHorizontalAlignment(JFormattedTextField.RIGHT);
jtf.addFocusListener(listener);
jtf.addPropertyChangeListener("value", listener);
return jtf;
}
private void update() {
long total = 0;
for (JFormattedTextField tf : fields) {
Number v = (Number) tf.getValue();
total += v.longValue();
}
sum.setValue(total);
}
private void display() {
JFrame f = new JFrame("Adder");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new Adder().display();
}
});
}
}
我需要使用 UI 组件更新模型 class 的数据,同时使用数据对象的更改更新 UI 组件。具体来说,大量数据依赖于其他数据。 e.a.: A 和 B 的 SUM。SUM 需要显示在 UI 并存储在模型 Class.
在真实情况下,我有大约 58 个可编辑字段,混合了文本和数字。以及一半的计算字段。
思来想去,我想出了很多解决方案。我的问题是我没有经验来决定或判断什么是最好的方法,如果有的话。两位主要候选人是:
- 首先是将 DocumentListeners 添加到所有可编辑的 UI 字段。更改时,它们会更新模型中的数据并调用一个方法来更新 UI 中的所有字段。缺点 - 我的粗略看法 - 是我有 50 多个字段。如果不为每个 UI 组件编写特定的侦听器,我不知道如何编写代码。这也可能使以后处理代码更改变得困难。
- 创建一个 class 数组,用于注册每个可编辑或计算的 UI 组件。 class 不仅会注册 UI 组件,还会使用反射注册要调用的方法,以从模型对象设置或检索信息。文档列表器仍将处理更改,但现在所有 UI 组件都可以相同,因为数组可以处理更改。一个好处是模型和 UI 之间的所有转换都可以编码在一个 class 中。缺点是反射,人们似乎总是建议避免它。
处理这种情况的最佳或良好方法是什么?
我用来测试的代码:
public class Comunication {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
//Main Window
JFrame frame = new JFrame();
frame.setTitle("SumTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setMinimumSize(new Dimension(500,200));
frame.setVisible(true);
//Main Panel
JPanel pane = new JPanel();
frame.setContentPane(pane);
//Component
JTextField valueA = new JTextField("VALUE A");
JTextField valueB = new JTextField("VALUE B");
JTextField valueSum = new JTextField("VALUE SUM");
pane.add(valueA);
pane.add(valueB);
pane.add(valueSum);
}
});
}
}
class Data {
private int a;
private int b;
private int sum;
public Data() {
a = 1;
b = 2;
Calculate();
}
public void Calculate() {
sum = a + b;
}
public int getA() { return a; }
public int getB() { return b; }
public int getSUM() { return sum; }
public void setA(int i) { a = i; }
public void setB(int i) { b = i; }
}
第 2 部分:
通过用户提供的信息进行试验,我自由地尝试了其他东西。 一种解决方案是创建一个侦听器 class 来 link 视图和模型。每次将此侦听器添加到字段(视图)时都应稍作更改,这是在不使用反射的情况下找到 link 模型中具有方法的字段的唯一方法。
因此,更新算法是:当更改时,视图更新模型。 之后:gobal 控制器使用模型的新信息更新所有视图。
我发现的问题,可能是由于缺乏经验:
1) 由于所有视图都有文档更改侦听器:当全局控制器更新所有 views/fields 他们再次调用侦听器。我发现的解决方法是向监听器添加一个 "silent" 标志。
2) 当一个视图更新模型并调用全局控制器更新所有其他视图时,它不能更新自己。我还没有找到原因,我认为可能导致循环。解决方法是告诉调用它的全局控制器,并更新除调用者之外的每个视图。
包含解决方案和解决方法的完整工作代码如下。 非常欢迎提出意见,我想做得对,或者更好。
import java.awt.*;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
public class Comunication {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
//Main Window
JFrame frame = new JFrame();
frame.setTitle("SumTest");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setMinimumSize(new Dimension(500,200));
frame.setVisible(true);
//Main Panel
JPanel pane = new JPanel();
frame.setContentPane(pane);
//Data Model
DataModel model = new DataModel();
GlobalUpdateController viewUpdateController = new GlobalUpdateController();
//Component
JTextField valueA = new JTextField("");
JTextField valueB = new JTextField("");
JTextField valueSum = new JTextField("");
valueA.setPreferredSize(new Dimension(30, 20));
valueB.setPreferredSize(new Dimension(30, 20));
valueSum.setPreferredSize(new Dimension(30, 20));
pane.add(valueA);
pane.add(valueB);
pane.add(valueSum);
//Listeners
valueA.getDocument().addDocumentListener(new StealthListener(valueA , viewUpdateController) {
@Override
public void updateView() {
this.view.setText( Integer.toString( model.getA() ) );
}
@Override
public void updateModel() {
model.setA( Integer.parseInt( this.view.getText() ) );
}
});
valueB.getDocument().addDocumentListener(new StealthListener(valueB , viewUpdateController) {
@Override
public void updateView() {
this.view.setText( Integer.toString( model.getB() ) );
}
@Override
public void updateModel() {
model.setB( Integer.parseInt( this.view.getText() ) );
}
});
valueSum.getDocument().addDocumentListener(new StealthListener(valueSum , viewUpdateController) {
@Override
public void updateView() {
this.view.setText( Integer.toString( model.getSUM() ) );
}
@Override
public void updateModel() {
//Do nothing
}
});
//Initial Update
viewUpdateController.updateAllViews(null);
}
});
}
}
class DataModel {
private int a;
private int b;
private int sum;
public DataModel() {
a = 3;
b = 5;
Calculate();
}
public void Calculate() {
sum = a + b;
}
public int getA() { return a; }
public int getB() { return b; }
public int getSUM() { return sum; }
public void setA(int i) { a = i; Calculate(); }
public void setB(int i) { b = i; Calculate(); }
}
class StealthListener implements DocumentListener {
JTextField view;
GlobalUpdateController viewList;
private boolean silent;
public StealthListener(JTextField view, GlobalUpdateController viewList) {
this.view = view;
this.viewList = viewList;
this.silent = false;
this.viewList.add(this);
}
public void setSilent(boolean val) {
this.silent = val;
}
public void updateView() {
// Unique to each view, to be Overriden
}
public void updateModel() {
// Unique to each view, to be Overriden
}
public void update() {
//The silent flag is meant to avoid ListenerLoop when changing the document.
//When the silent is true is meant to listen to internal changes.
if(this.silent == false) {
updateModel();
this.viewList.updateAllViews(this);
}
}
@Override
public void insertUpdate(DocumentEvent e) {
update();
}
@Override
public void removeUpdate(DocumentEvent e) {
update();
}
@Override
public void changedUpdate(DocumentEvent e) {
update();
}
}
class GlobalUpdateController {
private ArrayList<StealthListener> viewList;
public GlobalUpdateController() {
this.viewList = new ArrayList<StealthListener>();
}
public void add(StealthListener control) {
this.viewList.add(control);
}
public void updateAllViews(StealthListener caller) {
for( StealthListener view : viewList) {
if( caller==null || view != caller ) {
view.setSilent(true);
view.updateView();
view.setSilent(false);
}
}
}
}
在此相关 example 中,任意数量的可编辑文本字段中的每一个都添加了
PropertyChangeListener
和FocusListener
的实例。每个侦听器调用一个通用的update()
方法来重新计算派生的sum
。在下面的变体中,字段共享UpdateListener
的 single 实例,即 both aFocusListener
和一个PropertyChangeListener
。让UpdateListener
也实现DocumentListener
只有当你每次击键都需要update()
时。实际上,模型是
List<JFormattedTextField>
中的数据,控制器是普通的[=13] =] 方法强制输入字段之间的关系。这种方法的可扩展性取决于update()
的复杂性。该示例使用JFormattedTextField
以便于对相关字段进行格式化。每当我考虑使用反射时,我也会考虑strategy pattern in conjunction with
enum
as an alternative. Examples are seen here and here.
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* @see
* @see
* @see
*/
public class Adder extends JPanel {
private static final int MAX = 3;
private final List<JFormattedTextField> fields = new ArrayList<>();
private final NumberFormat format = NumberFormat.getNumberInstance();
private final JFormattedTextField sum = new JFormattedTextField(format);
private final UpdateListener listener = new UpdateListener();
private class UpdateListener extends FocusAdapter implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent e) {
update();
}
@Override
public void focusLost(FocusEvent e) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
update();
}
});
}
}
public Adder() {
this.setLayout(new GridLayout(0, 1));
for (int i = 0; i < MAX; i++) {
JFormattedTextField tf = init();
fields.add(tf);
this.add(tf);
}
sum.setHorizontalAlignment(JFormattedTextField.RIGHT);
sum.setEditable(false);
sum.setFocusable(false);
this.add(sum);
}
private JFormattedTextField init() {
JFormattedTextField jtf = new JFormattedTextField(format);
jtf.setValue(0);
jtf.setHorizontalAlignment(JFormattedTextField.RIGHT);
jtf.addFocusListener(listener);
jtf.addPropertyChangeListener("value", listener);
return jtf;
}
private void update() {
long total = 0;
for (JFormattedTextField tf : fields) {
Number v = (Number) tf.getValue();
total += v.longValue();
}
sum.setValue(total);
}
private void display() {
JFrame f = new JFrame("Adder");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
new Adder().display();
}
});
}
}