A Controller creates a new JFrame with a button. Using the actionListener is getting the order correctly but the action is asked to perform after is not working. Is asked to change a button name literal on the view but it never happens. Otherwise with debugging the function seems tu run but there is no change on the view.
With Java are implemented the following classes:
public class Window extends JFrame {
JButton bGoFile;
public static final String FILE = "FILE";
public Window(ActionListener actionListener) {
this.actionListener = actionListener;
setupButtons();
setupView();
}
private void setupButtons() {
bGoFile = new JButton("Button");
bGoFile.addActionListener(actionListener);
bGoFile.setActionCommand(GO_FILE);
}
private void setupView() {
setTitle("Cover pdf to img");
setBounds(300, 90, 900, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(false);
setVisible(true);
ImageIcon icon = new ImageIcon("./src/resources/logo.jpg");
setIconImage(icon.getImage());
JPanel jp = new JPanel;
jp.add(bGoFile);
add(jp);
}
public void changeButtonName(String name) {
bGoFile.setText(name);
System.out.println("Name should be changed.");
// Here I already tried to user repaint() but with no result.
}
public class WindowController implements ActionListener {
Window window;
public WindowController() {
this.window = new Window(this);
}
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Action performed");
switch (e.getActionCommand()) {
case GO_FILE -> {
synchronized (this) {
window.changeButtonName("New name");
break;
}
}
}
}
The point is that the both prints on terminal are shown but the button name on the running Window is not changing.
You shouldn't be creating an instance of the view in the controller. This should be passed to the controller (AKA using dependency injection).
You should also be making use of interfaces, as the controller should not be bound to an implementation of a view (or model), but should be working through an established series of contracts and observers.
So, let's start with some basics...
public interface View {
public JComponent getView();
}
public interface Controller<V extends View> {
public V getView();
}
I did say basic. But, working with these interfaces directly will become tedious really fast, so let's add some helpers...
public abstract class AbstractController<V extends View> implements Controller<V> {
private V view;
public AbstractController(V view) {
this.view = view;
}
#Override
public V getView() {
return view;
}
}
public abstract class AbstractView extends JPanel implements View {
#Override
public JComponent getView() {
return this;
}
}
Nothing special, but this takes care of the a lot of boiler plating.
Next, we want to define the contract of our view...
public interface MainView extends View {
public interface Observer {
public void didPerformGoFile(MainView view);
}
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
public void setDescription(String description);
}
That's pretty simple. Note though, this does not describe any kind of implementation detail. The contract does not care how didPerformGoFile might be generated, only that the action can be observed by interested parties
Next, we want to define or implementations for the MainView...
public class DefaultMainView extends AbstractView implements MainView {
private List<Observer> observers = new ArrayList<>(8);
private JButton goFileButton;
private JLabel descriptionLabel;
public DefaultMainView() {
goFileButton = new JButton("Make it so");
descriptionLabel = new JLabel("...");
descriptionLabel.setHorizontalAlignment(JLabel.CENTER);
setBorder(new EmptyBorder(32, 32, 32, 32));
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = gbc.REMAINDER;
add(goFileButton, gbc);
add(descriptionLabel, gbc);
goFileButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fireDidPerformGoFile();
}
});
}
#Override
public void addObserver(Observer observer) {
observers.add(observer);
}
#Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
protected void fireDidPerformGoFile() {
for (Observer observer : observers) {
observer.didPerformGoFile(this);
}
}
#Override
public void setDescription(String description) {
descriptionLabel.setText(description);
}
}
And MainController....
public class MainViewController extends AbstractController<MainView> {
public MainViewController(MainView view) {
super(view);
view.addObserver(new MainView.Observer() {
#Override
public void didPerformGoFile(MainView view) {
view.setDescription("Go file!");
}
});
}
}
Now, we can put them together and run them...
JFrame frame = new JFrame();
Controller controller = new MainViewController(new DefaultMainView());
frame.add(controller.getView().getView());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Now, you're probably sitting there thinking, "that's a lot of work for little gain" and you'd be ... wrong, actually.
Let's say you wanted to change how the controller responds to the goFileAction depending on some kind of state, like the user's credentials or something. You could put a lot of logic into the MainViewController to handle it, or, more easily, just create a different controller altogether (nb: This is where the model in "Model-View-Controller" would come in, but since there's no concept of model in your example, I've done it "differently")
public class OverlordController extends AbstractController<MainView> {
public OverlordController(MainView view) {
super(view);
view.addObserver(new MainView.Observer() {
#Override
public void didPerformGoFile(MainView view) {
view.setDescription("Your overload has spoken!");
}
});
}
}
Then, by simply changing...
Controller controller = new MainViewController(new DefaultMainView());
to
Controller controller = new OverlordController(new DefaultMainView());
you change the output!
"Model-View-Controller" is not as straight forward in Swing as it might be in other APIs/frameworks, this is because Swing is already based on MVC, so you're actually wrapping a MVC on a MVC. If you understand this, you can make it work more easily.
For example, above, I don't expose the ActionListener to the controller, instead I created my own observer which described the actual actions which might be triggered by implementations of the view. The actual action handling took place in the implementation of the view itself.
This is good in the fact that we've decoupled the workflow, it also means that the view is free to implement the triggers for these actions in any way it sees fit.
You might want to also take a look at:
Implementing the Controller part of MVC in Java Swing
How MVC work with java swing GUI
Java and GUI - Where do ActionListeners belong according to MVC pattern?
What is the correct way of Message Passing between Classes in MVC?
Listener Placement Adhering to the Traditional (non-mediator) MVC Pattern
JTextField input fails to update output in TextView in MVC
Multithreaded MVC to recreate plane dashboard with multiple independent gauges
Where to store model objects in MVC design?
Runnable example...
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public final class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
Controller controller = new OverlordController(new DefaultMainView());
frame.add(controller.getView().getView());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface View {
public JComponent getView();
}
public interface Controller<V extends View> {
public V getView();
}
public abstract class AbstractController<V extends View> implements Controller<V> {
private V view;
public AbstractController(V view) {
this.view = view;
}
#Override
public V getView() {
return view;
}
}
public abstract class AbstractView extends JPanel implements View {
#Override
public JComponent getView() {
return this;
}
}
public interface MainView extends View {
public interface Observer {
public void didPerformGoFile(MainView view);
}
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
public void setDescription(String description);
}
public class MainViewController extends AbstractController<MainView> {
public MainViewController(MainView view) {
super(view);
view.addObserver(new MainView.Observer() {
#Override
public void didPerformGoFile(MainView view) {
view.setDescription("Go file!");
}
});
}
}
public class OverlordController extends AbstractController<MainView> {
public OverlordController(MainView view) {
super(view);
view.addObserver(new MainView.Observer() {
#Override
public void didPerformGoFile(MainView view) {
view.setDescription("Your overload has spoken!");
}
});
}
}
public class DefaultMainView extends AbstractView implements MainView {
private List<Observer> observers = new ArrayList<>(8);
private JButton goFileButton;
private JLabel descriptionLabel;
public DefaultMainView() {
goFileButton = new JButton("Make it so");
descriptionLabel = new JLabel("...");
descriptionLabel.setHorizontalAlignment(JLabel.CENTER);
setBorder(new EmptyBorder(32, 32, 32, 32));
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = gbc.REMAINDER;
add(goFileButton, gbc);
add(descriptionLabel, gbc);
goFileButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
fireDidPerformGoFile();
}
});
}
#Override
public void addObserver(Observer observer) {
observers.add(observer);
}
#Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
protected void fireDidPerformGoFile() {
for (Observer observer : observers) {
observer.didPerformGoFile(this);
}
}
#Override
public void setDescription(String description) {
descriptionLabel.setText(description);
}
}
}
So what I am trying to accomplish is to add ActionListener to a button which is defined in another class, without breaking encapsulation of this button.
My GUI class:
public class GUI extends JFrame {
private JButton button;
public GUI () {
this.button = new JButton ();
}
public void setText (Text text) {
this.button.setText (text);
}
public JButton getButton () {
return this.button;
}
}
My Game class:
public class Game {
private GUI gui;
public Game () {
this.gui = new GUI ();
this.gui.getButton ().addActionListener (new ActionListener () {
public void actionPerformed (ActionEvent evt) {
play ();
}
});
}
public void play () {
this.gui.setText ("Play");
}
}
Then I call a new Game instance in the Main class.
I would like to get rid of the getter in GUI class, otherwise there is no point in using text setter or setters similar to that.
When I add ActionListener to GUI constructor, I have no access to Game methods than. Is there a solution that I don't see?
Normally when I do this, I add an interface that describes the View (GUI), and then have the view implement that interface.
public interface MyView {
void addActionListener( ActionListener l );
}
And the view:
public class GameGui implements MyView {
// lots o' stuff
public void addActionListener( ActionListener l ) {
button.addActionListener( l );
}
}
Then your main code is free from dependencies on what kind of view you actually implement.
public class Main {
public static void main( String... args ) {
SwingUtils.invokeLater( Main::startGui );
}
public static void startGui() {
MyView gui = new GameGui();
gui.addActionListener( ... );
}
}
Don't forget that Swing is not thread safe and must be invoked on the EDT.
Let the GUI add the action listener to the button, let the Game create the action listener:
public class GUI extends JFrame {
public void addActionListenerToButton(ActionListener listener) {
button.addActionListener(listener);
}
....
}
public class Game {
private GUI gui;
public Game () {
this.gui = new GUI ();
this.gui.addActionListenerToButton (new ActionListener () {
public void actionPerformed (ActionEvent evt) {
play ();
}
});
}
...
}
Alternatively just pass in a functional interface instead of a fully built ActionListener.
I'm implementing a program within Swing, and I've read Nirmal's implementation of this pattern in Swing, which seems to show a rather elegant handling of the whole "separation of responsibilities" concept.
However, as I'm developing a more complicated program than the one posted by Nirml, which consists of a single JFrame container, I look for guidance as how to implement MVC properly.
My program will be consisting of sub-containers and such. I am curious as to how the Controller is supposed to implement the logic behind defining and assigining all the listeners of the View.. or if the controller defining the listeners for every single View component is even practical?
It would seem that I would need a method in the View's top-level container to allow the Controller to invoke the view to add a Listener to the component in question? and so I would need a chain of methods, each passing down the listener from the top-level container, to the immediate container holding the component.. culminating with the container invoking addActionListener() on it.
Is this the proper way to handle listeners in MVC?
Is defining all listeners of every component in the View by the Controller mandatory in MVC, or a useful practice? This would also imply that I create methods in the top-level container(View) to give the Controller a way to assign listeners to every single component in sub-containers?
Okay, first things first, Swing implements a form of the MVC already, albeit in the form of VC-M. This means that you shouldn't try and constrain Swing to a pure MVC directly, as you'll be very disappointed and spend a lot of time trying to make hacks where they shouldn't be.
Instead, you can wrap a MVC around Swing, allowing it to work around the API instead.
To my mind, a controller doesn't need to know, nor should it care, how the view or model are implemented, but it should only care how it can work with them (I've had too many developers get hold of UI components and do things to/with them that they shouldn't have, and broken the API when we've changed the implementation. It's best to hide that kind of detail)
In this vein, you can think of a view as self contained entity - it has controls and it does stuff independent of the controller. The controller doesn't care about the implementation specifics. What it does care about is getting information and been told when some event, described by the contract, has occurred. It shouldn't care about HOW it was generated.
For example, say you have a login view. The controller only wants to know the user name and password that the user entered and when it should validate that information.
Let's say you implement the view/controller to expose the JTextField and JPasswordFields to start with, but later on, your users want the user name selection to be restricted to a specific list (possibly provided by the model). Now you have implementation details stuck in your controller which are no longer applicable and you have to manually change or create a new MVC for this new use case.
What if, instead, you simply stated that the view has a getter for the user name and password and some kind event listener which would tell the controller when the user wanted the credentials verified? Well now, you'd only need to provide a new view, no need to modify the controller. The controller won't care HOW these values are generated.
As to the greater aspect of your question.
My program will be consisting of sub-containers and such. I am curious
as to how the Controller is supposed to implement the logic behind
defining and assigning all the listeners of the View.. or if the
controller defining the listeners for every single View component is
even practical?
It would seem that I would need a method in the View's top-level
container to allow the Controller to invoke the view to add a Listener
to the component in question? and so I would need a chain of methods,
each passing down the listener from the top-level container, to the
immediate container holding the component.. culminating with the
container invoking addActionListener() on it.
Is this the proper way to handle listeners in MVC?
The general answer is, no, it's not the proper way.
Each sub view would become its own MVC, with it focusing on its own requirements. The parent MVC might use events or other functionality provided by the child MVC's to make updates or even modify the states of other child MVCs.
The important thing to remember here, is a view can act as a controller for other views, although, you might choose to have a series of controllers which the view would be allowed to manage.
Imagine something like a "wizard". It has a bunch of steps which collects various information from the user, each step needs to be valid before it can move on to the next step.
Now, you might be tempted to integrate navigation into this directly, but a better idea would be to separate the navigation details as its own MVC.
The wizard would, when asked, present a step to the user, the user would fill in the information, possibly triggering events. These events would then allow the navigation MVC to decide if the user can move to the next step or the previous step.
The two MVC's would be controlled by a third "master" MVC which would help manage the states (listening for events from the wizard and updating the state of the navigation)
Let's try an example with a question that gets asked way to much around here, a quiz!
A quiz has questions, each question has a prompt, a correct answer, a series of possible answers and we also want to store the resulting answer from the user.
The Quiz API
So, below we have the basic outline of the quiz MVC, we have a question, which is managed by a model, there is a controller and a view and series of observers (listeners)
The Contracts (interfaces)
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();
}
I, personally, work to the principle of "code to interface (not implementation)", I've also deliberately gone overboard with the idea to demonstrate the point.
If you look closely, you will note that neither the view or model actually have any relationship to each other. This is all controlled via, the controller
One of the things I've done here is to provide the controller with a askNextQuestion, because the controller doesn't know when that should occur (you might think about using the userDidChangeAnswer, but that would mean that the user only gets a single attempt to answer the question, kind of mean)
The implementation
Now, normally, I like to have some abstract implementations laying around to fill out the "common" functionality, I've forgone that for the most part and gone directly to the default implementation, this done mostly for demonstration purposes.
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();
}
}
}
Really nothing fancy here. About the only thing of significant interest here is how the controller is managing the events between the model and the view
The Navigation API
The navigation API is pretty basic. It allows you to control if the user can actually navigate to the next or previous element (if the actions should be made available to the user) as well as disable either of the actions at any time
(Again, I've focused on a simple design, realistically, it would be nice to have some control over modifying the state of the model to change in which directions the navigation can work, but I've left this out on purpose to keep it simple)
The Contracts (interfaces)
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);
}
The implementation
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;
}
}
}
The Quiz Master
Now, these are two distinct APIs, they have nothing in common, so, we need some kind of controller to bridge them
The contracts (the interfaces)
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();
}
Okay, so you're probably asking yourself the obvious question, where's the model? Well, it doesn't need one, it's just a bridge between the navigation and quiz APIs, it doesn't manage any data of it's own...
The implementations
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());
}
}
}
Now, the implementation is interesting, it actually has two "states" or "views", the "question and answer" view and the "score view". This, again, is deliberate, because I really didn't want ANOTHER MVC. The Q&A view is already managing two MVCs any way :P
Basically, what this does is monitors the quiz API for when the user changes the answer to a question, it then tells the navigation API that it can move to the next question. It monitors the start and completed events as well, presenting the required view for those states.
It is also monitoring the navigation API for navigation events. In this example, we can only move in a single direction and even if the navigation API was configured to do otherwise, the quiz API does not provide that functionality
Put it together
Now, I've chosen to deliberately build each section separately, conceivably, you could have the QuizMasterController build the Navigation API itself, as it knows that the quiz API only allows for forward navigation, equally we could change the navigation API to allow those states to be modified via the model or the model changed, these are all viable solutions, I've just gone for a direct example.
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);
And finally we end up with something like...
This is nothing if not rough, but is designed to provide some ideas into how you might accomplish complex, compound MVCs
i'm doing a class that does a sort of "file explorer",
in the constructor i create the frame ,panel ecc.. but than i want to say to the main program that calls this class that the user has finish the selection, i know i can call a static method that is in the main from this class,but i want to make a action listener because i want to use this class for different programs
For Example if FileEx is my class:
public class FileEx()
{
public FileEx()
{
//program that do something
if(done == true)
//here i want to call the action
}
public void addActionListener(ActionListener ac) //i don't know if it's correct
//but i want something like this
{
}
}
public static void main(String[] args)
{
FileEx fileex = new FileEx();
fileex.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e)
{
//when done is true i want this block of code to be called
}
});
}
ActionListeners will only work when added to components that allow them to be added and that notify listeners with them such as JButtons, JMenuItems, JComboBoxes and such. We have no idea what type of class FileEx is or why it should accept an ActionListener and a little more information would be qutie helpful. If you want to notify another object that an event occurs, such as that a calculation is done, use another type of listener such as a PropertyChangeListener. Alternatively you could do the processing in a modal JDialog window, which will notify the calling window that it is done performing its duties by returning code flow to the calling window.
For example, please look at my answers to similar questions:
Drawing with paintComponent after value of Jbutton changed in another class
JTextField data in different frames, with data stored in global variable?
Loop making program freeze
EDIT
For example, if you wanted your FileEx to allow other classes to listen for changes to a String called selection (the so-called "bound" property) you could create it to look something like:
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class TestFileEx {
public static void main(String[] args) {
final FileEx fileEx = new FileEx();
fileEx.addPropertyChangeListener(FileEx.SELECTION, new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
// TODO code to call when fileEx has changed selections
String fileExSelection = evt.getNewValue().toString();
// or
String fileExSelection2 = fileEx.getSelection();
}
});
}
}
and
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
publicclass FileEx {
public static final String SELECTION = "selection";
private SwingPropertyChangeSupport propertyChangeSupport = new SwingPropertyChangeSupport(
this);
private String selection;
public void someMethodThatChangesSelection() {
}
public String getSelection() {
return selection;
}
public void setSelection(String selection) {
String oldValue = this.selection;
String newValue = selection;
this.selection = selection;
// notify the listeners of change
propertyChangeSupport.firePropertyChange(SELECTION, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
public void rem(String propertyName, PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
}
}
Here is the code based on your example which adds actionlisteners and calls them:
public class FileEx()
{
private final List<ActionListener> listeners = new ArrayList<>();
public FileEx()
{
//program that do something
if(done == true) {
notifyListeners();
}
}
public void addActionListener(ActionListener ac)
{
listeners.add(ac);
}
private void notifyListeners()
{
for (final ActionListener listener: listeners)
{
listener.actionPerformed(null);//You can create event if you want.
}
}
}
public static void main(String[] args)
{
FileEx fileex = new FileEx();
fileex.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e)
{
//when done is true i want this block of code to be called
}
});
}
I'm developing the MVC application and don't know how to pass values of textfields in View to Controller, when a user press the button. ActionListener of this button is placed in Controller:
private class NewTaskListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
/* here I need to get values of textfields in View
and pass them to AddNewTaskForUser
*/
model.AddNewTaskForUser(userName, newTask);
}
}
Listener is added to button in such way:
Controller:
this.view.AddNewTaskListanaer(new NewTaskListener());
View:
public void AddNewTaskListanaer(ActionListener actionListener)
{
btnSetupTask.addActionListener(actionListener);
}
Thanks!
Your view should have public methods:
public String getTaskName() {
return taskName.getText();
}
public String getDescription() {
return description.getText();
}
// And so on and so on...
Now in your controller you can get the desired text field data from the view. So now in your action listener you can do:
private class NewTaskListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
String userName = view.getUsername();
String newTask = view.getTaskName();
model.AddNewTaskForUser(userName, newTask);
}
}
This is assuming the action listener class is declared within the controller so it has access to the controllers properties.
Also, methods should not start with a capital letter. They should be in the format lowerCamelCase.
Example:
addActionListener and actionPerformed.
you need to pass the instances of your textfield to your listener.
private class NewTaskListener implements ActionListener {
private JTextField textField;
public NewTaskListener(JTextField textField) {
this.textField = textField;
}
#Override
public void actionPerformed(ActionEvent e) {
//.. do stuff
model.AddNewTaskForUser(userName, newTask);
}
}
and create it like this:
this.view.AddNewTaskListanaer(new NewTaskListener(textField));