我正在根据MVC模式开发我的GUI:
-GUIview:Swing组件(JFrame和几个JTable)。 -GUIcontroller:监听器(在这里添加,在内部类中定义) -GUImodel:修改和存储数据,触发change-events。
模型中的更改通过控制器(而非直接)传递给视图,如this示例中所示。
我还为View类中包含的不同JTable编写了不同的自定义JTableModel(扩展AbstractTableModel)。所有JTableModel都在包“GUImodel”中的不同类中定义。每个JTableModel定义一个ArrayList和一些操作ArrayList的方法。
根据MVC指南,模型应该对视图一无所知。实际上,main()方法定义如下:
GUImodel model = new GUImodel();
GUIcontroller controller = new GUIcontroller();
GUIview view = new GUIview(controller, model);
controller.addView(view);
controller.addModel(model);
view.setVisible(true);
controller.addControllerListerners();
我的问题是: 当我在GUImodel中执行方法时(例如因为按下了JButton并且我需要从外部文件加载数据),我需要修改一些JTableModel(将数据/行添加到其ArrayList)并获取更改反映在JTable中。我的第一个想法是:
ArrayList newArrayList = fileLoader(filePath); //create ArrayList and load info
guiView.getTable1Model().updateArrayList(newArrayList); //update JTableModel ArrayList
但是,这种方法无效,因为GUImodel应完全独立于GUIview。
有什么想法吗?
答案 0 :(得分:3)
可能很高兴认识到MVC主要是与数据封装有关的模式,它使用另一种模式Observer来进行通信更改。作为数据封装器,模型不知道视图和控制器,但作为Observable,它确实知道它有Observers,需要在发生更改时得到通知。
为了管理变更通知,开发了依赖项对象的概念。意见和 模型的控制器作为模型的依赖者在列表中注册,以便随时被通知 模型的某些方面发生了变化。模型更改后,将广播消息以进行通知 所有家属都对这一变化有所了解。此消息可以参数化(带参数),所以 可以有许多类型的模型更改消息。每个视图或控制器都响应 适当的模式改变。
为了说明这个概念,你可以从你自己的Observer / Observable类开始:
public interface Observer {
public void update(int message);
}
public interface Observable {
public void registerObserver(Observer observer);
}
public class Model implements Observable {
List<Observer> observers;
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void loadFile(String path) {
// load file and change data
foreach (Observer observer: observers)
observer.update(READ_NEW_DATA);
}
public ArrayList getData() { return data; }
}
public class View implements Observer {
public void update(int message) {
doWhateverWith(model.getData());
}
}
public class Controller implements Observer {
public void update(int message) {
doWhateverWith(model.getData());
}
public void onClick() {
model.loadFile("someFile");
}
}
如您所见,模型对视图和控制器的内部工作原理一无所知。它甚至不知道返回一个ArrayList对他们来说是否特别有用(尽管实际上你也喜欢这样)。所以在这方面,实现了独立。
Obervable和Observers之间的通信没有独立性,但这不是MVC模式要求的一部分。
如果您希望GUI在现有的Swing Observer模式(Listeners)之上搭便车,那么您的类应该从相应的类继承:
public class Model extends AbstractTableModel...
public class View implements TableModelListener...
public class Controller implements CellEditorListener...
等等。由于JTable同时实现了TableModelListener和CellEditorListener,因此它实际上是View和Controller的组合。因此,您可以选择组合ViewController类扩展JTable,也可以单独使用它们。在后一种情况下,View可以扩展JTable,覆盖控件Listener,以便将它们的事件传递给Controller类。但这听起来像是值得付出的工作。
答案 1 :(得分:2)
正如here所讨论的那样,将模型和视图松散耦合是正确的。 JTable
实现TableModelListener
来监听自己的模型,而AbstractTableModel
毫无疑问会触发导致监听表自行更新的事件。
在这种情况下,让从属TableModel
将自己添加为TableModelListener
给主TableModel
。然后,依赖模型可以触发所需的事件,以通知自己的监听器从主服务器传播的更改。
答案 2 :(得分:2)
但是,这种方法无效,因为GUImodel应完全独立于GUIview。
Swing组件本身使用MVC模型。模型中的更改必须触发视图中的更改。问题是你是怎么做到的?
一种方法是模型可以访问视图实例,正如您在问题中所说明的那样。
ArrayList newArrayList = fileLoader(filePath); //create ArrayList and load info
guiView.getTable1Model().updateArrayList(newArrayList); //update JTableModel ArrayList
另一种方法是控制器更新模型并更新视图。这就是我在Swing应用程序中通常所做的事情。
model.loadArrayList(filePath);
frame.getFrame().getMainPanel().repaint();
另一种方法是解雇行动。这就是Swing组件更新GUI的方式。
ArrayList newArrayList = fileLoader(filePath); //create ArrayList and load info
fireAction(newArrayLiat);
fireAction
方法适用于侦听器。这是我从AbstractListModel
复制的火方法。
protected void fireContentsChanged(Object source, int index0, int index1) {
Object[] listeners = listenerList.getListenerList();
ListDataEvent e = null;
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == ListDataListener.class) {
if (e == null) {
e = new ListDataEvent(source,
ListDataEvent.CONTENTS_CHANGED, index0, index1);
}
((ListDataListener) listeners[i + 1]).contentsChanged(e);
}
}
}
您必须在模型类中编写侦听器,视图类可以编写代码来更改视图。
EventListenerList的Javadoc提供了有关侦听器的更多信息。谢谢 Catalina Island
答案 3 :(得分:1)
我在摇摆中的MVC风格是,模型和视图彼此之间以及控制器都不知道,但控制器很清楚地知道视图和模型。这样,我就完成了控制器中的所有逻辑。我只是在视图中留下了UI +复杂布局的长代码,并考虑了应用程序模型和模型所需的所有数据。决定某些数据是否应出现在我的视图中。我通过view.getBtn().setAction(new ActionForThisOrThatInnerClass())
种类的东西把控制器添加到控制器的功能
在您的情况下,我同意表格将使用的数据应以理想情况下以List
的形式存储在您的主模型中,但我不会打扰自己将新TableModel
子类化。 1}}处理这些数据,我认为DefaultTableModel
足以做很多事情。
如果我要编写您的要求,这是可运行的示例
public class Sample {
public static void main(String[] args){
View view = new View();
Model model = new Model();
Controller controller = new Controller(view, model);
JFrame frame = new JFrame("MVC Demo");
frame.getContentPane().setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(view.getUI());
frame.pack();
frame.setVisible(true);
view.getBtnFileLoader().doClick();
}
}
class View{
private JButton btnFileChooser;
private JButton btnFileLoader;
private JTable tblData;
private JPanel pnlMain;
public View(){
pnlMain = new JPanel(new BorderLayout()){
@Override public Dimension getPreferredSize(){
return new Dimension(300, 400);
}
};
JPanel pnlFileLoader = new JPanel();
pnlFileLoader.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
pnlFileLoader.setLayout(new BoxLayout(pnlFileLoader, BoxLayout.LINE_AXIS));
JTextField txtFileDir = new JTextField();
pnlFileLoader.add(txtFileDir);
btnFileLoader = new JButton();
pnlFileLoader.add(btnFileLoader);
btnFileChooser = new JButton();
pnlFileLoader.add(btnFileChooser);
tblData = new JTable();
JScrollPane pane = new JScrollPane(tblData);
pnlMain.add(pane);
pnlMain.add(pnlFileLoader, BorderLayout.PAGE_START);
}
public JPanel getUI(){
return pnlMain;
}
public JButton getBtnFileLoader(){
return btnFileLoader;
}
public JButton getBtnFileChooser(){
return btnFileChooser;
}
public JTable getTblData(){
return tblData;
}
}
class Controller implements PropertyChangeListener{
private View view;
private Model model;
private DefaultTableModel tmodel;
public Controller(View view, Model model){
this.view = view;
this.model = model;
model.addModelListener(this);
setupViewEvents();
setupTable();
}
private void setupTable(){
tmodel = new DefaultTableModel();
tmodel.addColumn("First Name");
tmodel.addColumn("Last Name");
tmodel.addColumn("Occupation");
view.getTblData().setModel(tmodel);
}
private void setupViewEvents(){
view.getBtnFileChooser().setAction(new AbstractAction("Choose"){
@Override
public void actionPerformed(ActionEvent arg0) {
//choose the file then put the dir
//in the txtfield
}
});
view.getBtnFileLoader().setAction(new AbstractAction("Load"){
@Override
public void actionPerformed(ActionEvent arg0) {
//validate if the dir in the textfield exists and the file is loadable
//load the file specified in the textfield
//assumming the list is already retrieved from the file
//and the list contains the following person
List<Person> list = new ArrayList<Person>();
Person p1 = new Person("Bernardo", "Santos", "Developer");
Person p2 = new Person("Robert", "Erasquin", "Architect");
Person p3 = new Person("Klarrise", "Caparas", "Food Scientist");
list.add(p1);
list.add(p2);
list.add(p3);
//now update the model of the new value for the list
model.setTheList(list);
}
});
}
@Override
@SuppressWarnings("unchecked")
public void propertyChange(PropertyChangeEvent evt) {
if(evt.getPropertyName().equals("theList")){
List<Person> newVal = (List<Person>) evt.getNewValue();
DefaultTableModel tmodel = (DefaultTableModel)view.getTblData().getModel();
for(Person p : newVal){
tmodel.addRow(new Object[]{p.getFirstName(), p.getLastName(), p.getOccupation()});
}
}
}
}
class Model{
private List<Person> theList;
private SwingPropertyChangeSupport propChangeFirer;
public Model(){
propChangeFirer = new SwingPropertyChangeSupport(this);
}
public void setTheList(List<Person> theList){
List<Person> oldVal = this.theList;
this.theList = theList;
//after the model has been updated, notify its listener about
//the update, in our case the controller itself listens to the model
propChangeFirer.firePropertyChange("theList", oldVal, theList);
}
public void addModelListener(PropertyChangeListener prop) {
propChangeFirer.addPropertyChangeListener(prop);
}
}
class Person{
private String firstName;
private String lastName;
private String occupation;
public Person(String firstName, String lastName, String occupation){
this.firstName = firstName;
this.lastName = lastName;
this.occupation = occupation;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getOccupation() {
return occupation;
}
public void setOccupation(String occupation) {
this.occupation = occupation;
}
}