MVC模式和SWING

MVC模式是我在“真正的SWING生活”中最难以真正掌握的devise模式之一。 我已经在这个网站上讨论了这个模式的相当多的post,但我仍然不觉得如何利用我的(Java SWING)应用程序中的模式。

假设我有一个包含一个表格,几个文本字段和几个button的JFrame。 我可能会使用TableModel将JTable与基础数据模型“桥接”。 但是,负责清除字段,validation字段,locking字段以及button操作的所有function通常都将直接在JFrame中进行。 但是,这不是混合模式的控制器和视图?

就我所知,在查看JTable(和模型)时,我设法实现了“正确”实现的MVC模式,但是当我将整个JFrame视为一个整体时,情况变得很混乱。

我真的很想听听其他人如何处理这个问题。 当你需要向用户显示一个表,几个字段和一些button(使用MVC模式)时,你如何去做?

我强烈推荐给你的一本MVC的书将是Freeman和Freeman的首个devise模式。 他们对MVC有一个非常全面的解释。

小结

  1. 你是用户 – 你与视图交互。 控制器采取你的行动并解释它们。 如果你点击一个button,控制者的工作就是根据这个动作找出这个模型的意义,以及如何操作模型。
  2. 控制器要求模型改变其状态。 当控制器从视图中接收到一个动作时,可能需要告诉视图改变结果。 例如,控制器可以启用或禁用界面中的某些button或菜单项。
  3. 控制者也可能要求改变观点。 当模型中的某些内容发生变化时,基于某个动作(如单击button)或某些其他内部变化(如播放列表中下一首歌曲已启动),模型会通知视图状态已更改。
  4. 模型在状态发生变化时通知视图。 当模型中的某些内容发生变化时,基于某个动作(如单击button)或某些其他内部变化(如播放列表中下一首歌曲已启动),模型会通知视图状态已更改。
  5. 该观点要求模型的状态。 视图获取它直接从模型显示的状态。 例如,当模型通知观看新歌已经开始播放时,视图从模型请求歌曲名称并显示它。 该视图也可能要求模型的状态作为控制器请求在视图中进行一些更改的结果。

在这里输入图像说明 来源 (如果你想知道什么是“奶油控制器”,可以考虑一个奥利奥cookies,控制器是奶油中心,视图是顶级cookies,模型是底部cookies。)

嗯,如果你有兴趣,你可以从这里下载一个相当有趣的MVC模式的歌曲!

Swing编程中可能遇到的一个问题涉及将SwingWorker和EventDispatch线程与MVC模式合并。 根据您的程序,您的视图或控制器可能必须扩展SwingWorker,并覆盖放置资源密集型逻辑的doInBackground()方法。 这可以很容易地融合到典型的MVC模式中,并且是Swing应用程序的典型特征。

编辑#1

另外,将MVC看作是各种模式的组合是很重要的。 例如,您的模型可以使用观察者模式(需要将视图注册为模型的观察者)来实现,而您的控制器可以使用策略模式。

编辑#2

我还想特别回答你的问题。 你应该在View中显示你的表格button等,这显然会实现一个ActionListener。 在actionPerformed()方法中,检测事件并将其发送给控制器中的相关方法(请记住,视图持有对控制器的引用)。 所以当单击一个button时,事件被视图检测到,发送给控制器的方法,控制器可能会直接要求视图禁用button或其他东西。 接下来,控制器将会与模型进行交互和修改(主要有getter和setter方法,还有一些注册和通知观察者等等)。 一旦模型被修改,它会调用已注册观察者的更新(这将是您的案例中的视图)。 因此,该视图现在将自行更新。

我不喜欢这个视图是数据变化时模型通知的视图。 我会将该function委托给控制器。 在这种情况下,如果更改应用程序逻辑,则不需要干涉视图的代码。 视图的任务只是为了应用程序组件+布局而已。 在swing中的布局已经是一个冗长的任务,为什么让它干扰应用程序的逻辑呢?

我的MVC(我目前正在使用,迄今为止很好)的想法是:

  1. 这个观点是三个中最愚蠢的。 它对控制器和模型一无所知。 它关注的只是摆动部件的前景和布局。
  2. 模型也是愚蠢的,但并不像视图那样愚蠢。 它执行以下function。
    • 一个。 当它的一个setter被控制器调用时,它会触发通知给它的听众/观察者(就像我说过的,我会把这个angular色交给控制器)​​。 我更喜欢SwingPropertyChangeSupport ,因为它已经为此目的进行了优化。
    • 湾 数据库交互function。
  3. 一个很聪明的控制器。 非常了解视图和模型。 控制器有两个function:
    • 一个。 它定义了当用户与之交互时视图将会执行的动作。
    • 湾 它倾听模型。 就像我刚才所说的那样,当调用模型的setter时,模型会向控制器发出通知。 控制器的工作是解释这个通知。 它可能需要反映视图的变化。

代码示例

风景 :

就像我说的创build视图已经是详细的,所以只需创build你自己的实现:)

 interface View{ JTextField getTxtFirstName(); JTextField getTxtLastName(); JTextField getTxtAddress(); } 

为了可testing性的目的,接口三是理想的。 我只提供了模型和控制器的实现。

该模型 :

 public class MyImplementationOfModel implements Model{ ... private SwingPropertyChangeSupport propChangeFirer; private String address; private String firstName; private String lastName; public MyImplementationOfModel() { propChangeFirer = new SwingPropertyChangeSupport(this); } public void addListener(PropertyChangeListener prop) { propChangeFirer.addPropertyChangeListener(prop); } public void setAddress(String address){ String oldVal = this.address; this.address = address; //after executing this, the controller will be notified that the new address has been set. Its then the controller's //task to decide what to do when the address in the model has changed. Ideally, the controller will update the view about this propChangeFirer.firePropertyChange("address", oldVal, address); } ... //some other setters for other properties & code for database interaction ... } 

控制器:

 public class MyImplementationOfController implements PropertyChangeListener, Controller{ private View view; private Model model; public MyImplementationOfController(View view, Model model){ this.view = view; this.model = model; //register the controller as the listener of the model this.model.addListener(this); setUpViewEvents(); } //code for setting the actions to be performed when the user interacts to the view. private void setUpViewEvents(){ view.getBtnClear().setAction(new AbstractAction("Clear") { @Override public void actionPerformed(ActionEvent arg0) { model.setFirstName(""); model.setLastName(""); model.setAddress(""); } }); view.getBtnSave().setAction(new AbstractAction("Save") { @Override public void actionPerformed(ActionEvent arg0) { ... //validate etc. ... model.setFirstName(view.getTxtFName().getText()); model.setLastName(view.getTxtLName().getText()); model.setAddress(view.getTxtAddress().getText()); model.save(); } }); } public void propertyChange(PropertyChangeEvent evt){ String propName = evt.getPropertyName(); Object newVal = evt.getNewValue(); if("address".equalsIgnoreCase(propName)){ view.getTxtAddress().setText((String)newVal); } //else if property (name) that fired the change event is first name property //else if property (name) that fired the change event is last name property } } 

Main,MVC的设置:

 public class Main{ public static void main(String[] args){ View view = new YourImplementationOfView(); Model model = new MyImplementationOfModel(); ... //create jframe //frame.add(view.getUI()); ... //make sure the view and model is fully initialized before letting the controller control them. Controller controller = new MyImplementationOfController(view, model); ... //frame.setVisible(true); ... } } 

MVC模式是如何构build用户界面的模型。 因此它定义了3个元素Model,View,Controller:

  • 模型模型是呈现给用户的东西的抽象。 在摇摆你有gui模型和数据模型的区别。 GUI模型抽象像ButtonModel这样的UI组件的状态。 数据模型将抽象的结构化数据提交给像TableModel这样的用户。
  • 查看该视图是一个UI组件,负责将数据呈现给用户。 因此,它负责所有与用户界面相关的问题,如布局,绘图等,例如JTable 。
  • 控制器控制器封装为了用户交互而执行的应用程序代码(鼠标移动,鼠标点击,按键等)。 控制器可能需要input来执行,并产生输出。 他们从模型中读取他们的input,并作为执行的结果更新模型。 他们也可能重构UI(例如,replaceUI组件或显示一个完整的新视图)。 但是他们一定不知道UI组件,因为你可以将重组封装在一个独立的接口中,控制器只调用它。 在摆动中,控制器通常由ActionListener或Action来实现 。

  • 红色=模型
  • 绿色=查看
  • 蓝色=控制器

在这里输入图像说明

Button被点击时,调用ActionListenerActionListener只依赖于其他模型。 它使用一些模型作为input,其他模型作为结果或输出。 这就像方法参数和返回值。 这些模型在更新时通知用户。 所以控制器逻辑不需要知道UI组件。 模型对象不知道用户界面。 通知是由观察者模式完成的。 因此,模型对象只知道有人想要在模型改变时得到通知。

在java swing中,还有一些实现模型和控制器的组件。 例如javax.swing.Action 。 它实现了一个UI模型(属性:启用,小图标,名称等),并且是一个控制器,因为它扩展了ActionListener 。

详细的解释,示例应用程序和源代码 : https : //www.link-intersystems.com/blog/2013/07/20/the-mvc-pattern-implemented-with-java-swing/ 。

less于240行的MVC基础:

 public class Main { public static void main(String[] args) { JFrame mainFrame = new JFrame("MVC example"); mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); mainFrame.setSize(640, 300); mainFrame.setLocationRelativeTo(null); PersonService personService = new PersonServiceMock(); DefaultListModel searchResultListModel = new DefaultListModel(); DefaultListSelectionModel searchResultSelectionModel = new DefaultListSelectionModel(); searchResultSelectionModel .setSelectionMode(ListSelectionModel.SINGLE_SELECTION); Document searchInput = new PlainDocument(); PersonDetailsAction personDetailsAction = new PersonDetailsAction( searchResultSelectionModel, searchResultListModel); personDetailsAction.putValue(Action.NAME, "Person Details"); Action searchPersonAction = new SearchPersonAction(searchInput, searchResultListModel, personService); searchPersonAction.putValue(Action.NAME, "Search"); Container contentPane = mainFrame.getContentPane(); JPanel searchInputPanel = new JPanel(); searchInputPanel.setLayout(new BorderLayout()); JTextField searchField = new JTextField(searchInput, null, 0); searchInputPanel.add(searchField, BorderLayout.CENTER); searchField.addActionListener(searchPersonAction); JButton searchButton = new JButton(searchPersonAction); searchInputPanel.add(searchButton, BorderLayout.EAST); JList searchResultList = new JList(); searchResultList.setModel(searchResultListModel); searchResultList.setSelectionModel(searchResultSelectionModel); JPanel searchResultPanel = new JPanel(); searchResultPanel.setLayout(new BorderLayout()); JScrollPane scrollableSearchResult = new JScrollPane(searchResultList); searchResultPanel.add(scrollableSearchResult, BorderLayout.CENTER); JPanel selectionOptionsPanel = new JPanel(); JButton showPersonDetailsButton = new JButton(personDetailsAction); selectionOptionsPanel.add(showPersonDetailsButton); contentPane.add(searchInputPanel, BorderLayout.NORTH); contentPane.add(searchResultPanel, BorderLayout.CENTER); contentPane.add(selectionOptionsPanel, BorderLayout.SOUTH); mainFrame.setVisible(true); } } class PersonDetailsAction extends AbstractAction { private static final long serialVersionUID = -8816163868526676625L; private ListSelectionModel personSelectionModel; private DefaultListModel personListModel; public PersonDetailsAction(ListSelectionModel personSelectionModel, DefaultListModel personListModel) { boolean unsupportedSelectionMode = personSelectionModel .getSelectionMode() != ListSelectionModel.SINGLE_SELECTION; if (unsupportedSelectionMode) { throw new IllegalArgumentException( "PersonDetailAction can only handle single list selections. " + "Please set the list selection mode to ListSelectionModel.SINGLE_SELECTION"); } this.personSelectionModel = personSelectionModel; this.personListModel = personListModel; personSelectionModel .addListSelectionListener(new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { ListSelectionModel listSelectionModel = (ListSelectionModel) e .getSource(); updateEnablement(listSelectionModel); } }); updateEnablement(personSelectionModel); } public void actionPerformed(ActionEvent e) { int selectionIndex = personSelectionModel.getMinSelectionIndex(); PersonElementModel personElementModel = (PersonElementModel) personListModel .get(selectionIndex); Person person = personElementModel.getPerson(); String personDetials = createPersonDetails(person); JOptionPane.showMessageDialog(null, personDetials); } private String createPersonDetails(Person person) { return person.getId() + ": " + person.getFirstName() + " " + person.getLastName(); } private void updateEnablement(ListSelectionModel listSelectionModel) { boolean emptySelection = listSelectionModel.isSelectionEmpty(); setEnabled(!emptySelection); } } class SearchPersonAction extends AbstractAction { private static final long serialVersionUID = 4083406832930707444L; private Document searchInput; private DefaultListModel searchResult; private PersonService personService; public SearchPersonAction(Document searchInput, DefaultListModel searchResult, PersonService personService) { this.searchInput = searchInput; this.searchResult = searchResult; this.personService = personService; } public void actionPerformed(ActionEvent e) { String searchString = getSearchString(); List<Person> matchedPersons = personService.searchPersons(searchString); searchResult.clear(); for (Person person : matchedPersons) { Object elementModel = new PersonElementModel(person); searchResult.addElement(elementModel); } } private String getSearchString() { try { return searchInput.getText(0, searchInput.getLength()); } catch (BadLocationException e) { return null; } } } class PersonElementModel { private Person person; public PersonElementModel(Person person) { this.person = person; } public Person getPerson() { return person; } @Override public String toString() { return person.getFirstName() + ", " + person.getLastName(); } } interface PersonService { List<Person> searchPersons(String searchString); } class Person { private int id; private String firstName; private String lastName; public Person(int id, String firstName, String lastName) { this.id = id; this.firstName = firstName; this.lastName = lastName; } public int getId() { return id; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } } class PersonServiceMock implements PersonService { private List<Person> personDB; public PersonServiceMock() { personDB = new ArrayList<Person>(); personDB.add(new Person(1, "Graham", "Parrish")); personDB.add(new Person(2, "Daniel", "Hendrix")); personDB.add(new Person(3, "Rachel", "Holman")); personDB.add(new Person(4, "Sarah", "Todd")); personDB.add(new Person(5, "Talon", "Wolf")); personDB.add(new Person(6, "Josephine", "Dunn")); personDB.add(new Person(7, "Benjamin", "Hebert")); personDB.add(new Person(8, "Lacota", "Browning ")); personDB.add(new Person(9, "Sydney", "Ayers")); personDB.add(new Person(10, "Dustin", "Stephens")); personDB.add(new Person(11, "Cara", "Moss")); personDB.add(new Person(12, "Teegan", "Dillard")); personDB.add(new Person(13, "Dai", "Yates")); personDB.add(new Person(14, "Nora", "Garza")); } public List<Person> searchPersons(String searchString) { List<Person> matches = new ArrayList<Person>(); if (searchString == null) { return matches; } for (Person person : personDB) { if (person.getFirstName().contains(searchString) || person.getLastName().contains(searchString)) { matches.add(person); } } return matches; } } 

您可以在单独的普通Java类中创build模型,在另一个控制器中创build模型。

那么你可以在其上面有Swing组件。 JTable将是其中一个观点( 事实上 ,表模型将成为观点的一部分 – 它只会从“共享模型”转化为JTable )。

每当表格被编辑时,其表格模型就会告诉“主控制器”来更新一些东西。 但是,控制器应该对表格一无所知。 所以调用看起来应该更像: updateCustomer(customer, newValue) ,而不是updateCustomer(row, column, newValue)

为共享模型添加一个侦听器(观察者)接口。 一些组件(例如你的表)可以直接实现它。 另一位观察者可以是坐标button可用性等的控制器。


这是做这件事的一种方法,但是当然如果你的用例过于矫枉过正,你可以简化或扩展它。

您可以将控制器与模型合并,并具有相同的类进程更新和维护组件可用性。 你甚至可以使“共享模型”成为一个TableModel (尽pipe如果它不仅被表使用,我会build议至less提供一个友好的API,不泄漏表抽象)

另一方面,您可以拥有更新的复杂接口( CustomerUpdateListenerOrderItemListenerOrderCancellationListener )和专用控制器(或调解器),仅用于协调不同的视图。

这取决于你的问题有多复杂。

为了正确分离,你通常会有一个Frame类将委托给的控制器类。 有很多方法可以设置类之间的关系 – 您可以实现一个控制器并将其扩展为主视图类,或者使用Frame在事件发生时调用的独立控制器类。 该视图通常会通过实现监听器接口来接收来自控制器的事件。

有时候,MVC模式的一个或多个部分是微不足道的,或者如此“薄”,以至于增加了不必要的复杂性来分离它们。 如果你的控制器充满了一个线路调用,把它放在一个单独的类中最终会混淆底层的行为。 例如,如果您正在处理的所有事件都与TableModel相关,并且是简单的添加和删除操作,那么您可以select在该模型中实现所有表操作函数(以及必要的callback函数以将其显示在JTable中)。 这不是真正的MVC,但它避免了在不需要的地方增加复杂性。

但是,如果要实现它,请记住JavaDoc的类,方法和包,以便正确描述组件及其关系。

我发现了一些关于实现MVC模式的有趣的文章,这可能会解决你的问题。

如果你用GUI开发一个程序, mvc模式几乎是模糊的。

理解模型,视图和控制器代码是困难的,通常不仅是重构任务。

你知道你的代码是可重用的。 如果你已经正确实现了MVC,应该很容易实现TUI或者CLI或者RWD或者具有相同function的移动devise 。 看起来很容易,而不是实际上,而且现有的代码。

事实上,模型,视图和控制器之间的交互使用其他隔离模式(如Observer或Listener)

我想这个post详细解释了它,从直接的非MVC模式(如你将在Q&D上做的 )到最终的可重用实现:

http://www.austintek.com/mvc/