Im building a Ui in Swing wherein my requirement is to have JPanes within a JTable. These JPanes will have JButtons within them.
My use case is as follows --
Im writing a MethodEditor wherein im providing a UI to store the methods within a supplied file. The UI would also allow editing the parameters being passed to the method on the click of a button.
Every single method would have a UI representation as follows --
My basic representation of the Method class is as follows --
public Class Method {
String methodName;
List<String> InputVariableNames;
String OutputVariableName;
}
Now i have a list of Method objects, List<Method> methodList on which i want to base my JTable.
This List is contained in a MethodModel class as follows --
public class MethodModel {
List<Method> methodModel;
}
I had asked a question earlier and have based my code on the answer provided there. My code however does not seem to be working. My code is as follows --
public class MethodEditor extends JTable {
private static final long serialVersionUID = 1L;
private MethodEditorModel model ;
private MethodCellRenderer cellRenderer;
public MethodEditor(MethodModel bean) {
setRowHeight(25);
this.setPreferredSize(new Dimension(500, 500));
model = new MethodEditorModel(bean);
this.setModel(model);
setupComponent();
}
private void setupComponent() {
cellRenderer = new MethodCellRenderer();
this.setDefaultRenderer(Object.class,cellRenderer);
this.setBorder(BorderFactory.createLineBorder(Color.GRAY));
}
private static class MethodEditorModel extends DefaultTableModel implements PropertyChangeListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private MethodModel bean;
public MethodEditorModel(MethodModel bean) {
this.bean = bean;
bean.addPropertyChangeListener(this);
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
fireTableDataChanged();
}
}
private static class MethodCellRenderer implements TableCellRenderer {
/**
*
*/
private static final long serialVersionUID = 1L;
private MethodEditorCellPanel renderer = new MethodEditorCellPanel();
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
MethodModel methodModel = (MethodModel)value;
for(Method method : methodModel.getMethodList()) {
renderer.setComponents((Method) method);
}
return renderer;
}
}
private static class MethodEditorCellPanel extends JPanel implements ActionListener {
private static final long serialVersionUID = 1L;
private JButton upButton;
private JButton downButton;
private JButton methodDetailsButton;
private Method method;
public MethodEditorCellPanel() {
upButton = new JButton("Up");
downButton = new JButton("Down");
}
public void setComponents(Method method)
{
this.method = method;
methodDetailsButton = new JButton(method.getMethodName());
upButton.addActionListener(this);
downButton.addActionListener(this);
methodDetailsButton.addActionListener(this);
Box verticalBar = Box.createHorizontalBox();
verticalBar.add(upButton);
verticalBar.add(Box.createHorizontalStrut(15));
verticalBar.add(methodDetailsButton);
verticalBar.add(Box.createHorizontalStrut(15));
verticalBar.add(downButton);
add(verticalBar);
}
#Override
public void actionPerformed(ActionEvent evt) {
if(evt.getSource().equals(downButton)) {
}
if(evt.getSource().equals(upButton)) {
}
if(evt.getSource().equals(methodDetailsButton)) {
}
}
}
}
The code compiles but the JTable does not show up.
Any pointers on what i may be doing wrong would be of great help.
Don't include another components to JTable. Let alone components with multiple other components. The reason is that JTable won't pass mouse events to its cells. So even when you have buttons inside JTable, then you would have to take care about pressing them by yourself, by:
get cell it was clicked to
get the exact coordinates
extrapolate these coordinates to the inner component
manually call click on the corresponding button.
And even then you won't get button animation and stuff.
If you need to arrange components into a table, use JPanel with GridLayout or GridBagLayout.
Related
I'created a JLabel that should display "TextA" if the variable count == -1,
"Text B" if the variable count == 0 and "TextC" if the variable count == 1.
I've used Swing to create my interface, which you can see below
TempConverter
The red rectangle shows where the JLabel should be.
I have tried creating 3 JLabels and changing the setVisible(Boolean) whenever the variable count value condition applies. This didn't work because I got the following error:
Exception in thread "main" java.lang.NullPointerException
at tempconverterUI.TempConverter.main(TempConverter.java:354)
C:\Users\x\AppData\Local\NetBeans\Cache\8.1\executor-snippets\run.xml:53: Java returned: 1
And the JLabels could not be placed in the same location in the GUI (overlapping was not possible).
I've tried using jLabel.setText() to change the Text displayed in the JLabel, whenever the variable condition applied. I got a similar error to the one above (if not the same).
I've read some other posts and researched further and found that some people suggested ActionListeners to be set but I am unsure that these will work with a simple variable, as opposed to a component in the GUI.
My Code is as follows:
package tempconverterUI;
import javax.swing.JOptionPane;
import messageBoxes.UserData;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.WString;
public class TempConverter extends javax.swing.JFrame {
public interface someLib extends Library
{
public int engStart();
public int endStop();
public int engCount();
public WString engGetLastError();
public int engSetAttribute(WString aszAttributeID, WString aszValue);
}
/**
* Creates new form TempConverter
*/
public TempConverter() {
initComponents();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
#SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
The layout is created here, followed by the Temperature convertion methods and unrelated component's functionality (which I believe is not relevant in this case)
/**
* #param args the command line arguments
*/
public static void main(String args[]) {
/**This is where the Login form gets created*/
UserData.popUp();
/**After this the Library functions are called, which will return the variable count value*/
someLib lib = (someLib) Native.loadLibrary("someLib", someLib.class);
int startResult = lib.engStart();
System.out.println(startResult);
if (startResult < 0)
{
System.out.println(lib.engGetLastError());
}
System.out.println(UserData.getAcInput());
int setAtResult = lib.engSetAttribute(new WString("CODE"), UserData.getAcInput());
System.out.println(setAtResult);
if (setAtResult < 0)
{
System.out.println(lib.engGetLastError());
}
And next is the piece of code from where I should control the JLabel Text to display
int count = lib.engCount();
System.out.println(count);
if (count == -1)
{
System.out.println(lib.engGetLastError());
}
else if (count == 0)
{
}
else
{
}
new TempConverter().setVisible(true);
}
// Variables declaration - do not modify
private javax.swing.JPanel bottomPanel;
private javax.swing.JButton convertButton;
private static javax.swing.JButton button;
private javax.swing.JTextField from;
private javax.swing.JComboBox<String> fromCombo;
private javax.swing.JLabel fromLabel;
private javax.swing.JLabel title;
private javax.swing.JTextField to;
private javax.swing.JComboBox<String> toCombo;
private javax.swing.JLabel toLabel;
private javax.swing.JPanel topPanel;
// End of variables declaration
}
Any help with this would be much appreciated. If you could include a simple code example as well, this would be fantastic as I am new to Java (and programming, in general).
Issues:
Don't set the JLabel visible, but rather add it initially to the GUI, leave it visible by default, and simply set its text via setText(...).
Give the class that holds the JLabel public methods that allow outside classes the ability to set the label's text. Something like public void setLabelText(String text), and in the method call setText(text) on the JLabel.
Debug your NullPointerException as you would any other NPE -- look at the stacktrace, find the line that throws it, and then look back into the code to see why a key variable on that line is null.
When and how you change the JLabel will depend on what event you want to listen for. If it is user input, then you will want to respond to that input, be it an ActionListener added to a JButton or to a JTextField, or an itemListener added to a JRadioButton.
If you want instead to listen for the change in state of a variable, no matter how the variable is changed, then make it a "bound property" (tutorial) using PropertyChangeSupport and a PropertyChangeListener.
For an example of the latter:
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;
#SuppressWarnings("serial")
public class ShowCount extends JPanel {
private static final int TIMER_DELAY = 1000;
private JLabel countLabel = new JLabel(" ");
private CountModel model = new CountModel();
public ShowCount() {
model.addPropertyChangeListener(CountModel.COUNT, new ModelListener(this));
setPreferredSize(new Dimension(250, 50));
add(new JLabel("Count:"));
add(countLabel);
Timer timer = new Timer(TIMER_DELAY, new TimerListener(model));
timer.start();
}
public void setCountLabelText(String text) {
countLabel.setText(text);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
ShowCount mainPanel = new ShowCount();
JFrame frame = new JFrame("ShowCount");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
}
class CountModel {
public static final String COUNT = "count"; // for count "property"
// support object that will notify listeners of change
private SwingPropertyChangeSupport support = new SwingPropertyChangeSupport(this);
private int count = 0;
public int getCount() {
return count;
}
public void setCount(int count) {
int oldValue = this.count;
int newValue = count;
this.count = count;
// notify listeners that count has changed
support.firePropertyChange(COUNT, oldValue, newValue);
}
// two methods to allow listeners to register with support object
public void addPropertyChangeListener(PropertyChangeListener listener) {
support.addPropertyChangeListener(listener);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
support.addPropertyChangeListener(propertyName, listener);
}
}
class ModelListener implements PropertyChangeListener {
private ShowCount showCount;
public ModelListener(ShowCount showCount) {
super();
this.showCount = showCount;
}
#Override
public void propertyChange(PropertyChangeEvent evt) {
int newValue = (int) evt.getNewValue();
showCount.setCountLabelText(String.format("%03d", newValue));
}
}
class TimerListener implements ActionListener {
private CountModel model;
public TimerListener(CountModel model) {
super();
this.model = model;
}
#Override
public void actionPerformed(ActionEvent e) {
int oldCount = model.getCount();
int newCount = oldCount + 1;
model.setCount(newCount);
}
}
I'm very new to java which is why I'm using NetBeans GUI builder, basically I've created a JFrame which has two components and I'm able to save the data of two text fields and use a submit button to put this into a JTable thats in the JFrame. But I've created a new JFrame specifically to hold the JTable. so one JFrame has two textfield and a submit button, and another JFrame as a JTable. below is the code I used when I had the JTable, button and two textfield in one JFrame. How would I go about trying to save data into a different JFrame containing only JTable?
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
DefaultTableModel model = (DefaultTableModel) table.getModel();
model.addRow(new Object[]{/*some stuff here ignore for this question*/});
}
One way to update table from Frame2 with values from text fields from Frame1 is to use the observer pattern. Frame1 will have a list of observers which need to be updated once the observable (Frame1) inserts or has new values. I will add the code to be able to understand this better. Also, have a look at the Observer Pattern.
Let's define an Observable interface (these are all the methods that an Observable needs to implement)
public interface Observable {
public void addObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver(String[] row);
}
Let's define Frame1 which will be the Observervable
public class Frame1 extends javax.swing.JFrame implements Observable{
private javax.swing.JTextField jTextField1;
private javax.swing.JTextField jTextField2;
private javax.swing.JButton submitButton;
private List<Observer> observers = new ArrayList<>();
public Frame1() {
initComponents(); // 2 text fields and 1 button
}
private void initComponents() {
// I will skip this part you can generate it with NetBeans
// Basically initialise jTextField1, jTextField2, and submitButton
}
private void submitButtonActionPerformed(java.awt.event.ActionEvent evt) {
String[] row = {jTextField1.getText(), jTextField2.getText()};
notifyObserver(row);
}
#Override
public void addObserver(Observer o) {
observers.add(o); // subscribe new observer
}
#Override
public void removeObserver(Observer o) {
observers.remove(o); // unsubscribe new observer
}
#Override
public void notifyObserver(String[] row) {
for (Observer observer: observers) {
observer.update(row); // notify all observers that new row values are available
}
}
}
Also, let's define an Observer interface (these are all the methods that an Observer needs to implement)
public interface Observer {
public void update(String[] row);
}
Let's define Frame2 which will be the Observer
public class Frame2 extends javax.swing.JFrame implements Observer {
private javax.swing.JTable jTable1;
public Frame2() {
initComponents();
}
private void initComponents() {
// I will skip this part you can generate it with NetBeans
// Basically initialise jTable1
}
public void addRow(String column1, String column2){
DefaultTableModel model = (DefaultTableModel) jTable1.getModel();
model.addRow(new Object[]{column1, column2});
}
#Override
public void update(String[] row) {
addRow(row[0], row[1]);
}
}
Now, let's wrap everything and test:
public class Main {
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
Frame2 frame2 = new Frame2();
Frame1 frame1 = new Frame1();
// Register frame2 as an observer of frame1
frame1.addObserver(frame2);
frame1.setVisible(true);
frame2.setVisible(true);
}
});
}
}
I currently have a JTextField and inside that, I have default text.
The problem I currently have is getting that JTextField to have a working ActionListener. I have added an action listener to the component, but when I use FocusListener to check for focus, it will not give any output/reply.
Any help will be much appreciated. And please supply me with some example code of what I should change, thanks.
PS. I am using this class as a component from another class, so in another class I wrote:
window.add(smartTextField);
SmartText.java
package com.finn.multiweb;
import java.awt.Color;
import javax.swing.JTextField;
public class SmartText extends JTextField {
private static final long serialVersionUID = 1L;
JTextField textField = new JTextField();
String defaultText;
boolean hasDefaultText;
public SmartText() {
super();
hasDefaultText = false;
notFocused();
}
public SmartText(String defaultText) {
super(defaultText);
this.defaultText = defaultText;
hasDefaultText = true;
notFocused();
}
private void notFocused() {
super.setForeground(Color.GRAY);
if (hasDefaultText == true) {
super.setText(defaultText);
} else if (hasDefaultText == false) {
super.setText("");
}
}
private void isFocused() {
super.setForeground(Color.BLACK);
super.setText("");
}
private void focusGained(java.awt.event.FocusEvent evt) {
System.out.println("test");
}
}
You've not added a FocusListener to the field
// You need to implement the FocusListener interface
public class SmartText extends JTextField implements FocusListener {
private static final long serialVersionUID = 1L;
JTextField textField = new JTextField();
String defaultText;
boolean hasDefaultText;
public SmartText() {
super();
hasDefaultText = false;
notFocused();
// Then register yourself as interested in focus events
addFocusListener(this);
}
public SmartText(String defaultText) {
super(defaultText);
this.defaultText = defaultText;
hasDefaultText = true;
notFocused();
// Then register yourself as interested in focus events
addFocusListener(this);
}
// Then implement the contract of the FocusListener interface
public void focusGained(FocusEvent e) {
}
public void focusLost(FocusEvent e) {
}
Take a read through How to Write a Focus Listener for more details
From the looks of your code, you trying to add "prompt support" to the field, you may consider using the PromptSupport from the SwingLabs, SwingX libraries, for example
You can use the Text Prompt which is a single class.
To work with FocusListener Interface and in order to listen’s the keyboards gaining or losing focus, the listener object created from class is need to registered with a component using the component’s addFocusListener() method. The two important method focusGained(FocusEvent e) and void focusLost(FocusEvent e) which helps to find which component is focused.
Take a read through What is FocusListener Interface and How it Work and Validate Text Field Using FocusListener Interface in Java for more details with proper examples.
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.
So here I have two classes: Customer Order Class and Confirmation Class. I want to access the data stored in LastNameTextField (Customer Order Class) and set it as the text for UserLastNameLabel (Confirmation Class) after clicking a "Submit" button. For some reason however, the output displays nothing.
Snippet of my code:
package customer_order;
public class customer_order extends Frame{
private static final long serialVersionUID = 1L;
private JPanel jPanel = null;
private JLabel LastNameLabel = null;
protected JTextField LastNameTextField = null;
private JButton SubmitButton = null;
public String s;
public customer_order() {
super();
initialize();
}
private void initialize() {
this.setSize(729, 400);
this.setTitle("Customer Order");
this.add(getJPanel(), BorderLayout.CENTER);
}
/**
* This method initializes LastNameTextField
*
* #return javax.swing.JTextField
*/
public JTextField getLastNameTextField() {
if (LastNameTextField == null) {
LastNameTextField = new JTextField();
LastNameTextField.setBounds(new Rectangle(120, 100, 164, 28));
LastNameTextField.setName("LastNameTextField");
}
return LastNameTextField;
}
/**
* This method initializes SubmitButton
*
* #return javax.swing.JButton
*/
private JButton getSubmitButton() {
if (SubmitButton == null) {
SubmitButton = new JButton();
SubmitButton.setBounds(new Rectangle(501, 225, 96, 29));
SubmitButton.setName("SubmitButton");
SubmitButton.setText("Submit");
SubmitButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent e) {
System.out.println("actionPerformed()"); // TODO Auto-generated Event stub actionPerformed()
//THE STRING I WANT
s = LastNameTextField.getText();
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new confirmation().setVisible(true);
}
});
}
});
}
return SubmitButton;
}
package customer_order;
public class confirmation extends customer_order{
private static final long serialVersionUID = 1L;
private JPanel jPanel = null; // #jve:decl-index=0:visual-constraint="58,9"
private JLabel LastNameLabel = null;
private JLabel UserLastNameLabel = null;
// #jve:decl-index=0:
/**
* This method initializes frame
*
* #return java.awt.Frame
*/
public confirmation() {
super();
initialize();
}
private void initialize() {
this.setSize(729, 400);
this.setTitle("Confirmation");
this.add(getJPanel(), BorderLayout.CENTER);
}
/**
* This method initializes jPanel
*
* #return javax.swing.JPanel
*/
private JPanel getJPanel() {
if (jPanel == null) {
UserLastNameLabel = new JLabel();
UserLastNameLabel.setBounds(new Rectangle(121, 60, 167, 26));
//THE PROBLEM?
UserLastNameLabel.setText(s);
}
return jPanel;
}
The s field will only have a value if the getSubmitButton method is called (it doesn't seem to be in your code), the button it returns is added to the form, and the user has already clicked on the button.
When you call new confirmation(), you get a new object with its own field s, and for s in the new confirmation to have a value it will also need to have the getSubmitButton method called, and the action listener triggered.
I don't really see any reason for confirmation to extend customer_order in this case. Normally you extend a class to specialise - for example, if you had certain orders which were treated differently because they repeated every week you may make a new class repeating_customer_order which adds extra fields to deal with the repeating. Accessing data from one class in another isn't really a reason to make one extend the other.
For what you want to do, I'd suggest removing the inheritence (get rid of extends customer_order), and then passing the value of s into the constructor:
customer_order:
s = LastNameTextField.getText();
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new confirmation(s).setVisible(true);
}
});
confirmation:
private final String s;
public confirmation(final String s) {
super();
this.s = s;
initialize();
}
As an aside, your code would be much easier to read if you kept to the standard naming schemes used in most Java apps:
classes should begin with an uppercase letter and be CamelCased, e.g. CustomerOrder instead of customer_order
fields should begin with a lowercase letter, e.g. lastNameTextField or submitButton
This helps people see at a glance what is going on - at the minute a quick glance at LastNameTextField.getText() makes it look like you're calling a static method in a class called LastNameTextField!