简体   繁体   中英

Java swing controller view comunication don't perform

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM