MVC模式和Swing

问题描述 投票:70回答:7

我发现最难以真正掌握“真正的Swing生活”的设计模式之一是MVC模式。我已经浏览了这个讨论模式的网站上的一些帖子,但我仍然觉得我并不清楚如何利用Java Swing应用程序中的模式。

假设我有一个包含表格,几个文本字段和几个按钮的JFrame。我可能会使用TableModel将JTable与基础数据模型“桥接”。但是,负责清除字段,验证字段,锁定字段以及按钮操作的所有函数通常都直接在JFrame中。但是,是不是混合了Controller和View的模式?

据我所知,我设法在查看JTable(和模型)时“正确”实现了MVC模式,但是当我整个看整个JFrame时,事情变得混乱。

我真的很想听听别人对此的看法。当你需要使用MVC模式向用户显示表格,几个字段和一些按钮时,你如何去做?

java swing model-view-controller
7个回答
101
投票

我强烈推荐给你的摇摆MVC的书是Freeman和Freeman的“Head First Design Patterns”。他们对MVC有非常全面的解释。

简要总结

  1. 您是用户 - 您与视图进行交互。视图是模型的窗口。当您对视图执行某些操作(例如单击“播放”按钮)时,视图会告诉控制器您执行了哪些操作。这是控制器的工作。
  2. 控制器要求模型改变其状态。控制器采取您的行动并解释它们。如果单击某个按钮,控制器的工作就是弄清楚这意味着什么,以及如何根据该操作操纵模型。
  3. 控制器还可以要求视图改变。当控制器从视图接收到操作时,可能需要告知视图作为结果进行更改。例如,控制器可以启用或禁用界面中的某些按钮或菜单项。
  4. 模型在状态发生变化时通知视图。当模型中的某些内容发生变化时,根据您采取的某些操作(如单击按钮)或其他一些内部更改(如播放列表中的下一首歌曲已启动),模型会通知视图其状态已更改。
  5. 视图询问模型的状态。视图获取直接从模型显示的状态。例如,当模型通知视图新歌曲已开始播放时,视图从模型中请求歌曲名称并显示它。该视图还可能要求模型状态为控制器请求视图中的某些更改的结果。

Source(如果你想知道什么是“奶油控制器”,想想一个奥利奥饼干,控制器是奶油中心,视图是顶部饼干,模型是底部饼干。)

嗯,如果你有兴趣,你可以从here下载一首关于MVC模式的相当有趣的歌!

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

编辑#1:

此外,将MVC视为各种模式的复合是很重要的。例如,您的模型可以使用Observer模式实现(需要将View注册为模型的观察者),而您的控制器可能使用策略模式。

编辑#2:

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


33
投票

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

我对MVC(我目前正在使用,迄今为止如此优秀)的想法是:

  1. 这是三个人中最愚蠢的观点。它对控制器和模型一无所知。它关注的只是摆动组件的前瞻性和布局。
  2. 模型也很愚蠢,但不像视图那样愚蠢。它执行以下功能。 一个。当控制器调用其中一个setter时,它会向其监听器/观察者发出通知(就像我说的那样,我会将这个角色转发给控制器)​​。我更喜欢SwingPropertyChangeSupport实现这一目标,因为它已经为此目的进行了优化。 湾数据库交互功能。
  3. 一个非常聪明的控制器非常了解视图和模型。控制器有两个功能: 一个。它定义了视图在用户与之交互时执行的操作。 湾它听取了模型。就像我所说的那样,当调用模型的setter时,模型会向控制器发出通知。解释此通知是控制器的工作。它可能需要反映视图的更改。

代码示例

风景 :

就像我说创建视图已经很冗长所以只需创建自己的实现:)

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

为了可测试性目的,将三者连接起来是理想的。我只提供了模型和控制器的实现。

该模型 :

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
    }
}

设置MVC的Main:

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);
        ...
    }
}

21
投票

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

  • 模型模型是呈现给用户的某事物的抽象。在摇摆中,您可以区分gui模型和数据模型。 GUI模型抽象了像ButtonModel这样的ui组件的状态。数据模型抽象ui呈现给用户的结构化数据,如TableModel
  • 视图视图是一个ui组件,负责向用户显示数据。因此,它负责所有与ui相关的问题,例如布局,绘图等。 JTable
  • 控制器控制器封装为了用户交互而执行的应用程序代码(鼠标移动,鼠标单击,按键等)。控制器可能需要输入以执行它们并产生输出。他们从模型中读取输入,并根据执行更新模型。他们也可能重组ui(例如,替换ui组件或显示完整的新视图)。但是,他们必须不了解ui compoenents,因为您可以将重组封装在控制器仅调用的单独接口中。在摆动中,控制器通常由ActionListenerAction实现。

  • 红色=模特儿
  • 绿色=视图
  • 蓝色=控制器

当点击Button时,它会调用ActionListenerActionListener仅取决于其他型号。它使用一些模型作为输入,其他模型作为结果或输出。它就像方法参数和返回值。模型在更新时通知ui。因此,控制器逻辑不需要知道ui组件。模型对象不知道ui。通知由观察者模式完成。因此,模型对象只知道有人想要在模型更改时收到通知。

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

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

MVC基础知识少于240行:

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;
    }

}

2
投票

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

然后你可以拥有Swing组件。 JTable将是其中一个观点(表格模型事实上将成为观点的一部分 - 它只会从“共享模型”转换为JTable)。

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

为共享模型添加侦听器(观察者)接口。某些组件(例如您的表)可以直接实现它。另一个观察者可以是协调按钮可用性等的控制器。


这是一种方法,但是如果它对你的用例来说太过分了,你当然可以简化或扩展它。

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

另一方面,您可以使用复杂的更新接口(CustomerUpdateListenerOrderItemListenerOrderCancellationListener)和专用控制器(或介体)来协调不同的视图。

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


0
投票

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

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

无论如何实现它,请记住JavaDoc您的类,方法和包,以便正确描述组件及其关系!


0
投票

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


0
投票

如果你开发一个带有GUI的程序,mvc pattern几乎就在那里但模糊了。

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

当您的代码可重用时,您知道自己拥有它。如果你已经正确实现了MVC,应该很容易实现具有相同功能的TUICLIRWDmobile first design。除了现有的代码之外,很容易看到它的实际完成情况。

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

我想这篇文章详细解释了它,从直接的非MVC模式(就像你将在Q&D上做的那样)到最终的可重用实现:

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

© www.soinside.com 2019 - 2024. All rights reserved.