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.
CodePudding user response:
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 interface
s, 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 interface
s 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);
}
}
}