Been sitting here for hours now trying to figure this out, so a bit sympathy for this large question. :)
The Goal: I simply want to divide my done code into MVC (Model View Controller) parts. I have the game logics done and text based - the code works fine.
The Problem: Well, I want to implement this code into MVC, but where do explain for the MODEL that it should use text-based? Because the VIEW is only for the layout (graphically) correct? I am having a REALLY hard time figuring out where to begin at all. Any pointers would be so nice!
Here is my game logics code:
import mind.*;
import javax.swing.*;
import java.util.*;
import java.lang.*;
import java.awt.*;
public class Drive {
String[] mellan;
boolean gameEnd, checkempty, checkempty2, enemy, enemy2;
String gr,rd,tom;
int digits;
public Drive() {
// Gamepieces in textform
gr="G"; rd="R"; tom=" ";
mellan = new String[7];
String[] begin = {gr,gr,gr,tom,rd,rd,rd};
String[] end = {rd,rd,rd,tom,gr,gr,gr};
//input
Scanner in = new Scanner(System.in);
mellan=begin;
gameEnd=false;
while (gameEnd == false) {
for(int i=0; i<mellan.length; i++) {
System.out.print(mellan[i]);
}
System.out.print(" Choose 0-6: ");
digits = in.nextInt();
move();
checkWin();
}
}
void move() {
//BOOLEAN for gameruls!!!
checkempty = digits<6 && mellan[digits+1]==tom;
checkempty2 = digits>0 && mellan[digits-1]==tom;
enemy = (mellan[digits]==gr && mellan[digits+1]==rd && mellan[digits+2]==tom);
enemy2 = (mellan[digits]==rd && mellan[digits-1]==gr && mellan[digits-2]==tom);
if(checkempty) {
mellan[digits+1]=mellan[digits];
mellan[digits]=tom;
} else if (checkempty2) {
mellan[digits-1]=mellan[digits];
mellan[digits]=tom;
} else if (enemy) {
mellan[digits+2]=mellan[digits];
mellan[digits]=tom;
} else if (enemy2) {
mellan[digits-2]=mellan[digits];
mellan[digits]=tom;
}
}
void checkWin() {
String[] end = {rd,rd,rd,tom,gr,gr,gr};
for (int i=0; i<mellan.length; i++){
}
if (Arrays.equals(mellan,end)) {
for (int j=0; j<mellan.length; j++) {
System.out.print(mellan[j]);
}
displayWin();
}
}
void displayWin() {
gameEnd = true;
System.out.println("\nNicely Done!");
return;
}
// Kör Drive!
public static void main(String args[]) {
new Drive();
}
}
Here is how I defined my DriveView thus far: (just trying to make one button to work)
import mind.*;
import javax.swing.*;
import java.util.*;
import java.lang.*;
import java.awt.*;
import java.awt.event.*;
public class DriveView extends JFrame {
JButton ruta1 = new JButton("Green");
JButton ruta2 = new JButton("Green");
JButton rutatom = new JButton("");
JButton ruta6 = new JButton("Red");
private DriveModel m_model;
public DriveView(DriveModel model) {
m_model = model;
//Layout for View
JPanel myPanel = new JPanel();
myPanel.setLayout(new FlowLayout());
myPanel.add(ruta1);
myPanel.add(ruta2);
myPanel.add(rutatom);
myPanel.add(ruta6);
this.setContentPane(myPanel);
this.pack();
this.setTitle("Drive");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
void addMouseListener(ActionListener mol) {
ruta2.addActionListener(mol);
}
}
And DriveController which gives me error at compile
import mind.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.lang.*;
public class DriveController {
private DriveModel m_model;
private DriveView m_view;
public DriveController(DriveModel model, DriveView view) {
m_model = model;
m_view = view;
view.addMouseListener(new MouseListener());
}
class MouseListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String mening;
mening = e.getActionCommand();
if (mening.equals("Green")) {
setForeground(Color.red);
}
}
}
}
Your game model can have more than one view: a GUI view, a console view, a status view, etc. Typically each view arranges to listen for changes in the model, and it then queries the model for the information it needs to render it's particular view. This simple game was designed specifically to illustrate the concepts. The section named "Design" elaborates in more detail.
Addendum: This outline corresponds roughly to this architecture, symbolized below.
public class MVCOutline {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
//#Override
public void run() {
new MVCOutline().create();
}
});
}
private void create() {
JFrame f = new JFrame("MVC Outline");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new MainPanel());
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class MainPanel extends JPanel {
public MainPanel() {
super(new BorderLayout());
Model model = new Model();
View view = new View(model);
Control control = new Control(model, view);
this.add(view, BorderLayout.CENTER);
this.add(control, BorderLayout.WEST);
}
}
class Control extends JPanel implements ... {
private Model model;
private View view;
public Control(Model model, View view) {
this.model = model;
this.view = view;
}
}
class View extends JPanel implements Observer {
private Model model;
public View(Model model) {
this.model = model;
model.addObserver(this);
}
public void update(Observable o, Object arg) {
// update GUI based on model
}
}
class Model extends Observable {
public void next() {
this.notifyObservers(...);
}
}
To take a stab (and this is kind of overkill), I would make a game state bean that would represent the state that the game is currently in; that would be a "model object". Looking at your code, it would probably contain String [] mellan. Then I would have a data access object that contains a reference to the game state bean and it would have methods for updating the game state.
The game logic for different actions would be in a service object that has a reference to the data access object and the controller would have a reference to the service object. It would call the different action methods depending on what interaction was received from the interface, the view.
Like I said, this is kind of overkill.
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 have this java swing program, and im trying to figure out how can i create a button that upon clicking it will clear the text areas & change the icon of the person to put their hand down.
The buttons are dynamically generated using a for loop
And this
// To create buttons
for(int i=0 ; i < list.length; i++){
Participant pa = list[i];
JButton b = new JButton(pa.getNameButton(),participant);
b.addActionListener(e ->
{
String s = pa.toString() + questionPane.getText();
final ImageIcon raise = resizeIcon(new ImageIcon("src/raise.png"),30,30);
b.setIcon(raise);
JOptionPane.showMessageDialog(null,s,"Welcome to Chat Room",JOptionPane.INFORMATION_MESSAGE,pa.getImage());
});
p.add(b);
}
// Clear button logic
clearButton.addActionListener(e ->{
questionPane.setText("");
hostPane.setText("");
});
Okay, this is going to be a bit of fun.
The following example decouples much of the concept and makes use of a basic "observer pattern" to notify interested parties that the state has changed (ie, the chat's been cleared).
This is a basic concept where by you decouple the "what" from the "how", ie, "what" it is you want done (update the model) from the "how" it gets done (ie, button push). This makes it easier to adapt to more complex systems.
The example contains a ChatService, which has a single listener, which, for this example, simple tells interested parties that the chat has been cleared.
A more complex solution might have the ChatService generating events for when a user "raises" their hand, which allows the interested parties to deal with it in what ever way is relevant to them.
The example makes use of the Action API to decouple the work performed by each action from the UI itself. This helps create a single unit of work which is easier to deal with when you have a dynamic data set.
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
ChatService chatService = new ChatService();
JPanel panel = new JPanel();
String[] names = new String[] {"Bryan", "Alan", "George", "Henry"};
List<PeopleAction> actions = new ArrayList<>(names.length);
for (String name : names) {
PeopleAction action = new PeopleAction(chatService, name, false);
actions.add(action);
}
Random rnd = new Random();
actions.get(rnd.nextInt(names.length)).setRaised(true);
for (Action action : actions) {
JButton btn = new JButton(action);
panel.add(btn);
}
setLayout(new GridLayout(2, 1));
add(panel);
JPanel hostPane = new JPanel();
JButton clearButton = new JButton(new ClearAction(chatService));
hostPane.add(clearButton);
add(hostPane);
}
}
public class ChatService {
private List<ChatListener> listeners = new ArrayList<>(25);
public void addChatListeners(ChatListener listener) {
listeners.add(listener);
}
public void removeChatListener(ChatListener listener) {
listeners.remove(listener);
}
protected void fireChatCleared() {
if (listeners.isEmpty()) {
return;
}
for (ChatListener listener : listeners) {
listener.chatCleared();
}
}
public void clear() {
// Do what's required
fireChatCleared();
}
}
public interface ChatListener {
public void chatCleared();
}
public class PeopleAction extends AbstractAction implements ChatListener {
private String name;
private boolean raised;
public PeopleAction(ChatService chatService, String name, boolean raised) {
// You can use either LARGE_ICON_KEY or SMALL_ICON to set the icon
this.name = name;
if (raised) {
putValue(NAME, "* " + name);
} else {
putValue(NAME, name);
}
chatService.addChatListeners(this);
}
public void setRaised(boolean raised) {
if (raised) {
putValue(NAME, "* " + name);
} else {
putValue(NAME, name);
}
}
public boolean isRaised() {
return raised;
}
#Override
public void actionPerformed(ActionEvent evt) {
// Do what ever needs to be done
setRaised(!isRaised());
}
#Override
public void chatCleared() {
setRaised(false);
}
}
public class ClearAction extends AbstractAction {
private ChatService chatService;
public ClearAction(ChatService chatService) {
this.chatService = chatService;
putValue(NAME, "Clear");
}
#Override
public void actionPerformed(ActionEvent evt) {
chatService.clear();
}
}
}
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 am using a gui with JTextFields to collect some information and then a JButton that takes that infomration and writes it to a file, sets the gui visibility to false, and then uses Runnable to create an instance of another JFrame from a different class to display a slideshow.
I would like to access some of the information for the JTextFields from the new JFrame slideshow. I have tried creating an object of the previous class with accessor methods, but the values keep coming back null (I know that I have done this correctly).
I'm worried that when the accessor methods go to check what the variables equal the JTextFields appear null to the new JFrame.
Below is the sscce that shows this problem.
package accessmain;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class AccessMain extends JFrame implements ActionListener
{
private static final int FRAMEWIDTH = 800;
private static final int FRAMEHEIGHT = 300;
private JPanel mainPanel;
private PrintWriter outputStream = null;
private JTextField subjectNumberText;
private String subjectNumberString;
public static void main(String[] args)
{
AccessMain gui = new AccessMain();
gui.setVisible(true);
}
public AccessMain()
{
super("Self Paced Slideshow");
setSize(FRAMEWIDTH, FRAMEHEIGHT);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
//Begin Main Content Panel
mainPanel = new JPanel();
mainPanel.setBorder(new EmptyBorder(0,10,0,10));
mainPanel.setLayout(new GridLayout(7, 2));
mainPanel.setBackground(Color.WHITE);
add(mainPanel, BorderLayout.CENTER);
mainPanel.add(new JLabel("Subject Number: "));
subjectNumberText = new JTextField(30);
mainPanel.add(subjectNumberText);
mainPanel.add(new JLabel(""));
JButton launch = new JButton("Begin Slideshow");
launch.addActionListener(this);
mainPanel.add(launch);
//End Main Content Panel
}
#Override
public void actionPerformed(ActionEvent e)
{
String actionCommand = e.getActionCommand();
if(actionCommand.equals("Begin Slideshow"))
{
subjectNumberString = subjectNumberText.getText();
if(!(subjectNumberString.equals("")))
{
System.out.println(getSubjectNumber());
this.setVisible(false);
writeFile();
outputStream.println("Subject Number:\t" + subjectNumberString);
outputStream.close();
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
AccessClass testClass = new AccessClass();
testClass.setVisible(true);
}
});
}
else
{
//Add warning dialogue here later
}
}
}
private void writeFile()
{
try
{
outputStream = new PrintWriter(new FileOutputStream(subjectNumberString + ".txt", false));
}
catch(FileNotFoundException e)
{
System.out.println("Cannot find file " + subjectNumberString + ".txt or it could not be opened.");
System.exit(0);
}
}
public String getSubjectNumber()
{
return subjectNumberString;
}
}
And then creating a barebones class to show the loss of data:
package accessmain;
import javax.swing.*;
import java.awt.*;
public class AccessClass extends JFrame
{
AccessMain experiment = new AccessMain();
String subjectNumber = experiment.getSubjectNumber();
public AccessClass()
{
System.out.println(subjectNumber);
}
}
Hardcoding the accessor method with "test" like this:
public String getSubjectNumber()
{
return "test";
}
Running this method as below in the new JFrame:
SelfPaceMain experiment = new SelfPaceMain();
private String subjectNumber = experiment.getSubjectNumber();
System.out.println(subjectNumber);
Does cause the system to print "test". So the accessor methods seem to be working. However, trying to access the values from the JTextFields doesn't seem to work.
I would read the information from the file I create, but without being able to pass the subjectNumber (which is used as the name of the file), I can't tell the new class what file to open.
Is there a good way to pass data from JTextFields to other classes?
pass the argument 'AccessMain' or 'JTextField' to the second class:
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
AccessClass testClass = new AccessClass(AccessMain.this); //fixed this
testClass.setVisible(true);
}
});
Then reading the value of 'subjectNumber'(JTextField value) from the 'AccessMain' or 'JTextField' in the second class:
public class AccessClass extends JFrame
{
final AccessMain experiment;
public AccessClass(AccessMain experiment)
{
this.experiment = experiment;
}
public String getSubjectNumber(){
return experiment.getSubjectNumber();
}
}
Also, you should try Observer pattern.
A simple demo of Observalbe and Observer
Observable and Observer Objects
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...