I am trying to separate the logic and UI design of the below code into their own classes but I'm having trouble figuring out how to call certain methods linked to the swing components of my GUI class in the Controller class.
My problem is that when I create an instance of the GUI class in the Controller class, the JRadioButtons class variables in GUI are only initialized in the createRBButtonsPanel() method in GUI. This then gives me a NullPointerException when I attempt to call the cokeRb.getText() method for example in the actionPerformed method of the Controller as I have not called the chain of events that leads them to be initialized.
I am trying to avoid setting them all to static as I am sure it is not the best practice to do this.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class GUI {
private CoinAcceptor coinSlot = new CoinAcceptor();
private VMController controller;
GUI(VMController controller) {
this.controller = controller;
}
private JRadioButton cokeRb, lemonadeRb, tangoRb, waterRb, pepsiRb, spriteRb;
private JButton purchaseBtn , clearBtn, cancelBtn, loyaltyCardBTN;
private JTextField moneyRemainingTf, balanceTf;
private GridBagConstraints c = new GridBagConstraints();
//// Getters /////////////////////////////////
public String getMoneyTFText() { return this.moneyRemainingTf.getText(); }
public String getBalanceTF() { return balanceTf.getText(); }
public String getCokeRbText() { return this.cokeRb.getText(); }
public String getTangoRbText() { return this.tangoRb.getText(); }
public String getLemonadeRbText() { return this.lemonadeRb.getText(); }
public String getWaterRbText() { return this.waterRb.getText(); }
public String getPepsiRbText() { return this.pepsiRb.getText(); }
public String getSpriteRbText() { return this.spriteRb.getText(); }
//// Setters /////////////////////////////////
public void setBalanceTF(String text) { balanceTf.setText(text); }
public void setCokeRbText(String text) { this.cokeRb.setText(text); }
public void setLemonadeRbText(String text) { this.lemonadeRb.setText(text); }
public void setTangoRbText(String text) { this.tangoRb.setText(text); }
public void setWaterRbText(String text) { this.waterRb.setText(text); }
public void setPepsiRbText(String text) { this.pepsiRb.setText(text);}
public void setSpriteRbText(String text) { this.spriteRb.setText(text); }
public void deselectRadioButtons(String button) {
switch (button) {
case "COKE":
this.cokeRb.setSelected(true);
this.lemonadeRb.setSelected(false);
this.tangoRb.setSelected(false);
this.waterRb.setSelected(false);
this.pepsiRb.setSelected(false);
this.spriteRb.setSelected(false);
break;
case "LEMONADE":
this.cokeRb.setSelected(false);
this.lemonadeRb.setSelected(true);
this.tangoRb.setSelected(false);
this.waterRb.setSelected(false);
this.pepsiRb.setSelected(false);
this.spriteRb.setSelected(false);
break;
case "TANGO":
this.lemonadeRb.setSelected(false);
this.cokeRb.setSelected(false);
this.tangoRb.setSelected(true);
this.waterRb.setSelected(false);
this.pepsiRb.setSelected(false);
this.spriteRb.setSelected(false);
break;
case "WATER":
this.lemonadeRb.setSelected(false);
this.tangoRb.setSelected(false);
this.cokeRb.setSelected(false);
this.waterRb.setSelected(true);
this.pepsiRb.setSelected(false);
this.spriteRb.setSelected(false);
break;
case "PEPSI":
this.lemonadeRb.setSelected(false);
this.tangoRb.setSelected(false);
this.waterRb.setSelected(false);
this.cokeRb.setSelected(false);
this.pepsiRb.setSelected(true);
this.spriteRb.setSelected(false);
break;
case "SPRITE":
this.lemonadeRb.setSelected(false);
this.tangoRb.setSelected(false);
this.waterRb.setSelected(false);
this.pepsiRb.setSelected(false);
this.cokeRb.setSelected(false);
this.spriteRb.setSelected(true);
break;
default:
this.lemonadeRb.setSelected(false);
this.tangoRb.setSelected(false);
this.waterRb.setSelected(false);
this.pepsiRb.setSelected(false);
this.cokeRb.setSelected(false);
this.spriteRb.setSelected(false);
break;
}
}
public void clearAllFields() {
deselectRadioButtons("");
balanceTf.setText("");
this.moneyRemainingTf.setText("");
}
public boolean isRBSelected() {
JRadioButton[] radioButtons = new JRadioButton[] { cokeRb, lemonadeRb, tangoRb, waterRb, pepsiRb, spriteRb };
for (JRadioButton button : radioButtons) {
if (button.isSelected())
return true;
}
return false;
}
private JPanel createRBButtonsPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(3, 6, 5, 5));
panel.setBorder(BorderFactory.createTitledBorder("Select and item"));
cokeRb = new JRadioButton("Coke : £1.50");
cokeRb.addActionListener(controller);
cokeRb.setActionCommand("COKE");
lemonadeRb = new JRadioButton("Lemonade : £1.20");
lemonadeRb.addActionListener(controller);
lemonadeRb.setActionCommand("LEMONADE");
tangoRb = new JRadioButton("Tango : £1.40");
tangoRb.addActionListener(controller);
tangoRb.setActionCommand("TANGO");
waterRb = new JRadioButton("Water : £1");
waterRb.addActionListener(controller);
waterRb.setActionCommand("WATER");
pepsiRb = new JRadioButton("Pepsi : £1.30");
pepsiRb.addActionListener(controller);
pepsiRb.setActionCommand("PEPSI");
spriteRb = new JRadioButton("Sprite : £1.20");
spriteRb.addActionListener(controller);
spriteRb.setActionCommand("SPRITE");
purchaseBtn = new JButton("PURCHASE");
purchaseBtn.addActionListener(controller);
clearBtn = new JButton("CLEAR");
clearBtn.addActionListener(controller);
cancelBtn = new JButton("CANCEL");
cancelBtn.addActionListener(controller);
panel.add(cokeRb);
panel.add(lemonadeRb);
panel.add(tangoRb);
panel.add(waterRb);
panel.add(pepsiRb);
panel.add(spriteRb);
panel.add(purchaseBtn);
panel.add(clearBtn);
panel.add(cancelBtn);
c.gridx = 0;
c.gridy = 0;
c.insets = new Insets(10, 10, 10, 10);
return panel;
}
private JPanel createInputPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
balanceTf = new JTextField();
// balanceTf.setForeground(Color.gray);
// balanceTf.addFocusListener(new FocusListener() {
//
// @Override
// public void focusGained(FocusEvent e) {
// if (balanceTf.getText().equals("2.50")) {
// balanceTf.setText("");
// balanceTf.setForeground(Color.black);
// }
// }
//
// @Override
// public void focusLost(FocusEvent e) {
// if (balanceTf.getText().isEmpty()) {
// balanceTf.setText("2.50");
// balanceTf.setForeground(Color.gray);
// }
// }
// });
moneyRemainingTf = new JTextField();
moneyRemainingTf.setEditable(false);
loyaltyCardBTN = new JButton("Scan Loyalty Card");
loyaltyCardBTN.addActionListener(controller);
loyaltyCardBTN.setActionCommand("SCAN-CARD");
panel.add(new JLabel("Cash"));
panel.add(balanceTf);
panel.add(new JLabel("Remaining Balance"));
panel.add(moneyRemainingTf);
panel.add(loyaltyCardBTN);
c.gridx = 1;
c.gridy = 0;
return panel;
}
private JPanel coinKeypad() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createTitledBorder("Coins"));
panel.setLayout(new GridLayout(3, 2, 5, 5));
JButton button = new JButton("5p");
button.addActionListener(e -> {
coinSlot.depositCoin(0.05);
balanceTf.setText(String.valueOf(coinSlot.getCoinsValue()));
});
panel.add(button);
button = new JButton("10p");
button.addActionListener(e -> {
coinSlot.depositCoin(0.10);
balanceTf.setText(String.valueOf(coinSlot.getCoinsValue()));
});
panel.add(button);
button = new JButton("20p");
button.addActionListener(e -> {
coinSlot.depositCoin(0.20);
balanceTf.setText(String.valueOf(coinSlot.getCoinsValue()));
});
panel.add(button);
button = new JButton("50p");
button.addActionListener(e -> {
coinSlot.depositCoin(0.50);
balanceTf.setText(String.valueOf(coinSlot.getCoinsValue()));
});
panel.add(button);
button = new JButton("£1");
button.addActionListener(e -> {
coinSlot.depositCoin(1);
balanceTf.setText(String.valueOf(coinSlot.getCoinsValue()));
});
panel.add(button);
button = new JButton("£2");
button.addActionListener(e -> {
coinSlot.depositCoin(2);
balanceTf.setText(String.valueOf(coinSlot.getCoinsValue()));
});
panel.add(button);
c.gridx = 0;
c.gridwidth = 2;
c.fill = GridBagConstraints.HORIZONTAL;
c.gridy = 1;
return panel;
}
private JPanel createBody() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.setLayout(new GridBagLayout());
panel.add(createRBButtonsPanel(), c);
panel.add(createInputPanel(), c);
panel.add(coinKeypad(), c);
return panel;
}
private JPanel createHeader() {
JPanel panel = new JPanel();
JLabel label = new JLabel("Vending Machine");
label.setFont(new Font("Helvetica Neue", 3, 48));
panel.add(label);
return panel;
}
private void addComponentsToPane(Container pane) {
JPanel header = createHeader();
JPanel body = createBody();
pane.add(header, BorderLayout.NORTH);
pane.add(body, BorderLayout.CENTER);
}
private void setLookAndFeel() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
}
public void createAndShowGUI() {
JFrame frame = new JFrame("Vending Machine with a Four Dillabyte Crossfade");
addComponentsToPane(frame.getContentPane());
frame.setSize(800, 500);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
setLookAndFeel();
frame.setVisible(true);
frame.pack();
}
}
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JOptionPane;
public class VMController implements ActionListener {
private PaymentHandler handler = new PaymentHandler();
private double balance;
private String selectedRBText;
private GUI gui = new GUI(this);
VMController() {
this.balance = 0;
}
private double balanceToDouble() {
double balance = 0;
try {
return Double.valueOf(gui.getBalanceTF());
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(null, "Invalid coin deposit.", "Enter numerical amount",
JOptionPane.ERROR_MESSAGE);
return balance;
}
}
private double productPriceToDouble() {
double productPrice = 0;
try {
return Double.valueOf(selectedRBText);
} catch (NullPointerException ex) {
return productPrice;
}
}
@Override
public void actionPerformed(ActionEvent e) {
String action = e.getActionCommand();
switch (action) {
case "COKE":
gui.deselectRadioButtons("COKE");
selectedRBText = gui.getCokeRbText().substring(gui.getCokeRbText().indexOf('£') 1);
break;
case "LEMONADE":
gui.deselectRadioButtons("LEMONADE");
selectedRBText = gui.getLemonadeRbText().substring(gui.getLemonadeRbText().indexOf('£') 1);
break;
case "TANGO":
gui.deselectRadioButtons("TANGO");
selectedRBText = gui.getTangoRbText().substring(gui.getTangoRbText().indexOf('£') 1);
break;
case "WATER":
gui.deselectRadioButtons("WATER");
selectedRBText = gui.getWaterRbText().substring(gui.getWaterRbText().indexOf('£') 1);
break;
case "PEPSI":
gui.deselectRadioButtons("PEPSI");
selectedRBText = gui.getPepsiRbText().substring(gui.getPepsiRbText().indexOf('£') 1);
break;
case "SPRITE":
gui.deselectRadioButtons("SPRITE");
selectedRBText = gui.getSpriteRbText().substring(gui.getSpriteRbText().indexOf('£') 1);
break;
case "PURCHASE":
if (!gui.isRBSelected())
JOptionPane.showMessageDialog(null, "Choose a product to purchase", "Select a product",
JOptionPane.ERROR_MESSAGE);
else {
if (handler.handlePayment(balanceToDouble(), productPriceToDouble()))
gui.setBalanceTF(String.valueOf(handler.getBalance()));
else
JOptionPane.showMessageDialog(null, "Balance is insufficient for product purchase.",
"Insufficient Funds", JOptionPane.ERROR_MESSAGE);
}
break;
case "CANCEL":
if (handler.getBalance() > 0) {
JOptionPane.showMessageDialog(null, "Order Cancelled.\nChange dispensed.\n£" handler.getBalance());
}
JOptionPane.showMessageDialog(null, "Order Cancelled.");
System.exit(0);
break;
case "CLEAR":
gui.clearAllFields();
break;
case "SCAN-CARD":
System.out.println("scan card");
break;
default:
System.out.println("Action command not defined - \"" action "\"");
break;
}
}
}
CodePudding user response:
Introduction
I copied your code into my Eclipse IDE. A class was missing, so I couldn't see what your GUI looks like.
So I created this GUI.
I left off the loyalty card button.
Explanation
Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section.
I use the model-view-controller (MVC) pattern when creating a Swing application. The name implies that you create the model first, then the view, then the controller.
The application model consists of one or more plain Java getter/setter classes.
The view consists of one JFrame
, one or more JPanels
, and Swing components.
Each Action
or ActionListener
class makes up the controller. There's usually not one controller to "rule them all".
Model
The first model class I created is the Item
class. The Item
class holds a name and a price. The price is specified in pence. Using an int field to hold the price makes the calculations easier. I translate the pence into pounds when displaying the prices.
The second model class I created is the VendingMachineModel
class. The VendingMachineModel
class holds a java.util.List
of items, a java.util.List
of coins, an int
balance field, and an int
change field.
View
Your view was easy to follow and well structured, so I don't have a lot of comments here. I used a ButtonGroup
to limit the JRadioButtons
to one selection.
Don't make all the Swing components class fields. It's confusing for readers of your code. Class fields should only be used for fields needed in more than one method.
Order your methods within a class from the most important method to the least important methods. This makes the code easier for people to read and understand.
Controller
I used lambda expressions for all the JButtons
except the purchase button. That ActionPerformed
method was too large for a lambda expression, in my opinion, so I created a PurchaseListener
class.
Code
Here's the complete runnable code. I made the additional classes inner classes so I could post the code as one block.
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class VendingMachineExample implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new VendingMachineExample());
}
private ButtonGroup buttonGroup;
private JFrame frame;
private JTextField balanceField, changeField;
private final VendingMachineModel model;
public VendingMachineExample() {
this.model = new VendingMachineModel();
}
@Override
public void run() {
frame = new JFrame("Vending Machine with a Four Dillabyte Crossfade");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createHeaderPanel(), BorderLayout.NORTH);
frame.add(createBodyPanel(), BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createHeaderPanel() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JLabel label = new JLabel("Vending Machine");
label.setFont(new Font("Helvetica Neue", 3, 48));
panel.add(label);
return panel;
}
private JPanel createBodyPanel() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.setLayout(new FlowLayout());
panel.add(createVendingPanel());
panel.add(createDisplayPanel());
panel.add(createCoinKeypad());
return panel;
}
private JPanel createVendingPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(0, 3, 5, 5));
panel.setBorder(BorderFactory.createTitledBorder("Select an item"));
buttonGroup = new ButtonGroup();
for (Item item : model.getItems()) {
double price = 0.01 * item.getPrice();
String text = item.getName() ": £" String.format("%.2f", price);
JRadioButton button = new JRadioButton(text);
text = item.getName() ";;;" item.getPrice();
button.setActionCommand(text);
buttonGroup.add(button);
panel.add(button);
}
JButton button = new JButton("Purchase");
button.addActionListener(new PurchaseListener(this, model));
panel.add(button);
button = new JButton("Clear");
button.addActionListener(event -> {
buttonGroup.clearSelection();
});
panel.add(button);
button = new JButton("Cancel");
button.addActionListener(event -> {
buttonGroup.clearSelection();
int amount = model.getBalance();
model.setBalance(0);
model.setChange(amount);
updateDisplayPanel();
});
panel.add(button);
return panel;
}
private JPanel createDisplayPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(new JLabel("Cash"));
balanceField = new JTextField(10);
balanceField.setEditable(false);
balanceField.setHorizontalAlignment(JTextField.CENTER);
panel.add(balanceField);
panel.add(new JLabel("Change"));
changeField = new JTextField(10);
changeField.setEditable(false);
changeField.setHorizontalAlignment(JTextField.CENTER);
panel.add(changeField);
updateDisplayPanel();
return panel;
}
public void updateDisplayPanel() {
double amount = 0.01 * model.getBalance();
String text = "£" String.format("%.2f", amount);
balanceField.setText(text);
amount = 0.01 * model.getChange();
text = "£" String.format("%.2f", amount);
changeField.setText(text);
}
private JPanel createCoinKeypad() {
JPanel panel = new JPanel();
panel.setBorder(BorderFactory.createTitledBorder("Coins"));
panel.setLayout(new GridLayout(0, 2, 5, 5));
for (Item item : model.getCoins()) {
JButton button = new JButton(item.getName());
button.setActionCommand(Integer.toString(item.getPrice()));
button.addActionListener(event -> {
model.setChange(0);
model.addCoin(item.getPrice());
updateDisplayPanel();
});
panel.add(button);
}
return panel;
}
public ButtonGroup getButtonGroup() {
return buttonGroup;
}
public JFrame getFrame() {
return frame;
}
public class PurchaseListener implements ActionListener {
private final VendingMachineExample view;
private final VendingMachineModel model;
public PurchaseListener(VendingMachineExample view, VendingMachineModel model) {
this.view = view;
this.model = model;
}
@Override
public void actionPerformed(ActionEvent event) {
ButtonModel buttonModel = view.getButtonGroup().getSelection();
if (buttonModel == null) {
JOptionPane.showMessageDialog(view.getFrame(),
"Please select an item", "Select an item",
JOptionPane.ERROR_MESSAGE);
} else {
String text = buttonModel.getActionCommand();
String[] parts = text.split(";;;");
String name = parts[0];
int amount = Integer.valueOf(parts[1]);
int balance = model.getBalance();
if (balance >= amount) {
displayPurchaseMessage(name, amount, balance);
} else {
displayDifferenceMessage(amount, balance);
}
}
}
private void displayPurchaseMessage(String name, int amount, int balance) {
String text;
text = "You purchased a " name;
JOptionPane.showMessageDialog(view.getFrame(), text, "Item purchased",
JOptionPane.INFORMATION_MESSAGE);
model.purchaseProduct(amount);
int change = balance - amount;
model.setBalance(0);;
model.setChange(change);
view.updateDisplayPanel();
view.getButtonGroup().clearSelection();
}
private void displayDifferenceMessage(int amount, int balance) {
String text;
int difference = amount - balance;
double price = 0.01 * difference;
text = "Please deposit an additional £"
String.format("%.2f", price);
JOptionPane.showMessageDialog(view.getFrame(), text, "Add Coins",
JOptionPane.ERROR_MESSAGE);
}
}
public class VendingMachineModel {
private int balance, change;
private final List<Item> coins, items;
public VendingMachineModel() {
this.balance = 0;
this.change = 0;
this.coins = new ArrayList<>();
this.coins.add(new Item("5p", 5));
this.coins.add(new Item("10p", 10));
this.coins.add(new Item("20p", 20));
this.coins.add(new Item("50p", 50));
this.coins.add(new Item("£1", 100));
this.coins.add(new Item("£2", 200));
this.items = new ArrayList<>();
this.items.add(new Item("Coke", 150));
this.items.add(new Item("Lemonade", 120));
this.items.add(new Item("Tango", 140));
this.items.add(new Item("Water", 100));
this.items.add(new Item("Pepsi", 130));
this.items.add(new Item("Sprite", 120));
}
public List<Item> getCoins() {
return coins;
}
public List<Item> getItems() {
return items;
}
public void addCoin(int amount) {
this.balance = amount;
}
public void purchaseProduct(int amount) {
this.balance -= amount;
}
public void setBalance(int balance) {
this.balance = balance;
}
public int getBalance() {
return balance;
}
public void setChange(int change) {
this.change = change;
}
public int getChange() {
return change;
}
}
public class Item {
private final int price;
private final String name;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public int getPrice() {
return price;
}
public String getName() {
return name;
}
}
}