I wanted to do really simple MVC project, when i click on the button, I'd like to see "napis" on my console, but the ActionListener doesn't work properly. Any thoughts ? (If i've done something wrong with post please be understanding its my first post here :))
public class Model {
public String napis (){
return "napis";
}
}
public class View {
private JFrame frame;
private JLabel label;
private JButton button;
public View (){
frame = new JFrame();
label = new JLabel("Napis");
button = new JButton("click");
frame.add(label);
frame.add(button);
frame.setSize(500,500);
button.setSize(30,30);
frame.setVisible(true);
}
public void addActionListener(ActionListener click){
button.addActionListener(click);
}
}
public class Controller {
private Model model;
private View view = new View();
public Controller (final Model model, View view) {
view.addActionListener(
new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
model.napis();
}
}
);{
}
}
}
ofc all imports are fixed.
model.napis(); doesn't write anything in the console :
public String napis (){
return "napis";
}
It returns simply a String.
This should do it :
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(model.napis());
}
In your controller class, change
model.napis();
to
System.out.println(model.napis());
What your problem is that the action listener takes the String but does nothing with it. What you have to do is to print the returned String.
Okay, so your code is a little confusing. Your Controller is creating an instance of View, but it's also been passed an instance of View.
The instance it create's does not have the ActionListener attached to it, but the one you pass in does.
Both Views are create and showing a JFrame
Because the instance of View you're passing gets created first, it appears first, but the instance of View which your Controller creates gets created second, it appears over the top - but it does not have an ActionListener attached to it...
So, the first thing you should do, is get rid of the instance of the View that the Controller is creating
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
Controller controller = new Controller(new Model(), new View());
}
public class Model {
public String napis() {
return "napis";
}
}
public class View {
private JFrame frame;
private JLabel label;
private JButton button;
public View() {
frame = new JFrame();
label = new JLabel("Napis");
button = new JButton("click");
frame.add(label);
frame.add(button);
frame.setSize(500, 500);
button.setSize(30, 30);
frame.setVisible(true);
}
public void addActionListener(ActionListener click) {
button.addActionListener(click);
}
}
public class Controller {
private Model model;
private View view;
public Controller(final Model model, View view) {
this.view = view;
this.model = model;
view.addActionListener(
new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Clicked");
this.model.napis();
}
});
}
}
}
You should also be focused on the side effect that your View's constructor is creating, creating and showing the frame probably isn't the right choice here
I would also encourage you to do some research into the concept of "code to interface not implementation" as it's one of the key aspects of MVC
Related
I have a JTabbedPane with two JPanels that need to stay in seperate classes. In PageOne, I want to be able to increment MyInteger by clicking the add button, and I then want to be able to print that integer in PageTwo by clicking the button there. It prints the correct value in PageOne, but prints 0 when I pass it to the PageTwo class and print it there.
How can I pass the value in such a way that it prints the correct value when clicking the button in both JPanels? I figure it has something to do with how I inherit from PageOne, but couldn't find a way of changing it on SO that solved my problem.
Main class:
import javax.swing.*;
public class MyJFrame {
PageOne pageOne;
PageTwo pageTwo;
public MyJFrame() {
JFrame f = new JFrame();
pageOne = new PageOne();
pageTwo = new PageTwo();
JTabbedPane jTabbedPane = new JTabbedPane();
jTabbedPane.addTab("Page One", pageOne);
jTabbedPane.addTab("Page Two", pageTwo);
f.add(jTabbedPane);
f.setSize(200,120);
f.setVisible(true);
}
public static void main(String[] args) throws InterruptedException {
new MyJFrame();
}
}
JPanel One:
import javax.swing.*;
public class PageOne extends JPanel {
public Integer myInteger = 0;
public JButton add;
public PageOne() {
add = new JButton();
add.setText("Increment number");
add(add);
add.addActionListener(actionEvent -> {
myInteger++;
printOne();
});
}
public void printOne() {
System.out.println("Page One:" + myInteger);
}
}
JPanel Two:
import javax.swing.*;
public class PageTwo extends JPanel {
PageOne pageOneRef = new PageOne();
public JButton button;
public PageTwo() {
JPanel panel = new JPanel();
button = new JButton("Click me");
panel.add(button);
add(panel);
button.addActionListener(e -> printTwo());
}
public void printTwo() {
System.out.println("Page Two:" + pageOneRef.myInteger);
}
}
The basic answer is, you need some kind of "container" which can be shared between the two components. This is commonly achieved through the use of a "model" of some kind.
See:
Model-View-Controller
Observer Pattern
Writing Event Listeners
for an overview of the concepts presented below
Runnable example
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
DefaultIntegerModel model = new DefaultIntegerModel();
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.addTab("Page One", new PageOne(model));
tabbedPane.addTab("Page Two", new PageTwo(model));
frame.add(tabbedPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public interface IntegerModel {
public interface Observer {
public void valueDidChange(IntegerModel source, int value);
}
public int getValue();
public void addObserver(Observer observer);
public void removeObserver(Observer observer);
}
public interface MutableIntegerModel extends IntegerModel {
public void setValue(int value);
}
public class DefaultIntegerModel implements MutableIntegerModel {
private int value;
private List<Observer> observers;
public DefaultIntegerModel() {
this(0);
}
public DefaultIntegerModel(int value) {
this.value = value;
observers = new ArrayList<Observer>(8);
}
#Override
public void setValue(int value) {
this.value = value;
fireValueDidChange(value);
}
#Override
public int getValue() {
return value;
}
#Override
public void addObserver(Observer observer) {
observers.add(observer);
}
#Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
protected void fireValueDidChange(int value) {
for (Observer observer : observers) {
observer.valueDidChange(this, value);
}
}
}
public class PageOne extends JPanel {
public JButton add;
private MutableIntegerModel model;
public PageOne(MutableIntegerModel model) {
this.model = model;
add = new JButton();
add.setText("Increment number");
add(add);
add.addActionListener(actionEvent -> {
model.setValue(model.getValue() + 1);
printOne();
});
}
public void printOne() {
System.out.println("Page One:" + model.getValue());
}
}
public class PageTwo extends JPanel {
private JButton button;
private JLabel label;
private IntegerModel model;
public PageTwo(IntegerModel model) {
this.model = model;
model.addObserver(new IntegerModel.Observer() {
#Override
public void valueDidChange(IntegerModel source, int value) {
System.out.println("Page two value did change to " + value);
label.setText(Integer.toString(model.getValue()));
}
});
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
label = new JLabel(Integer.toString(model.getValue()));
add(label, gbc);
button = new JButton("Click me");
button.addActionListener(e -> printTwo());
add(button, gbc);
}
public void printTwo() {
System.out.println("Page Two:" + model.getValue());
}
}
}
But why are there two models
Stop for a second and think about the responsibilities of each component.
PageOne want's to update the model, in order to do so, it also needs to know the value of the model. The model makes no assumption about "how" the consumer of this model will do that (so I didn't provide a increment method), it just allows the consumer to set the value it wants
PageTwo just wants to display the value (and be notified when some change occurs), so it doesn't need a mutable version of the model.
This restricts what consumers maybe able to do to the model rather the exposing functionality to parties which don't need it (and might be tempted to abuse it)
This is a demonstration and your needs may differ, but I'm bit of a scrooge when I design these kinds of things, I need the consumers to prove to me that they need functionality, rather then "assuming" what functionality they "might" require 😉
This is a practice known is "information hiding", which is supported by Polymorphism in OO languages
I've tried to apply the Observable/Observer pattern but there is something wrong with my code when I try to change a the textfield of a JTextPane.
I've got 3 classes, Play, Controller and SecondWindow here are a sample of their code.
public class Play() {
Controller c = new Controller();
SecondWindow sw = new SecondWindow();
c.addObserver(sw)
c.setText("blabla");
}
My class Controller:
public class Controller extends Observable(){
private String text ="";
private static Controller getInstance() {
if (instance == null) {
instance = new Controller();
}
return instance;
}
public void setText(String s) {
text = s;
setChanged();
notifyObservers();
}
}
and SecondWindow:
public class SecondWindow extends JFrame implements Observer{
private JPanel contentPane;
private Controller c;
private JTextPane txt = new JTextPane();
public SecondWindow () {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
SecondWindow frame = new SecondWindow();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public SecondWindow() {
initComponents();
createEvents();
c = Controller.getInstance();
}
public void initComponents() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(1000, 0, 300,500);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
txt.setBounds(0, 0, 280, 460);
txt.enable(false);
contentPane.add(txt);
}
public void update(Observable arg0 , Object arg1){
// Things to change here
}
I can't manage to put the variable c in the textField (like a txt.setText(c.getText) instruction). I'm sure that it reads the method update, but I don't know how to make sure it works.
Hint: Per the Observerable API the notifyObservers method has an overload that accepts any object as a parameter:
public void notifyObservers(Object arg)
This can even be a String. And as per the Observer API, this object is then passed into the update method in the observer, and you can use it there.
void update(Observable o,
Object arg)
arg - an argument passed to the notifyObservers method.
Separate side issue here:
contentPane.setLayout(null);
For most Swing aficionados, seeing this is like hearing nails on a chalkboard -- it's painful. While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one. Instead you will want to study and learn the layout managers and then nest JPanels, each using its own layout manager to create pleasing and complex GUI's that look good on all OS's.
Side issue number two: your code is not Swing thread safe, since the Swing GUI could very well be notified by the observable off of the Swing event dispatch thread or EDT. While it is not likely to cause frequent or serious problems with this simple program, in general it would be better to use a SwingPropertyChangeSupport and PropertyChangeListeners rather than Observer / Observable if you can.
Next Side Issue
This:
public class Controller extends Observable(){
isn't compilable / kosher Java. Same for the duplicate parameter-less constructors for the SecondWindow class. Yes, we know what you're trying to do, but it's hard enough trying to understand someone else's code, you really don't want to make it harder by posting kind-of sort-of uncompilable code, trust me.
For example, something simple could be implemented in Swing using PropertyChangeListeners, like so:
import java.util.concurrent.TimeUnit;
public class Play2 {
public static void main(String[] args) {
Model2 model2 = new Model2();
View2 view2 = new View2();
new Controller2(model2, view2);
view2.show();
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
// one of the few times it's OK to ignore an exception
}
String text = String.format("Counter Value: %d", i);
model2.setText(text);
}
}
}
import java.beans.PropertyChangeListener;
import javax.swing.event.SwingPropertyChangeSupport;
public class Model2 {
private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
public static final String TEXT = "text"; // name of our "bound" property
private String text = "";
public String getText() {
return text;
}
public void setText(String text) {
String oldValue = this.text;
String newValue = text;
this.text = text;
pcSupport.firePropertyChange(TEXT, oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(listener);
}
public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.addPropertyChangeListener(name, listener);
}
public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
pcSupport.removePropertyChangeListener(name, listener);
}
}
import javax.swing.*;
public class View2 {
private JPanel mainPanel = new JPanel();
private JTextField textField = new JTextField(10);
public View2() {
textField.setFocusable(false);
mainPanel.add(new JLabel("Text:"));
mainPanel.add(textField);
}
public JPanel getMainPanel() {
return mainPanel;
}
public void setText(String text) {
textField.setText(text);
}
public void show() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("View");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(getMainPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
public class Controller2 {
private Model2 model2;
private View2 view2;
public Controller2(Model2 model2, View2 view2) {
this.model2 = model2;
this.view2 = view2;
model2.addPropertyChangeListener(Model2.TEXT, new ModelListener());
}
private class ModelListener implements PropertyChangeListener {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
view2.setText((String) pcEvt.getNewValue());
}
}
}
In the Passive View Model View Presenter pattern, who has the responsibility for displaying the view? I have found related answers for other MVP versions, but they don't seem applicable to the passive view version.
I have a concrete example using Java Swing. It's pretty simple, but basically we have a SwingCustomersView which internally builds a JPanel with a table (list of customers) and a label displaying the currently selected customers age. When a customer is selected in the table, the presenter retrieves the selected customer age from the model. I think the example is a correct implementation of MVP Passive View, but correct me if I'm wrong.
The question is how do we bootstrap these classes? For example, if we wanted to display the SwingCustomersView in a JFrame. How would one do that? I imagine something along the lines of:
void launcher() {
CustomersModel model = new CustomersModel();
SwingCustomersView view = new SwingCustomersView();
CustomersPresenter presenter = new CustomersPresenter(view, model);
}
This is the initial wiring, but nothing is displayed yet. How do we actually display the view? Is it the responsibility of (1) launcher() , (2) SwingCustomersView or (3) CustomersPresenter to display the view? Unfortunately I don't believe any of those are very good as you can see from my thoughts below. Perhaps there's another way?
(1.a): launcher
Make SwingCustomersView extend JFrame and make it add it's internal JPanel to the content pane of itself. Then we can do this:
void launcher() {
CustomersModel model = new CustomersModel();
SwingCustomersView view = new SwingCustomersView();
CustomersPresenter presenter = new CustomersPresenter(view, model);
view.setVisible(true); // Displays the view
}
However in this case we don't use the presenter instance for anything. Isn't that strange? It's just there for wiring, we could just as well delete the variable and just do new CustomersPresenter(view, model).
(2): SwingCustomersView
Make SwingCustomersView take a Container in the constructor to which it should add it's internal JPanel:
void launcher() {
CustomersModel model = new CustomersModel();
JFrame frame = new JFrame("Some title");
SwingCustomersView view = new SwingCustomersView(frame.getContentPane());
CustomersPresenter presenter = new CustomersPresenter(view, model);
frame.pack();
frame.setVisible(true) // Displays the view
}
However, same problem as (1): the presenter instance does nothing. It seems strange. Furthermore with both (1) and (2) it is possible to display the view before the presenter is hooked up, which I imagine could cause strange results in some situations.
(3): CustomersPresenter
Make CustomersPresenter responsible for displaying the view somwhow. Then we could do this:
void launcher() {
CustomersModel model = new CustomersModel();
SwingCustomersView view = new SwingCustomersView();
CustomersPresenter presenter = new CustomersPresenter(view, model);
presenter.show() // Displays the view
}
This would solve the problem of not using it for anything after construction. But I don't see how do to this without either changing the CustomersView interface or making CustomersPresenter too dependent on the underlying GUI implementation. Furthermore, displaying a view doesn't sound like presentation logic and thus doesn't seem to belong in the presenter.
Example
public class CustomersModel {
private List<Customer> customers;
public CustomersModel() {
customers = new ArrayList<Customer>();
customers.add(new Customer("SomeCustomer", "31"));
customers.add(new Customer("SomeCustomer", "32"));
}
public List<Customer> getCustomers() {
return customers;
}
}
public class Customer {
public String name;
public String age;
public Customer(String name, String age) {
this.name = name;
this.age = age;
}
}
public interface CustomersView {
void addCustomerSelectionChangeListener(ItemListener listener);
void onNewActiveCustomer(String age);
void onNewCustomers(List<String> newCustomers);
}
public class SwingCustomersView implements CustomersView {
// Swing components here all put into a main JPanel
public void addCustomerSelectionChangeListener(ItemListener listener) {
// Add event listener to table
}
public void onNewActiveCustomer(String age) {
// Display age in label beneath table
}
public void onNewCustomers(List<String> newCustomers) {
// Display customers in table
}
}
public class CustomersPresenter {
private final CustomersView view;
private final CustomersModel model;
public CustomersPresenter(CustomersView view, CustomersModel model) {
this.view = view;
this.model = model;
initPresentationLogic();
populateView();
}
private void initPresentationLogic() {
view.addCustomerSelectionChangeListener(new ItemListener() {
#Override
public void itemStateChanged(ItemEvent e) {
String selectedName = (String)e.getItem();
List<Customer> customers = model.getCustomers();
for (Customer c : customers)
if (c.name.equals(selectedName))
view.onNewActiveCustomer(c.age);
}
});
}
private void populateView() {
List<Customer> customers = model.getCustomers();
List<String> names = new ArrayList<String>();
for (Customer c : customers)
names.add(c.name);
// View will now populate its table, which in turn will call customerSelectionChangeListener
// so the view 'automagically' updates the selected contact age too
view.onNewCustomers(names);
}
}
Option (3) all the way. It is the presenter's jobs for "controlling" the view, which includes making it visible. Yes, you'll need to add to the view's interface to allow this to happen, but that's not a big deal. Remember, you can make the view is as passive as possible. No logic whatsoever!
Working Example:
I stumbled upon this example of a simple Swing game using an MVC architecture. Since I write my Swing apps using MVP instead of MVC, I can't say with authority if this example is a true and pure example of MVC. It looks okay to me, and the author trashgod has more than proven himself here on SO using Swing, so I'll accept it as reasonable.
As an exercise, I decided to rewrite it using an MVP architecture.
The Driver:
As you can see in the code below, this is pretty simple. What should jump out at you are the separation of concerns (by inspecting the constructors):
The Model class is standalone and has no knowledge of Views or Presenters.
The View interface is implemented by a standalone GUI class, neither of which have any knowledge of Models or Presenters.
The Presenter class knows about both Models and Views.
Code:
import java.awt.*;
/**
* MVP version of https://stackoverflow.com/q/3066590/230513
*/
public class MVPGame implements Runnable
{
public static void main(String[] args)
{
EventQueue.invokeLater(new MVPGame());
}
#Override
public void run()
{
Model model = new Model();
View view = new Gui();
Presenter presenter = new Presenter(model, view);
presenter.start();
}
}
and the GamePiece that we'll be using for the game:
import java.awt.*;
public enum GamePiece
{
Red(Color.red), Green(Color.green), Blue(Color.blue);
public Color color;
private GamePiece(Color color)
{
this.color = color;
}
}
The Model: Primarily, the job of the Model is to:
Provide data for the UI (upon request)
Validation of data (upon request)
Long-term storage of data (upon request)
Code:
import java.util.*;
public class Model
{
private static final Random rnd = new Random();
private static final GamePiece[] pieces = GamePiece.values();
private GamePiece selection;
public Model()
{
reset();
}
public void reset()
{
selection = pieces[randomInt(0, pieces.length)];
}
public boolean check(GamePiece guess)
{
return selection.equals(guess);
}
public List<GamePiece> getAllPieces()
{
return Arrays.asList(GamePiece.values());
}
private static int randomInt(int min, int max)
{
return rnd.nextInt((max - min) + 1) + min;
}
}
The View: The idea here is to make it as "dumb" as possible by stripping out as much application logic as you can (the goal is to have none). Advantages:
The app now be 100% JUnit testable since no application logic is mixed in with Swing code
You can launch the GUI without launching the entire app, which makes prototyping much faster
Code:
import java.awt.*;
import java.awt.event.*;
import java.util.List;
public interface View
{
public void addPieceActionListener(GamePiece piece, ActionListener listener);
public void addResetActionListener(ActionListener listener);
public void setGamePieces(List<GamePiece> pieces);
public void setResult(Color color, String message);
}
and the GUI:
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import javax.swing.*;
/**
* View is "dumb". It has no reference to Model or Presenter.
* No application code - Swing code only!
*/
public class Gui implements View
{
private JFrame frame;
private ColorIcon icon;
private JLabel resultLabel;
private JButton resetButton;
private JButton[] pieceButtons;
private List<GamePiece> pieceChoices;
public Gui()
{
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
icon = new ColorIcon(80, Color.WHITE);
}
public void setGamePieces(List<GamePiece> pieces)
{
this.pieceChoices = pieces;
frame.add(getMainPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public void setResult(Color color, String message)
{
icon.color = color;
resultLabel.setText(message);
resultLabel.repaint();
}
private JPanel getMainPanel()
{
JPanel panel = new JPanel(new BorderLayout());
panel.add(getInstructionPanel(), BorderLayout.NORTH);
panel.add(getGamePanel(), BorderLayout.CENTER);
panel.add(getResetPanel(), BorderLayout.SOUTH);
return panel;
}
private JPanel getInstructionPanel()
{
JPanel panel = new JPanel();
panel.add(new JLabel("Guess what color!", JLabel.CENTER));
return panel;
}
private JPanel getGamePanel()
{
resultLabel = new JLabel("No selection made", icon, JLabel.CENTER);
resultLabel.setVerticalTextPosition(JLabel.BOTTOM);
resultLabel.setHorizontalTextPosition(JLabel.CENTER);
JPanel piecePanel = new JPanel();
int pieceCount = pieceChoices.size();
pieceButtons = new JButton[pieceCount];
for (int i = 0; i < pieceCount; i++)
{
pieceButtons[i] = createPiece(pieceChoices.get(i));
piecePanel.add(pieceButtons[i]);
}
JPanel panel = new JPanel(new BorderLayout());
panel.add(resultLabel, BorderLayout.CENTER);
panel.add(piecePanel, BorderLayout.SOUTH);
return panel;
}
private JPanel getResetPanel()
{
resetButton = new JButton("Reset");
JPanel panel = new JPanel();
panel.add(resetButton);
return panel;
}
private JButton createPiece(GamePiece piece)
{
JButton btn = new JButton();
btn.setIcon(new ColorIcon(16, piece.color));
btn.setActionCommand(piece.name());
return btn;
}
public void addPieceActionListener(GamePiece piece, ActionListener listener)
{
for (JButton button : pieceButtons)
{
if (button.getActionCommand().equals(piece.name()))
{
button.addActionListener(listener);
break;
}
}
}
public void addResetActionListener(ActionListener listener)
{
resetButton.addActionListener(listener);
}
private class ColorIcon implements Icon
{
private int size;
private Color color;
public ColorIcon(int size, Color color)
{
this.size = size;
this.color = color;
}
#Override
public void paintIcon(Component c, Graphics g, int x, int y)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(color);
g2d.fillOval(x, y, size, size);
}
#Override
public int getIconWidth()
{
return size;
}
#Override
public int getIconHeight()
{
return size;
}
}
}
What might not be so obvious right away is how large the View interface can get. For each Swing component on the GUI, you may want to:
Add/Remove a listener to the component, of which there are many types (ActionListener, FocusListener, MouseListener, etc.)
Get/Set the data on the component
Set the "usability" state of the component (enabled, visible, editable, focusable, etc.)
This can get unwieldy really fast. As a solution (not shown in this example), a key is created for each field, and the GUI registers each component with it's key (a HashMap is used). Then, instead of the View defining methods such as:
public void addResetActionListener(ActionListener listener);
// and then repeat for every field that needs an ActionListener
you would have a single method:
public void addActionListener(SomeEnum someField, ActionListener listener);
where "SomeEnum" is an enum that defines all fields on a given UI. Then, when the GUI receives that call, it looks up the appropriate component to call that method on. All of this heavy lifting would get done in an abstract super class that implements View.
The Presenter: The responsibilities are:
Initialize the View with it's starting values
Respond to all user interactions on the View by attaching the appropriate listeners
Update the state of the View whenever necessary
Fetch all data from the View and pass to Model for saving (if necessary)
Code (note that there's no Swing in here):
import java.awt.*;
import java.awt.event.*;
public class Presenter
{
private Model model;
private View view;
public Presenter()
{
System.out.println("ctor");
}
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
}
public void start()
{
view.setGamePieces(model.getAllPieces());
reset();
view.addResetActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
reset();
}
});
for (int i = 0; i < GamePiece.values().length; i++)
{
final GamePiece aPiece = GamePiece.values()[i];
view.addPieceActionListener(aPiece, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
pieceSelected(aPiece);
}
});
}
}
private void reset()
{
model.reset();
view.setResult(Color.GRAY, "Click a button.");
}
private void pieceSelected(GamePiece piece)
{
boolean valid = model.check(piece);
view.setResult(piece.color, valid ? "Win!" : "Keep trying.");
}
}
Keep in mind that each portion of the MVP architecture can/will be delegating to other classes (that are hidden to the other 2 portions) to perform many of its tasks. The Model, View, and Presenter classes are just the upper divisions in your code base heirarchy.
I'm trying to write a really simple Java example to learn MVC. It's a JButton that when clicked increments a counter and displays the number of clicks so far.
I broke out the Model, View and Controller into separate classes, and thought I was on the right path, but when I click the button the JLabel that displays the counter continues to stay at 0.
Can someone take a quick look and see why the JLabel that should display the number of clicks always stays at 0?
Thanks
View
package mvc;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
public class View extends javax.swing.JFrame {
private JButton jButton1;
private JLabel jLabel1;
private Controller c;
private Model m;
/**
* Auto-generated main method to display this JFrame
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Controller c = new Controller();
Model m = new Model();
View inst = new View(c,m);
inst.setLocationRelativeTo(null);
inst.setVisible(true);
}
});
}
public View(Controller c, Model m) {
super();
this.c = c;
this.m = m;
initGUI();
}
private void initGUI() {
try {
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
getContentPane().setLayout(null);
{
jButton1 = new JButton();
getContentPane().add(jButton1, "Center");
jButton1.setText("Click");
jButton1.setBounds(314, 180, 101, 34);
jButton1.addActionListener(c);
}
{
jLabel1 = new JLabel();
getContentPane().add(getJLabel1());
jLabel1.setText("Click Count = " + c.getClickCount());
jLabel1.setBounds(439, 183, 91, 27);
}
pack();
this.setSize(818, 414);
} catch (Exception e) {
//add your error handling code here
e.printStackTrace();
}
}
public JLabel getJLabel1() {
return jLabel1;
}
}
End View
Controller class
package mvc;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Controller implements ActionListener
{
Model m;
View v;
public Controller()
{
m = new Model();
v = new View(this, m);
}
#Override
public void actionPerformed(ActionEvent arg0)
{
if (arg0.getSource() == "Click")
{
m.addClick();
v.getJLabel1().setText("Click count = " + getClickCount());
}
}
public int getClickCount()
{
return m.getClicks();
}
}
End Controller class
Model class
package mvc;
public class Model
{
private int clicks;
public Model()
{
clicks = 0;
}
public void addClick()
{
clicks++;
}
public int getClicks()
{
return clicks;
}
}
End Model class
I see why now. You have two different Model objects created.
One in controller and one in Main() - Which one is it?
Another advice.. create a MainController class.
This should have your Main method.
Your main method creates another Controller responsible for creating your View and Model.
Use this Controller as the bridge.
There are couple of problems:
The actual data should that view displays generally comes from the model not from the controller.
So your code in the view
jLabel1.setText("Click Count = " + c.getClickCount());
should change to
jLabel1.setText("Click Count = " + m.getClickCount());
Inside the Controller, you create a new instance of the mode and view and inside the main() method you again create new instance of controller and view. So essentially the Controller class is working on a different view and model objects.
In general, but its not a stone-graved standard:
The View has a model, it doesent refer to the controller
The model is independent and doesent refer to view or controller
The controller has both view and model.
if (arg0.getSource() == "Click")
== is not meant for string (or object) equality comparison. equals method is what you should use instead.
Moreover, I think you are interested in public String getActionCommand() method rather than public Object getSource().
A little test
JButton btn = new JButton();
btn.setText("Click");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getSource());
System.out.println(e.getActionCommand());
}
});
btn.doClick();
and the output
javax.swing.JButton[,0,0,0x0,invalid,alignmentX=0.0,alignmentY=0.5,border=javax.swing.plaf.BorderUIResource$CompoundBorderUIResource#1f5b0afd,flags=296,maximumSize=,minimumSize=,preferredSize=,defaultIcon=,disabledIcon=,disabledSelectedIcon=,margin=javax.swing.plaf.InsetsUIResource[top=2,left=14,bottom=2,right=14],paintBorder=true,paintFocus=true,pressedIcon=,rolloverEnabled=true,rolloverIcon=,rolloverSelectedIcon=,selectedIcon=,text=Click,defaultCapable=true]
Click
should illustrate why.
Update
Try
if (arg0.getActionCommand().equals("Click"))
The number always stays at zero because you are updating the wrong instance of View. In your Controller class you create another instance which is not displayed.
v = new View(this, m);
You could instead pass in your main instance by adding a setter:
class Controller implements ActionListener {
Model m;
View v;
public Controller() {
m = new Model();
}
public void setView(View v) {
this.v = v;
}
...
ActionEvent.getSource() returns a component reference but jButton1 is not publicly visible. To fix you can either add a getter for the button or use the action command:
if (arg0.getActionCommand().equals("Click")) {
you could maybe try to make accessor to your button on the view class:
public JButton getButton(){
return jbutton1;
}
public void setButton(JButton button){
this.jbutton1 = button ;
}
and in your controler class accessing to your button like this:
if(arg0.getSource() == v.getButton()){
...
}
I always did like that and i never had any error...
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 4 years ago.
Improve this question
I'm quite new in desktop applications development and I have a pretty big project do deliver this summer. The thing is that the code has to be very clear, so I won't go in (much) trouble when I will update it.
As a result, I want a good "separation of concerns". And the most difficult part to me is the View-Controller separation.
Now, I have read lots of tutorials, discussions etc. And I have designed a mini-app in 3 different ways. The app is simple : click on a button that transform a label to a "Hello world".
What do you think of those 3 designs ?
Is there a better design to meet my expectations ?
Design 1
View1.java :
public View1() {
initComponents();
this.controller = new Controller1(this);
}
private Controller1 controller;
public void updateLabel(String message){
this.jLabel1.setText(message);
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
this.controller.doSomething();
}
private void initComponents() {
...
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
...}
Controller1.java :
public class Controller1 {
public Controller1(View1 v){
this.view = v;
}
public void doSomething(){
this.view.updateLabel("Hello world");
}
private View1 view;
}
Design 2
View2.java :
public View2() {
initComponents();
this.controller = new Controller2(this);
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
controller.doSomething();
}
});
}
public void updateLabel(String message){
this.jLabel1.setText(message);
}
private Controller2 controller;
...
}
Controller2.java :
public class Controller2 {
public Controller2(View2 v){
this.view = v;
}
public void doSomething(){
this.view.updateLabel("Hello world");
}
private View2 view;
}
Design 3
View3.java :
public View3() {
initComponents();
this.controller = new Controller3(this);
this.jButton1.addActionListener(this.controller.listener);
}
private Controller3 controller;
public void updateLabel(String message){
this.jLabel1.setText(message);
}
...}
Controller3.java :
public class Controller3 {
public Controller3(View3 v){
this.view = v;
this.listener = new MyListener(v);
}
private View3 view;
public MyListener listener;
}
MyListener.java :
public class MyListener implements ActionListener{
private View3 view;
public MyListener(View3 v){
this.view = v;
}
public void actionPerformed(java.awt.event.ActionEvent evt) {
this.view.updateLabel("Hello world");
}
}
I don't like any of these designs. You are coupling the controller to the view to tightly. Let's say you wanted to change controller implementation in the future so you would have to go in all your classes and change the class. Instead you should make it injected. There are a lot of libs that can do this for you via annotations like Guice or Spring but I won't go in to those. Here is a better design.
public class View{
private Controller controller;
public View(Controller controller) {
this.controller = controller;
}
}
This a much cleaner design because the view doesn't have to know what the implementation of the controller is. You can later create a subclass and pass that instead.
So now with the above design I think you can see that you shouldn't pass the View to the controller. This is again coupling which is not good. Instead you can pass an onCallback class that will get executed when it is done. Here is code to undersand it
jButton1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
controller.doSomething(new Runnable(){
public void run(){
updateLabel("Hello world");
}
});
}
});
Then in your controller do
public void doSomething(Runnable callback){
// do work
SwingUtilties.invokeLater(callback);
}
If you look exactly what I have suggested is removing any kind of coupling. The view should not ask for a Controller, it should be given on. The Controller should not know about the view it should just execute a call back. This is important because if you decided to not using Swing, then you wouldn't have all these dependencies on Swing package in your controller.
Hope this all helps!!
Deciding which pattern is best depends a lot on the problem you are solving. Swing is already an MVC framework, so you'll have to consider whether adding another layer of indirection on top of it is worth the effort.
Since you are new to UI programming, I suggest you throw together a walking skeleton of your system first, then based on what you learned from that, decide on your architecture. A well-designed architecture makes it easy to test and reuse components. MVP and MVVM are two well-known ways design patterns for UIs.
For your toy problem you could implement either MVP or MVVM as I do below. Keep in mind you also will typically use interfaces between each and will have observers on Model if that can change.
MVP
public class Model {
public String getWhatIWantToSay() {
return "Hello World";
}
}
public class Presenter implements ActionListener {
private final View view;
private final Model model;
public Presenter(Model model, View view) {
this.model = model;
this.view = view;
view.addButtonListener(this);
}
public void actionPerformed(ActionEvent e) {
view.setText(model.getWhatIWantToSay());
}
}
public class View {
private JButton button = new JButton();
private JLabel label = new JLabel();
public void addButtonListener(ActionListener listener) {
button.addActionListener(listener);
}
public void setText(String text) {
label.setText(text);
}
}
MVVP
public class ModelView extends Observable {
private final Model model;
private String text = "";
public ModelView(Model model) {
this.model = model;
}
public void buttonClicked() {
text = model.getWhatIWantToSay();
notifyObservers();
}
}
public class View implements Observer {
private JButton button = new JButton();
private JLabel label = new JLabel();
private final ModelView modelView;
public View(final ModelView modelView) {
this.modelView = modelView;
modelView.addObserver(this);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
modelView.buttonClicked();
}
});
}
public void update(Observable o, Object arg) {
label.setText(modelView.text);
}
}
I think Design 2 is your best option to meet your criteria.
Problems with Design 1: It is too complex on the view side. The extra methods make it look almost like it has a controller inside of it. Simple changes would become complex to implement.
Problems with Design 3: This pushes too much onto the controller. The controller should not know what Swing events are happening. In that design if you want an action to happen based on a JList instead of a JButton you have to change the view and the controller which is bad.
Other comments about your code:
Use import statements so you don't have to include the package of a class in code as in: java.awt.event.ActionListener().
You use this. in several places were it is not necessary and that just adds noise.
As Amir points out, you have very tight coupling between your view and controller that is not necessary.
Another design approach can be something like this:
Model
package biz.tugay.toypro.model;
public interface LabelService {
String getDateInRandomLocale();
}
package biz.tugay.toypro.model;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;
public class LabelServiceImpl implements LabelService {
private final Locale availableLocalesJava[];
public LabelServiceImpl() {
this.availableLocalesJava = DateFormat.getAvailableLocales();
}
#Override
public String getDateInRandomLocale() {
final int randomIndex = ThreadLocalRandom.current().nextInt(0, availableLocalesJava.length);
final Locale locale = availableLocalesJava[randomIndex];
final Calendar calendar = Calendar.getInstance();
final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG, locale);
return dateFormat.format(calendar.getTime());
}
}
View
package biz.tugay.toypro.view;
import biz.tugay.toypro.model.LabelService;
import javax.swing.*;
public class DateInRandomLocaleLabel extends JLabel {
private final LabelService labelService;
public DateInRandomLocaleLabel(final LabelService labelService) {
this.labelService = labelService;
}
public void showDateInRandomLocale() {
final String dateInRandomLocale = labelService.getDateInRandomLocale();
setText(dateInRandomLocale);
}
}
package biz.tugay.toypro.view;
import javax.swing.*;
public class RandomizeDateButton extends JButton {
public RandomizeDateButton() {
super("Hit Me!");
}
}
package biz.tugay.toypro.view;
import javax.swing.*;
import java.awt.*;
public class DateInRandomLocalePanel extends JPanel {
public DateInRandomLocalePanel(final JLabel dateInRandomLocaleLabel, final JButton randomizeDateButton) {
final GridLayout gridLayout = new GridLayout(1, 2);
setLayout(gridLayout);
add(dateInRandomLocaleLabel);
add(randomizeDateButton);
}
}
package biz.tugay.toypro.view;
import javax.swing.*;
public class MainFrame extends JFrame {
public void init() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(400, 50);
setVisible(true);
}
}
Controller
package biz.tugay.toypro.controller;
import biz.tugay.toypro.view.DateInRandomLocaleLabel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class RandomizeDateButtonActionListener implements ActionListener {
private DateInRandomLocaleLabel dateInRandomLocaleLabel;
#Override
public void actionPerformed(final ActionEvent e) {
dateInRandomLocaleLabel.showDateInRandomLocale();
}
public void setDateInRandomLocaleLabel(final DateInRandomLocaleLabel dateInRandomLocaleLabel) {
this.dateInRandomLocaleLabel = dateInRandomLocaleLabel;
}
}
and finally how I start the Application:
package biz.tugay.toypro;
import biz.tugay.toypro.controller.RandomizeDateButtonActionListener;
import biz.tugay.toypro.model.LabelService;
import biz.tugay.toypro.model.LabelServiceImpl;
import biz.tugay.toypro.view.DateInRandomLocaleLabel;
import biz.tugay.toypro.view.DateInRandomLocalePanel;
import biz.tugay.toypro.view.MainFrame;
import biz.tugay.toypro.view.RandomizeDateButton;
import javax.swing.*;
public class App {
public static void main(String[] args) {
final LabelService labelService = new LabelServiceImpl();
// View
final DateInRandomLocaleLabel dateInRandomLocaleLabel = new DateInRandomLocaleLabel(labelService);
final RandomizeDateButton randomizeDateButton = new RandomizeDateButton();
final DateInRandomLocalePanel dateInRandomLocalePanel = new DateInRandomLocalePanel(dateInRandomLocaleLabel, randomizeDateButton);
final MainFrame mainFrame = new MainFrame();
mainFrame.getContentPane().add(dateInRandomLocalePanel);
// Controller
final RandomizeDateButtonActionListener randomizeDateButtonActionListener = new RandomizeDateButtonActionListener();
// Bind Controller to the View..
randomizeDateButton.addActionListener(randomizeDateButtonActionListener);
// Bind View to the Controller..
randomizeDateButtonActionListener.setDateInRandomLocaleLabel(dateInRandomLocaleLabel);
// Show the main frame..
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
mainFrame.init();
}
});
}
}
And this is what the application looks like:
I think there is no just 1 right answer, it depends on how you would like to tie the components you have..
You might find the following useful as well:
http://www.tugay.biz/2017/05/trying-to-understand-model-view.html
http://www.tugay.biz/2017/05/swingynotes-design-modified.html
http://www.tugay.biz/2017/05/refactoring-spring-swing-mvc.html