java - 遵循传统(非中介)MVC模式的监听器放置

标签 java swing model-view-controller

我正在Swing中实现一个程序,并且已经阅读Nirmal's implementation of this pattern in Swing,这似乎显示了对整个“职责分离”概念的相当优雅的处理。

但是,由于我正在开发比Nirml发布的程序更复杂的程序,该程序由单个JFrame容器组成,因此我寻求有关如何正确实现MVC的指导。

我的程序将由子容器等组成。我对控制器应该如何实现定义和辅助View的所有侦听器背后的逻辑感到好奇,或者对于每个View组件定义侦听器的控制器是否实际可行?

看来我在View的顶级容器中需要一个方法,以允许Controller调用视图以将Listener添加到有问题的组件?因此,我需要一连串方法,每个方法都将侦听器从顶级容器传递到保存该组件的直接容器。最后是在容器上调用addActionListener()的方法。

这是在MVC中处理侦听器的正确方法吗?

在MVC中,是否必须通过Controller在View中定义每个组件的所有侦听器,还是一种有用的做法?这也意味着我要在顶级容器(视图)中创建方法,以使Controller能够为子容器中的每个单个组件分配侦听器?

最佳答案

好的,首先,Swing尽管已经以VC-M的形式实现了MVC的形式。这意味着您不应该尝试直接将Swing限制为纯MVC,因为您将非常失望,并花费大量时间尝试进行不应有的hack。

取而代之的是,您可以将MVC包裹在Swing周围,从而使其可以围绕API进行工作。

在我看来,控制器不需要知道也不应该关心视图或模型的实现方式,而应该关心它如何与它们一起工作(我有太多的开发人员掌握了UI组件)并对他们进行不该做的事情,并在更改实现时破坏了API。最好隐藏这种细节)

从这个角度来看,您可以将视图视为独立的实体-它具有控件并且它的工作独立于控制器。控制器不在乎实现细节。它真正关心的是获取信息,并在合同描述的某个事件发生时被告知。它不必在乎它是如何生成的。

例如,假设您有一个登录视图。控制器仅想知道用户输入的用户名和密码以及何时应验证该信息。

假设您实现视图/控制器来公开JTextFieldJPasswordField,但是稍后,您的用户希望将用户名选择限制为特定列表(可能由模型提供)。现在,您已经将实现细节卡在了控制器中,这些细节不再适用,并且您必须为此新用例手动更改或创建新的MVC。

相反,如果您只是简单地声明视图具有用于获取用户名和密码的getter以及某种事件侦听器,该侦听器会告诉控制器用户何时希望验证凭据,该怎么办?现在,您只需要提供一个新视图,而无需修改控制器。控制器将不在乎如何生成这些值。

至于问题的更大范围。


  我的程序将由子容器等组成。我好奇
  关于控制器应如何实施背后的逻辑
  定义并分配View的所有侦听器。
  定义每个View组件的侦听器的控制器是
  甚至实用?
  
  看来我在View的顶层需要一个方法
  允许Controller调用视图以添加侦听器的容器
  到有问题的组件?所以我需要一系列方法
  每个都从顶级容器向下传递到侦听器,
  立即容纳部件的容器。
  容器在其上调用addActionListener()。
  
  这是在MVC中处理侦听器的正确方法吗?


普遍的答案是,不,这不是正确的方法。

每个子视图都将成为其自己的MVC,并专注于自己的需求。父MVC可能使用子MVC提供的事件或其他功能来进行更新,甚至修改其他子MVC的状态。

这里要记住的重要一点是,一个视图可以充当其他视图的控制器,尽管您可以选择拥有一系列允许该视图管理的控制器。

想象一下类似“向导”的东西。它具有许多步骤,可从用户那里收集各种信息,每个步骤都必须有效,然后才能继续进行下一个步骤。

现在,您可能很想将导航直接集成到其中,但是更好的主意是将导航详细信息作为其自己的MVC进行分离。

当向导被询问时,将向用户显示一个步骤,用户将填写信息,可能触发事件。然后,这些事件将使导航MVC可以确定用户是否可以转到下一步或上一步。

这两个MVC将由第三个“主” MVC控制,这将有助于管理状态(从向导中侦听事件并更新导航状态)

让我们尝试一个示例,其中包含一个问题,一个测验!

测验有问题,每个问题都有提示,正确答案,一系列可能的答案,我们还希望存储用户的结果答案。

测验API

因此,在下面,我们有了测验MVC的基本轮廓,我们有一个问题,该问题由模型管理,有一个控制器,一个视图和一系列观察者(侦听器)

合同(界面)

public interface Question {
    public String getPrompt();
    public String getCorrectAnswer();
    public String getUserAnswer();
    public String[] getOptions();
    public boolean isCorrect();

}

/**
* This is a deliberate choice to separate the update functionality
* No one but the model should ever actually -apply- the answer to the
* question
*/
public interface MutableQuestion extends Question {
    public void setUserAnswer(String userAnswer);
}

public interface QuizModel {
    public void addQuizObserver(QuizModelObserver observer);
    public void removeQuizObserver(QuizModelObserver observer);
    public Question getNextQuestion();
    public Question getCurrentQuestion();
    public int size();
    public int getScore();
    public void setUserAnswerFor(Question question, String answer);
}

public interface QuizModelObserver {
    public void didStartQuiz(QuizModel quiz);
    public void didCompleteQuiz(QuizModel quiz);
    public void questionWasAnswered(QuizModel model, Question question);
}

public interface QuizView extends View {
    public void setQuestion(Question question);
    public boolean hasAnswer();
    public String getUserAnswer();
    public void addQuizObserver(QuizViewObserver observer);
    public void removeQuizObserver(QuizViewObserver observer);
}

public interface QuizViewObserver {
    public void userDidChangeAnswer(QuizView view);
}

public interface QuizController {
    public QuizModel getModel(); // This is the model
    public QuizView getView();
    public void askNextQuestion();
}


我个人遵循“代码到接口(而不是实现)”的原则,我也故意将这一想法付诸实践,以证明这一点。

如果仔细观察,您会发现视图或模型之间实际上没有任何关系。全部通过控制器控制

我在这里所做的一件事是为控制器提供askNextQuestion,因为控制器不知道何时应该发生(您可能会考虑使用userDidChangeAnswer,但这意味着仅用户试图回答这个问题,有点意思)

实施

现在,通常情况下,我喜欢安排一些abstract实现来填充“通用”功能,而我大部分时间都放弃了,直接使用默认实现,这主要是出于演示目的。

public class DefaultQuestion implements MutableQuestion {

    private final String prompt;
    private final String correctAnswer;
    private String userAnswer;
    private final String[] options;

    public DefaultQuestion(String prompt, String correctAnswer, String... options) {
        this.prompt = prompt;
        this.correctAnswer = correctAnswer;
        this.options = options;
    }

    @Override
    public String getPrompt() {
        return prompt;
    }

    @Override
    public String getCorrectAnswer() {
        return correctAnswer;
    }

    @Override
    public String getUserAnswer() {
        return userAnswer;
    }

    @Override
    public String[] getOptions() {
        List<String> list = new ArrayList<>(Arrays.asList(options));
        Collections.shuffle(list);
        return list.toArray(new String[list.size()]);
    }

    public void setUserAnswer(String userAnswer) {
        this.userAnswer = userAnswer;
    }

    @Override
    public boolean isCorrect() {
        return getCorrectAnswer().equals(getUserAnswer());
    }

}

public abstract class AbstractQuizModel implements QuizModel {

    private List<QuizModelObserver> observers;

    public AbstractQuizModel() {
        observers = new ArrayList<>(25);
    }

    @Override
    public void addQuizObserver(QuizModelObserver observer) {
        observers.add(observer);
    }

    @Override
    public void removeQuizObserver(QuizModelObserver observer) {
        observers.remove(observer);
    }

    protected void fireDidStartQuiz() {
        for (QuizModelObserver observer : observers) {
            observer.didStartQuiz(this);
        }
    }

    protected void fireDidCompleteQuiz() {
        for (QuizModelObserver observer : observers) {
            observer.didCompleteQuiz(this);
        }
    }

    protected void fireQuestionWasAnswered(Question question) {
        for (QuizModelObserver observer : observers) {
            observer.questionWasAnswered(this, question);
        }
    }

}

public class DefaultQuizModel extends AbstractQuizModel {

    private List<MutableQuestion> questions;
    private Iterator<MutableQuestion> iterator;

    private MutableQuestion currentQuestion;
    private boolean completed;

    private int score;

    public DefaultQuizModel() {
        questions = new ArrayList<>(50);
    }

    public void add(MutableQuestion question) {
        questions.add(question);
    }

    public void remove(MutableQuestion question) {
        questions.remove(question);
    }

    @Override
    public Question getNextQuestion() {
        if (!completed && iterator == null) {
            iterator = questions.iterator();
            fireDidStartQuiz();
        }
        if (iterator.hasNext()) {
            currentQuestion = iterator.next();
        } else {
            completed = true;
            iterator = null;
            currentQuestion = null;
            fireDidCompleteQuiz();
        }
        return currentQuestion;
    }

    @Override
    public Question getCurrentQuestion() {
        return currentQuestion;
    }

    @Override
    public int size() {
        return questions.size();
    }

    @Override
    public int getScore() {
        return score;
    }

    @Override
    public void setUserAnswerFor(Question question, String answer) {
        if (question instanceof MutableQuestion) {
            ((MutableQuestion) question).setUserAnswer(answer);
            if (question.isCorrect()) {
                score++;
            }
            fireQuestionWasAnswered(question);
        }
    }

}

public class DefaultQuizController implements QuizController {

    private QuizModel model;
    private QuizView view;

    public DefaultQuizController(QuizModel model, QuizView view) {
        this.model = model;
        this.view = view;
    }

    @Override
    public QuizModel getModel() {
        return model;
    }

    @Override
    public QuizView getView() {
        return view;
    }

    @Override
    public void askNextQuestion() {
        Question question = getModel().getCurrentQuestion();
        if (question != null) {
            String answer = getView().getUserAnswer();
            getModel().setUserAnswerFor(question, answer);
        }
        question = getModel().getNextQuestion();
        getView().setQuestion(question);
    }

}

public class DefaultQuizViewPane extends JPanel implements QuizView {

    private final JLabel question;
    private final JPanel optionsPane;
    private final ButtonGroup bg;

    private final List<JRadioButton> options;
    private String userAnswer;

    private final List<QuizViewObserver> observers;

    private final AnswerActionListener answerActionListener;

    private final GridBagConstraints optionsGbc;

    protected DefaultQuizViewPane() {

        setBorder(new EmptyBorder(4, 4, 4, 4));

        question = new JLabel();
        optionsPane = new JPanel(new GridBagLayout());
        optionsPane.setBorder(new EmptyBorder(4, 4, 4, 4));

        answerActionListener = new AnswerActionListener();

        optionsGbc = new GridBagConstraints();
        optionsGbc.gridwidth = GridBagConstraints.REMAINDER;
        optionsGbc.weightx = 1;
        optionsGbc.anchor = GridBagConstraints.WEST;

        options = new ArrayList<>(25);

        bg = new ButtonGroup();

        observers = new ArrayList<>(25);

        setLayout(new BorderLayout());

        add(question, BorderLayout.NORTH);
        add(optionsPane);

    }

    protected void reset() {
        question.setText(null);
        for (JRadioButton rb : options) {
            rb.removeActionListener(answerActionListener);
            bg.remove(rb);
            optionsPane.remove(rb);
        }
        options.clear();
    }

    @Override
    public void setQuestion(Question question) {
        reset();
        if (question != null) {
            this.question.setText(question.getPrompt());

            for (String option : question.getOptions()) {
                JRadioButton rb = makeRadioButtonFor(option);
                options.add(rb);
                optionsPane.add(rb, optionsGbc);
            }
            optionsPane.revalidate();
            revalidate();
            repaint();
        }
    }

    @Override
    public void addQuizObserver(QuizViewObserver observer) {
        observers.add(observer);
    }

    @Override
    public void removeQuizObserver(QuizViewObserver observer) {
        observers.remove(observer);
    }

    protected void fireUserDidChangeAnswer() {
        for (QuizViewObserver observer : observers) {
            observer.userDidChangeAnswer(this);
        }
    }

    protected JRadioButton makeRadioButtonFor(String option) {
        JRadioButton btn = new JRadioButton(option);
        btn.addActionListener(answerActionListener);
        bg.add(btn);

        return btn;
    }

    @Override
    public boolean hasAnswer() {
        return userAnswer != null;
    }

    @Override
    public String getUserAnswer() {
        return userAnswer;
    }

    @Override
    public JComponent getViewComponent() {
        return this;
    }

    protected class AnswerActionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            userAnswer = e.getActionCommand();
            fireUserDidChangeAnswer();
        }

    }

}


这里真的没什么好看的。唯一感兴趣的是控制器如何管理模型与视图之间的事件

导航API

导航API非常基本。它使您可以控制用户是否可以实际导航到下一个或上一个元素(如果应将操作提供给用户),以及在任何时候禁用任何一个操作

(再次,我一直专注于一个简单的设计,实际上,最好能对修改模型的状态进行一些控制,以更改导航的工作方向,但是我故意将其保留以保持很简单)

合同(界面)

public enum NavigationDirection {
    NEXT, PREVIOUS;
}

public interface NavigationModel {
    public boolean canNavigate(NavigationDirection direction);

    public void addObserver(NavigationModelObserver observer);
    public void removeObserver(NavigationModelObserver observer);

    public void next();
    public void previous();

}

public interface NavigationModelObserver {
    public void next(NavigationModel view);
    public void previous(NavigationModel view);
}

public interface NavigationController {
    public NavigationView getView();
    public NavigationModel getModel();

    public void setDirectionEnabled(NavigationDirection navigationDirection, boolean b);
}

public interface NavigationView extends View {

    public void setNavigatable(NavigationDirection direction, boolean navigtable);
    public void setDirectionEnabled(NavigationDirection direction, boolean enabled);

    public void addObserver(NavigationViewObserver observer);
    public void removeObserver(NavigationViewObserver observer);
}

public interface NavigationViewObserver {
    public void next(NavigationView view);
    public void previous(NavigationView view);
}


实施

public static class DefaultNavigationModel implements NavigationModel {

    private Set<NavigationDirection> navigatableDirections;
    private List<NavigationModelObserver> observers;

    public DefaultNavigationModel() {
        this(true, true);
    }

    public DefaultNavigationModel(boolean canNavigateNext, boolean canNavigatePrevious) {
        navigatableDirections = new HashSet<>(2);
        observers = new ArrayList<>(25);
        setCanNavigate(NavigationDirection.NEXT, canNavigateNext);
        setCanNavigate(NavigationDirection.PREVIOUS, canNavigatePrevious);
    }

    public void setCanNavigate(NavigationDirection direction, boolean canNavigate) {
        if (canNavigate) {
            navigatableDirections.add(direction);
        } else {
            navigatableDirections.remove(direction);
        }
    }

    @Override
    public boolean canNavigate(NavigationDirection direction) {
        return navigatableDirections.contains(direction);
    }

    @Override
    public void addObserver(NavigationModelObserver observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(NavigationModelObserver observer) {
        observers.remove(observer);
    }

    protected   void fireMoveNext() {
        for (NavigationModelObserver observer : observers) {
            observer.next(this);
        }
    }

    protected   void fireMovePrevious() {
        for (NavigationModelObserver observer : observers) {
            observer.previous(this);
        }
    }

    @Override
    public void next() {
        fireMoveNext();
    }

    @Override
    public void previous() {
        fireMovePrevious();
    }

}

public static class DefaultNavigationController implements NavigationController {

    private final NavigationModel model;
    private final NavigationView view;

    public DefaultNavigationController(NavigationModel model, NavigationView view) {
        this.model = model;
        this.view = view;

        view.setNavigatable(NavigationDirection.NEXT, model.canNavigate(NavigationDirection.NEXT));
        view.setNavigatable(NavigationDirection.PREVIOUS, model.canNavigate(NavigationDirection.PREVIOUS));

        view.addObserver(new NavigationViewObserver() {
            @Override
            public void next(NavigationView view) {
                if (getModel().canNavigate(NavigationDirection.NEXT)) {
                    getModel().next();
                }
            }

            @Override
            public void previous(NavigationView view) {
                if (getModel().canNavigate(NavigationDirection.PREVIOUS)) {
                    getModel().previous();
                }
            }
        });
    }

    @Override
    public NavigationView getView() {
        return view;
    }

    @Override
    public NavigationModel getModel() {
        return model;
    }

    @Override
    public void setDirectionEnabled(NavigationDirection navigationDirection, boolean enabled) {
        getView().setDirectionEnabled(navigationDirection, enabled);
    }

}

public static class DefaultNavigationViewPane extends JPanel implements NavigationView {

    private final List<NavigationViewObserver> observers;

    private final JButton btnNext;
    private final JButton btnPrevious;

    public DefaultNavigationViewPane() {
        btnNext = new JButton("Next >");
        btnNext.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireMoveNext();
            }
        });
        btnPrevious = new JButton("< Previous");
        btnPrevious.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fireMovePrevious();
            }
        });
        setLayout(new FlowLayout(FlowLayout.RIGHT));

        add(btnPrevious);
        add(btnNext);

        observers = new ArrayList<>();
    }

    @Override
    public void addObserver(NavigationViewObserver observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(NavigationViewObserver observer) {
        observers.remove(observer);
    }

    protected void fireMoveNext() {
        for (NavigationViewObserver observer : observers) {
            observer.next(this);
        }
    }

    protected void fireMovePrevious() {
        for (NavigationViewObserver observer : observers) {
            observer.previous(this);
        }
    }

    @Override
    public JComponent getViewComponent() {
        return this;
    }

    @Override
    public void setNavigatable(NavigationDirection direction, boolean navigtable) {
        switch (direction) {
            case NEXT:
                btnNext.setVisible(navigtable);
                break;
            case PREVIOUS:
                btnPrevious.setVisible(navigtable);
                break;
        }
    }

    @Override
    public void setDirectionEnabled(NavigationDirection direction, boolean enabled) {
        switch (direction) {
            case NEXT:
                btnNext.setEnabled(enabled);
                break;
            case PREVIOUS:
                btnPrevious.setEnabled(enabled);
                break;
        }
    }

}


测验大师

现在,这是两个截然不同的API,它们没有共同点,因此,我们需要某种控制器来桥接它们

合同(接口)

public interface QuizMasterController {
    public QuizController getQuizController();
    public NavigationController getNavigationController();
    public QuizMasterView getView();
}

public interface QuizMasterView extends View {
    public NavigationController getNavigationController();
    public QuizController getQuizController();
    public void showScoreView(int score, int size);
    public void showQuestionAndAnswerView();
}


好的,所以您可能会问自己一个明显的问题,模型在哪里?嗯,它不需要一个,它只是导航和测验API之间的桥梁,它不管理自己的任何数据...

实施

public class DefaultQuizMasterController implements QuizMasterController {

    private QuizController quizController;
    private NavigationController navController;

    private QuizMasterView view;

    public DefaultQuizMasterController(QuizController quizController, NavigationController navController) {
        this.quizController = quizController;
        this.navController = navController;

        view = new DefaultQuizMasterViewPane(quizController, navController);

        // Setup the initial state
        quizController.askNextQuestion();

        navController.getModel().addObserver(new NavigationModelObserver() {
            @Override
            public void next(NavigationModel view) {
                getQuizController().askNextQuestion();
                getNavigationController().setDirectionEnabled(NavigationDirection.NEXT, false);
            }

            @Override
            public void previous(NavigationModel view) {
                // NOOP
            }
        });

        quizController.getView().addQuizObserver(new QuizViewObserver() {
            @Override
            public void userDidChangeAnswer(WizeQuiz.QuizView view) {
                getNavigationController().setDirectionEnabled(NavigationDirection.NEXT, true);
            }
        });

        quizController.getModel().addQuizObserver(new QuizModelObserver() {
            @Override
            public void didStartQuiz(QuizModel quiz) {
                getView().showQuestionAndAnswerView();
            }

            @Override
            public void didCompleteQuiz(QuizModel quiz) {
                getView().showScoreView(quiz.getScore(), quiz.size());
                getNavigationController().setDirectionEnabled(NavigationDirection.NEXT, false);
            }

            @Override
            public void questionWasAnswered(QuizModel model, Question question) {
            }
        });

        navController.setDirectionEnabled(NavigationDirection.NEXT, false);
    }

    @Override
    public QuizController getQuizController() {
        return quizController;
    }

    @Override
    public NavigationController getNavigationController() {
        return navController;
    }

    @Override
    public QuizMasterView getView() {
        return view;
    }

}

public class DefaultQuizMasterViewPane extends JPanel implements QuizMasterView {

    private QuizController quizController;
    private NavigationController navController;

    private QuestionAndAnswerView qaView;
    private ScoreView scoreView;

    private CardLayout cardLayout;

    public DefaultQuizMasterViewPane(QuizController quizController, NavigationController navController) {
        this.quizController = quizController;
        this.navController = navController;

        quizController.getModel().addQuizObserver(new QuizModelObserver() {
            @Override
            public void didStartQuiz(QuizModel quiz) {
            }

            @Override
            public void didCompleteQuiz(QuizModel quiz) {
            }

            @Override
            public void questionWasAnswered(QuizModel model, Question question) {
                qaView.updateScore();
            }
        });

        scoreView = new ScoreView();
        qaView = new QuestionAndAnswerView();

        qaView.updateScore();

        cardLayout = new CardLayout();
        setLayout(cardLayout);

        add(qaView, "view.qa");
        add(scoreView, "view.score");
    }

    @Override
    public JComponent getViewComponent() {
        return this;
    }

    @Override
    public NavigationController getNavigationController() {
        return navController;
    }

    @Override
    public QuizController getQuizController() {
        return quizController;
    }

    @Override
    public void showScoreView(int score, int size) {
        scoreView.updateScore();
        cardLayout.show(this, "view.score");
    }

    @Override
    public void showQuestionAndAnswerView() {
        cardLayout.show(this, "view.qa");
    }

    protected class QuestionAndAnswerView extends JPanel {

        private JLabel score;

        public QuestionAndAnswerView() {

            setLayout(new BorderLayout());

            add(getQuizController().getView().getViewComponent());

            JPanel south = new JPanel(new BorderLayout());
            south.add(getNavigationController().getView().getViewComponent(), BorderLayout.SOUTH);

            score = new JLabel();
            score.setHorizontalAlignment(JLabel.RIGHT);
            south.add(score, BorderLayout.NORTH);

            add(south, BorderLayout.SOUTH);
        }

        protected void updateScore() {
            score.setText(getQuizController().getModel().getScore() + "/" + getQuizController().getModel().size());
        }

    }

    protected class ScoreView extends JPanel {

        private JLabel score;

        public ScoreView() {

            setLayout(new GridBagLayout());
            score = new JLabel("You scored:");
            add(score);

        }

        protected void updateScore() {
            score.setText("You scored: " + getQuizController().getModel().getScore() + "/" + getQuizController().getModel().size());
        }

    }
}


现在,实现很有趣,它实际上具有两个“状态”或“视图”,即“问答”视图和“得分视图”。同样,这是故意的,因为我真的不想要另一个MVC。问答视图已通过任何方式管理两个MVC:P

基本上,这是在用户更改问题答案时监视测验API,然后告诉导航API可以移至下一个问题。它还监视开始事件和完成事件,并提供这些状态所需的视图。

它还监视导航API的导航事件。在此示例中,我们只能沿单个方向移动,即使导航API已配置为执行其他操作,测验API也不提供该功能

把它放在一起

现在,我选择有意地分别构建每个部分,可以想象,您可以让QuizMasterController本身构建Navigation API,因为它知道测验API仅允许向前导航,同样,我们可以更改导航API为了允许通过模型或更改模型来修改这些状态​​,这些都是可行的解决方案,我只是举了一个直接的例子。

NavigationModel navigationModel = new DefaultNavigationModel(true, false);
NavigationView navigationView = new DefaultNavigationViewPane();
NavigationController navigationController = new NavWiz.DefaultNavigationController(navigationModel, navigationView);

DefaultQuizModel quizModel = new DefaultQuizModel();
quizModel.add(new DefaultQuestion(
                "Which pop duo was the first western band to play in The Peoples Republic of China?",
                "Wham",
                "Wham", "Simon and Garfunkel", "Chas and Dave", "Right Said Fred"));
quizModel.add(new DefaultQuestion(
                "Timber selected from how many fully grown oak trees were needed to build a large 3 decker Royal Navy battle ship in the 18th century?",
                "3,500",
                "50", "500", "1,500", "3,500"));
quizModel.add(new DefaultQuestion(
                "Speed skating originated in which country?",
                "Netherlands",
                "Russia", "Netherlands", "Canada", "Norway"));
quizModel.add(new DefaultQuestion(
                "Off the coast of which country did the Amoco Cadiz sink?",
                "France",
                "South Africa", "France", "USA", "Spain"));
quizModel.add(new DefaultQuestion(
                "The song 'An Englishman in New York' was about which man?",
                "Quentin Crisp",
                "Quentin Crisp", "Sting", "John Lennon", "Gordon Sumner"));
QuizView quizView = new DefaultQuizViewPane();
QuizController quizController = new DefaultQuizController(quizModel, quizView);

QuizMasterController quizMasterController = new DefaultQuizMasterController(quizController, navigationController);

JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(quizMasterController.getView().getViewComponent());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);


最后我们得到类似...

Quiz Quiz Quiz

即使不是很粗略,这也不是什么,但是它旨在为您提供一些想法,帮助您了解如何完成复杂的复合MVC。

关于java - 遵循传统(非中介)MVC模式的监听器放置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31602113/

相关文章:

java - 使用 double 和 float 计算不准确

java - Java 8中列表的二级过滤

java - 如何使用 Swing 通过 Java 桌面应用程序上的图标运行 JAR 文件?

java - JButton 位于类中,actionlistener 位于 main 中

delphi - 您知道 Win32 Delphi 应用程序的 MVC 框架吗?

c++ - Qt - 首选使用什么 - 小部件或 View ? (树、表、列表)

java - 在多线程环境中用 C# 和 Java 编写同一程序时获得不同的输出

java - StringEscapeUtils 不处理 utf-8

Java UIManager 键列表

c# - 你如何命名你的 ViewModel 类?