MVC模式和SWING

sbr*_*tla 73 java model-view-controller swing

我认为最难以真正掌握"真实SWING生活"的设计模式之一是MVC模式.我已经浏览了这个讨论模式的网站上的一些帖子,但我仍然觉得我不清楚如何利用我的(Java SWING)应用程序中的模式.

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

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

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

Dhr*_*ola 103

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

简要总结

  1. 您是用户 - 您与视图进行交互.视图是模型的窗口.当您对视图执行某些操作(例如单击"播放"按钮)时,视图会告诉控制器您执行了哪些操作.这是控制器的工作.

  2. 控制器要求模型改变其状态.控制器采取您的行动并解释它们.如果单击某个按钮,控制器的工作就是弄清楚这意味着什么,以及如何根据该操作操纵模型.

  3. 控制器还可以要求视图改变.当控制器从视图接收到操作时,可能需要告知视图作为结果进行更改.例如,控制器可以启用或禁用界面中的某些按钮或菜单项.

  4. 模型在状态发生变化时通知视图.当模型中的某些内容发生变化时,根据您采取的某些操作(如单击按钮)或其他一些内部更改(如播放列表中的下一首歌曲已启动),模型会通知视图其状态已更改.

  5. 视图询问模型的状态.视图获取直接从模型显示的状态.例如,当模型通知视图新歌曲已开始播放时,视图从模型中请求歌曲名称并显示它.该视图还可能要求模型状态为控制器请求视图中的某些更改的结果.

在此输入图像描述 来源(如果您想知道什么是"奶油控制器",想想一个奥利奥饼干,控制器是奶油中心,视图是顶部饼干,模型是底部饼干.)

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

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

编辑#1:

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

编辑#2:

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

  • @DhruvGairola第二点描述是针对第三点,第三点是针对点具有相同的重复描述.你能纠正他们吗? (2认同)

Bnr*_*rdo 34

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

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

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

代码示例

风景 :

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

interface View{
    JTextField getTxtFirstName();
    JTextField getTxtLastName();
    JTextField getTxtAddress();
}
Run Code Online (Sandbox Code Playgroud)

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

该模型 :

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
    ...
}
Run Code Online (Sandbox Code Playgroud)

控制者:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

设置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);
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这不是[MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)架构模式,但**密切相关**[MVP](http ://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter)(Model-View-Presenter)模式.在典型的MVC中,**正是模型的工作是在视图发生变化时通知视图**,正是您"不喜欢"的视图.看看[这个图表](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller#mediaviewer/File:MVC-Process.svg)看看典型的互动情况MVC的工作. (8认同)
  • 有趣但是当在多个视图中显示单个实体模型时效率较低......那么您的设计可能会导致"大控制器"处理单个模型但管理所有相关视图.如果你试图重用一组"小模型",它会变得更加棘手,这要归功于聚合成"大型模型",因为视图显示了在多个"小模型"实体中分派的信息. (4认同)

Ren*_*ink 22

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

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

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

在此输入图像描述

Button点击它调用ActionListener.该ActionListener只依赖于其他车型.它使用一些模型作为输入,其他模型作为结果或输出.它就像方法参数和返回值.模型在更新时通知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;
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个答案+1,提到`Action`作为`Controller`实际上我猜所有`EventListener`都是控制器.. (4认同)