I am task to do a simple order system. I want the JLabel (Amount: $0.00) show the corresponding amount on what the user picks for his burger and condiments. For example, if the user click on beef, the label will change into "Amount: $4.00", and when he choose a condiment, it will add $0.50 to the total based on how many condiments he picks and vice versa. Also, when the user unchecks a condiment (JCheckBox), it will deduct $0.50 dollars to the total.
My code for beef JRadioButton:
private void beef_radioBtnActionPerformed(java.awt.event.ActionEvent evt) {
total_amount.setText("Amount: $4.00");
ketchup_Checkbox.setEnabled(true);
mustard_Checkbox.setEnabled(true);
pickles_Checkbox.setEnabled(true);
}
Code for ketchup JCheckBox:
private void ketchup_CheckboxActionPerformed(java.awt.event.ActionEvent evt) {
float condiments_amount = (float) 0.50;
float beef_amount = (float) 4.00;
float total;
if (beef_radioBtn.isSelected()){
total = beef_amount + condiments_amount;
total_amount.setText("Amount: $" + decimal.format(total));
if (!ketchup_Checkbox.isSelected()){
total_amount.setText("Amount: $" + decimal.format(4.50 - condiments_amount));
}
else if (mustard_Checkbox.isSelected()){
total_amount.setText("Amount: $" + decimal.format(4.50 + condiments_amount));
}
else if (pickles_Checkbox.isSelected()){
total_amount.setText("Amount: $" + decimal.format(4.50 + condiments_amount));
}
}
}
Okay, buckle up, this is going to be a bit of ride.
One of the most powerful concepts you have available to you is the concept of "model". A model is just something that "models" something and is a way to seperate different areas of your program. So a model, models the data (think of it like a container) and the view will then use those models to format the data to the user (separation of concerns). A model may also contain business logic or perform calculations depending on its requirements.
The allows you to centralise concepts so you don't end up repeating yourself or forgetting to do things. It's also a way to change how parts of the program work, also known as "delegation"
Starting point, some interfaces
Well, that's a lot of "blah", so let's get started. I prefer to use interfaces to describe things, it provides a lot of freedom, as you can put different interfaces together to suit different requirements.
The Menu
Okay, simple concept, this will be a list of items which are available to sell
public interface Menu {
public List<MainMenuItem> getMenuItems();
}
Menu items
A description of a menu item, pretty basic
public interface MenuItem {
public String getDescription();
public double getPrice();
}
"Main" menu items
These are all the "top level", "stand alone" menu items and in our case, can have condiments :D
public interface MainMenuItem extends MenuItem {
public List<Condiment> getCondiments();
}
Condiments
Condiments are a "special" MenuItem, as they are associated with a MainMenuItem
public interface Condiment extends MenuItem {
}
Burgers
This is just a demonstration of some of the things you could do, Burger isn't anything special, but as you will see, we can use this concept to do different things
public interface Burger extends MainMenuItem {
}
Order
And finally, the "order", what have we ordered and what condiments do we want with it
public interface Order {
public MainMenuItem getItem();
public void setItem(MainMenuItem item);
public List<Condiment> getCondiments();
public void addCondiment(Condiment condiment);
public void removeCondiment(Condiment condiment);
public double getTally();
}
This is a good demonstration of the power of the model. The Order has a getTally method, which is used to calculate what is owed. Different implementations of the model might apply different calculations, like tax or discounts
Implementations
Okay, since you're probably aware, we can't create an instance of a interface, we need some "default" implementations to work with...
public class DefaultOrder implements Order {
private MainMenuItem item;
private List<Condiment> condiments = new ArrayList<>();
#Override
public MainMenuItem getItem() {
return item;
}
#Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
#Override
public double getTally() {
double tally = 0;
if (item != null) {
tally += item.getPrice();
}
for (Condiment condiment : condiments) {
tally += condiment.getPrice();
}
return tally;
}
#Override
public void setItem(MainMenuItem item) {
this.item = item;
// Oh look, we've established a "rule" that this model
// applies, by itself, sweet
condiments.clear();
}
#Override
public void addCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
// Probably should check for duplicates
condiments.add(condiment);
}
#Override
public void removeCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
condiments.remove(condiment);
}
}
public class DefaultMenu implements Menu {
private List<MainMenuItem> menuItems = new ArrayList<>();
public void add(MainMenuItem menuItem) {
menuItems.add(menuItem);
}
#Override
public List<MainMenuItem> getMenuItems() {
return Collections.unmodifiableList(menuItems);
}
}
public abstract class AbstractMenuItem implements MenuItem {
private String description;
private double price;
public AbstractMenuItem(String description, double price) {
this.description = description;
this.price = price;
}
#Override
public String getDescription() {
return description;
}
#Override
public double getPrice() {
return price;
}
}
public class DefaultCondiment extends AbstractMenuItem implements Condiment {
public DefaultCondiment(String description, double price) {
super(description, price);
}
}
public class DefaultBurger extends AbstractMenuItem implements Burger {
private List<Condiment> condiments;
public DefaultBurger(String description, double price, List<Condiment> condiments) {
super(description, price);
// Protect ourselves from external modifications
this.condiments = new ArrayList<>(condiments);
}
#Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
}
Okay, try not to get too caught up in this, but have a look at the use of abstract here. AbstractMenuItem encapsulates a lot of the "common" functionality that all MenuItem implementations are going to need, so we don't need to repeat ourselves, sweet.
Some of these implementations are already making decisions or applying rules. For example, the DefaultOrder will clear the condiments when ever the MainMenuItem is changed. It could also make sure that the condiment which is been applied is actually available fo this item.
Also note, the tally method is not a stored property, but is re-calculated every time you call it. This is design decision, it wouldn't be hard to make it a stored property instead, so each time you change the MenuMenuItem, add and/or remove condiments, the property was updated, but I'm feeling lazy. But you can see how these things can be changed, and it will effect ALL users of these models, sweet :D
Okay, but how does this actually answer the question? Well, quite a bit actually.
So, the idea is, you start with a blank Order. The user selects a "main item" (ie a burger), you set this to the Order and then you update the UI in response. The UI asks the Order to calculate the tally and presents that to the user.
More over, the same concept works for condiments as well. Each time a condiment is added or removed by the user, the Order is updated and you update the UI.
Okay, but maybe, it's a little easier to understand with an example...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.border.EmptyBorder;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
List<Condiment> condiments = new ArrayList<>(3);
condiments.add(new DefaultCondiment("Ketchup", 0.5));
condiments.add(new DefaultCondiment("Mustard", 0.5));
condiments.add(new DefaultCondiment("Pickles", 0.5));
DefaultMenu menu = new DefaultMenu();
menu.add(new DefaultBurger("Beef", 4.0, condiments));
menu.add(new DefaultBurger("Chicken", 3.5, condiments));
menu.add(new DefaultBurger("Veggie", 4.0, condiments));
MenuPane menuPane = new MenuPane();
menuPane.setMenu(menu);
frame.add(menuPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class MenuPane extends JPanel {
private Menu menu;
private Order order;
private List<Condiment> selectedCondiments = new ArrayList<>();
private JPanel burgerPanel;
private JPanel condimentPanel;
private JPanel totalPanel;
private JLabel totalLabel;
private JButton clearButton;
private JButton payButton;
private NumberFormat currencyFormatter;
public MenuPane() {
setLayout(new GridBagLayout());
order = new DefaultOrder();
burgerPanel = new JPanel();
burgerPanel.setBorder(new EmptyBorder(8, 8, 8, 8));
condimentPanel = new JPanel();
condimentPanel.setBorder(new EmptyBorder(8, 8, 8, 8));
totalPanel = makeTotalPanel();
totalPanel.setBorder(new EmptyBorder(8, 8, 8, 8));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 0.5;
add(burgerPanel, gbc);
gbc.gridy++;
add(condimentPanel, gbc);
gbc.gridy++;
gbc.weighty = 0;
gbc.fill = GridBagConstraints.HORIZONTAL;
add(totalPanel, gbc);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 200);
}
protected NumberFormat getCurrentFormatter() {
if (currencyFormatter != null) {
return currencyFormatter;
}
currencyFormatter = NumberFormat.getCurrencyInstance();
currencyFormatter.setMinimumFractionDigits(2);
return currencyFormatter;
}
protected JPanel makeTotalPanel() {
JPanel totalPanel = new JPanel(new GridBagLayout());
totalLabel = new JLabel();
clearButton = new JButton("CLR");
payButton = new JButton("PAY");
clearButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
order = new DefaultOrder();
buildCondiments();
orderDidChange();
}
});
GridBagConstraints gbc = new GridBagConstraints();
gbc.weightx = 1;
gbc.gridx = 0;
gbc.gridy = 0;
totalPanel.add(totalLabel, gbc);
gbc.weightx = 0;
gbc.gridx++;
totalPanel.add(clearButton, gbc);
gbc.gridx++;
totalPanel.add(payButton, gbc);
return totalPanel;
}
protected void buildBurgerMenu() {
burgerPanel.removeAll();
burgerPanel.setLayout(new GridBagLayout());
if (menu == null) {
return;
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.weightx = 1;
ButtonGroup bg = new ButtonGroup();
// Stick with me, this is a little more advanced, but provides
// a really nice concept and ease of use
// We could also make use of the Action API, but that might
// pushing you just a little to far ;)
ActionListener actionListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JComponent)) {
return;
}
JComponent comp = (JComponent) e.getSource();
Object obj = comp.getClientProperty("MenuItem");
// I'm putting this here to demonstrate part of the concept
// of polymorphism - techncially, we don't have to care
// of it's a burger or some other type of menu item,
// only that this is going to represent the "main" item
if (!(obj instanceof MainMenuItem)) {
return;
}
MainMenuItem item = (MainMenuItem) obj;
order.setItem(item);
buildCondiments();
orderDidChange();
}
};
System.out.println(menu.getMenuItems().size());
for (MenuItem item : menu.getMenuItems()) {
// Only interested in burgers
// Could have the Menu do this, but that's a design
// decision
if (!(item instanceof Burger)) {
continue;
}
Burger burger = (Burger) item;
JRadioButton btn = new JRadioButton(burger.getDescription() + " (" + getCurrentFormatter().format(burger.getPrice()) + ")");
// Ok, this is just a little cheeky, but we're associating the
// butger with the button for simplicity
btn.putClientProperty("MenuItem", burger);
bg.add(btn);
// Add all the buttons share the same listener, because of polymorphism :D
btn.addActionListener(actionListener);
burgerPanel.add(btn, gbc);
}
}
protected void buildCondiments() {
condimentPanel.removeAll();
condimentPanel.setLayout(new GridBagLayout());
if (menu == null || order.getItem() == null) {
return;
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.weightx = 1;
// Stick with me, this is a little more advanced, but provides
// a really nice concept and ease of use
// We could also make use of the Action API, but that might
// pushing you just a little to far ;)
ActionListener actionListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JCheckBox)) {
return;
}
JCheckBox checkBox = (JCheckBox) e.getSource();
Object obj = checkBox.getClientProperty("Condiment");
if (!(obj instanceof Condiment)) {
return;
}
Condiment condiment = (Condiment) obj;
if (checkBox.isSelected()) {
order.addCondiment(condiment);
} else {
order.removeCondiment(condiment);
}
orderDidChange();
}
};
for (Condiment condiment : order.getItem().getCondiments()) {
JCheckBox btn = new JCheckBox(condiment.getDescription() + " (" + getCurrentFormatter().format(condiment.getPrice()) + ")");
// Ok, this is just a little cheeky, but we're associating the
// butger with the button for simplicity
btn.putClientProperty("Condiment", condiment);
// Add all the buttons share the same listener, because of polymorphism :D
btn.addActionListener(actionListener);
condimentPanel.add(btn, gbc);
}
}
public Menu getMenu() {
return menu;
}
public void setMenu(Menu menu) {
this.menu = menu;
order = new DefaultOrder();
buildBurgerMenu();
orderDidChange();
}
protected void orderDidChange() {
if (order == null) {
totalLabel.setText("Amount: " + getCurrentFormatter().format(0));
return;
}
// And now, some magic, how easy is it to get the expected
// tally amount!!
totalLabel.setText("Amount: " + getCurrentFormatter().format(order.getTally()));
}
}
public interface Menu {
public List<MainMenuItem> getMenuItems();
}
public interface MenuItem {
public String getDescription();
public double getPrice();
}
public interface Condiment extends MenuItem {
}
public interface MainMenuItem extends MenuItem {
public List<Condiment> getCondiments();
}
public interface Burger extends MainMenuItem {
}
public interface Order {
public MainMenuItem getItem();
public void setItem(MainMenuItem item);
public List<Condiment> getCondiments();
public void addCondiment(Condiment condiment);
public void removeCondiment(Condiment condiment);
public double getTally();
}
public class DefaultOrder implements Order {
private MainMenuItem item;
private List<Condiment> condiments = new ArrayList<>();
#Override
public MainMenuItem getItem() {
return item;
}
#Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
#Override
public double getTally() {
double tally = 0;
if (item != null) {
tally += item.getPrice();
}
for (Condiment condiment : condiments) {
tally += condiment.getPrice();
}
return tally;
}
#Override
public void setItem(MainMenuItem item) {
this.item = item;
// Oh look, we've established a "rule" that this model
// applies, by itself, sweet
condiments.clear();
}
#Override
public void addCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
// Probably should check for duplicates
condiments.add(condiment);
}
#Override
public void removeCondiment(Condiment condiment) {
// Bit pointless if the menu item is not set
if (item == null) {
return;
}
condiments.remove(condiment);
}
}
public class DefaultMenu implements Menu {
private List<MainMenuItem> menuItems = new ArrayList<>();
public void add(MainMenuItem menuItem) {
menuItems.add(menuItem);
}
#Override
public List<MainMenuItem> getMenuItems() {
return Collections.unmodifiableList(menuItems);
}
}
public abstract class AbstractMenuItem implements MenuItem {
private String description;
private double price;
public AbstractMenuItem(String description, double price) {
this.description = description;
this.price = price;
}
#Override
public String getDescription() {
return description;
}
#Override
public double getPrice() {
return price;
}
}
public class DefaultCondiment extends AbstractMenuItem implements Condiment {
public DefaultCondiment(String description, double price) {
super(description, price);
}
}
public class DefaultBurger extends AbstractMenuItem implements Burger {
private List<Condiment> condiments;
public DefaultBurger(String description, double price, List<Condiment> condiments) {
super(description, price);
// Protect ourselves from external modifications
this.condiments = new ArrayList<>(condiments);
}
#Override
public List<Condiment> getCondiments() {
return Collections.unmodifiableList(condiments);
}
}
}
Okay, that's a lot to take in. Lets take a closer look at the buildBurgerMenu method. This gets called when ever the main menu is changed.
Pay close attention to the actionListener used in this method, there's only one and it's shared by all the buttons
protected void buildBurgerMenu() {
burgerPanel.removeAll();
burgerPanel.setLayout(new GridBagLayout());
if (menu == null) {
return;
}
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.weightx = 1;
ButtonGroup bg = new ButtonGroup();
// Stick with me, this is a little more advanced, but provides
// a really nice concept and ease of use
// We could also make use of the Action API, but that might
// pushing you just a little to far ;)
ActionListener actionListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (!(e.getSource() instanceof JComponent)) {
return;
}
JComponent comp = (JComponent) e.getSource();
Object obj = comp.getClientProperty("MenuItem");
// I'm putting this here to demonstrate part of the concept
// of polymorphism - techncially, we don't have to care
// of it's a burger or some other type of menu item,
// only that this is going to represent the "main" item
if (!(obj instanceof MainMenuItem)) {
return;
}
MainMenuItem item = (MainMenuItem) obj;
order.setItem(item);
buildCondiments();
orderDidChange();
}
};
System.out.println(menu.getMenuItems().size());
for (MenuItem item : menu.getMenuItems()) {
// Only interested in burgers
// Could have the Menu do this, but that's a design
// decision
if (!(item instanceof Burger)) {
continue;
}
Burger burger = (Burger) item;
JRadioButton btn = new JRadioButton(burger.getDescription() + " (" + getCurrentFormatter().format(burger.getPrice()) + ")");
// Ok, this is just a little cheeky, but we're associating the
// butger with the button for simplicity
btn.putClientProperty("MenuItem", burger);
bg.add(btn);
// Add all the buttons share the same listener, because of polymorphism :D
btn.addActionListener(actionListener);
burgerPanel.add(btn, gbc);
}
}
When ever the actionListener is triggered (via a user interaction for example), it makes a bunch of decisions, which, if all goes well, ends in the Order been updated, the buildCondiments and orderDidChange methods been called, which updates the available condiments and updates the UI's tally.
This is all done in a "abstract" way. The actionListener doesn't care about what type of MainMenuItem the user selected, that doesn't change its workflow, it only needs to apply the item to the Order. In the same vain, the Order doesn't care, as it just needs the price information in order to calculate the tally.
So you can add new menu items and/or change the prices and everything just keeps on working (🤞).
Let's look at the orderDidChange method...
protected void orderDidChange() {
if (order == null) {
totalLabel.setText("Amount: " + getCurrentFormatter().format(0));
return;
}
// And now, some magic, how easy is it to get the expected
// tally amount!!
totalLabel.setText("Amount: " + getCurrentFormatter().format(order.getTally()));
}
Not super complicated is it! All the work is been done by the, MODEL!
What I left out
For brevity, I left out one other concept, often used with models, the concept of the "observer pattern".
The observer pattern allows an interested party to be notified when some other object changes. It's pretty common concept and you've already used, ActionListener is an example of an observer pattern. It allows you to "observer" "actions events" when they are triggered by a given object.
Sure, but how is that helpful?
Well, imagine now if, instead of having to manually call orderDidChange every time you wanted to update the UI (or even forgetting to and spending a few hours debugging why), the MenuPane could, instead, registered itself as an observer directly to the Order and be notified when the order changed!! Super sweet!
This further helps you de-couple your code and makes it super easy to update the UI in a verity of ways independently of the model or other code requirements.
Models 💪
I am making a matching card program and I want to make sure the user only selects two card. So I have made Changelisteners and inside those changelisteners I would like to have an integer that would increase when there is a change in the state of the button. I have tried to use int, but it gave me the error where it says to use a final or effectively final. Is there some way that I can use an int inside of the changelistener method.
Here is an example:
card1Button.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
//int increases here
}
});
You have two basic choices to solve the immediate issue
You could...
Make the counter an instance field
public class MyAwesomeCardGame extends ... {
private int counter;
//...
public MyAwesomeCardGame() {
//...
card1Button.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
counter++;
}
});
}
}
You could...
Make the counter a instance of field of the anonymous class
public class MyAwesomeCardGame extends ... {
//...
public MyAwesomeCardGame() {
//...
card1Button.addChangeListener(new ChangeListener() {
private int counter;
public void stateChanged(ChangeEvent e) {
counter++;
}
});
}
}
Alternatively
Depending on what you're doing, you could use two ButtonGroups instead, it would ensure that only one button from each group can be selected at a time
You could change the scope of your variable
public class CardGame {
private int x;
public CardGame() {
card1Button.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
x++;
}
});
}
}
Here is an example that shows how you can do this when using a JCheckBox:
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
public class CheckBoxGroup
{
private Set<GroupButtonModel> models = new HashSet<GroupButtonModel>();
private int groupSize;
public CheckBoxGroup(int groupSize)
{
this.groupSize = groupSize;
}
public void register(JCheckBox checkBox)
{
ButtonModel groupModel = new GroupButtonModel();
groupModel.setSelected ( checkBox.getModel().isSelected() );
checkBox.setModel( groupModel );
}
private class GroupButtonModel extends JToggleButton.ToggleButtonModel
{
#Override
public void setSelected(boolean selected)
{
if (!selected)
{
models.remove( this );
super.setSelected( selected );
return;
}
// Check number of currently selected check boxes
if (models.size() == groupSize)
{
System.out.println("Only " + groupSize + " items can be selected");
}
else
{
models.add( this );
super.setSelected( selected );
}
}
}
private static void createAndShowGUI()
{
JPanel panel = new JPanel();
CheckBoxGroup group = new CheckBoxGroup(3);
for (int i = 0; i < 10; i++)
{
JCheckBox checkBox = new JCheckBox( String.valueOf(i) );
panel.add( checkBox );
group.register( checkBox );
}
JFrame frame = new JFrame("Check Box Group");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add( panel );
frame.setLocationByPlatform( true );
frame.pack();
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
It adds a custom model to the component to check the number of currently selected components before allowing you to select another component.
It will also work for a JToggleButton. Just change the register(...) method to register toggle buttons.
I posted a question earlier today and was directed by MadProgrammer to use ListCellRenderer to achieve the desired results. I have it almost working, but it is showing the same entry twice in the combobox and I don't know why. Please help me solve this riddle. The code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
public class NotWorking extends JPanel {
private JPanel editPanel;
private JComboBox<String> editComboLevel;
private JComboBox editCombo;
private String[] levels = {"Level 1", "Level 2", "Level 3"};
private static ArrayList<NotWorking> course = new ArrayList<NotWorking>();
public static String courseNum, courseTitle, courseLevel;
public JPanel createContentPane (){
Integer[] intArray = new Integer[course.size()];
for (int i = 0; i < course.size(); i++) {
intArray[i] = new Integer(i);
}
editPanel = new JPanel(new GridLayout(4,2));
editPanel.setPreferredSize(new Dimension(100,75));
editPanel.add(editCombo = new JComboBox(intArray));
ComboBoxRenderer renderer= new ComboBoxRenderer();
editCombo.setRenderer(renderer);
return editPanel;
}
NotWorking() {}
NotWorking(String courseNum, String courseTitle, String courseLevel) {
this.courseNum = courseNum;
this.courseTitle = courseTitle;
this.courseLevel = courseLevel;
}
#Override
public String toString() {
String courseInfo = getCourseNum() + ", " + getCourseTitle() + ", " + getCourseLevel();
return courseInfo;
}
public String getCourseNum() {
return this.courseNum;
}
public String getCourseTitle() {
return this.courseTitle;
}
public String getCourseLevel() {
return this.courseLevel;
}
public void setCourseNum(String courseNum) {
this.courseNum = courseNum;
}
public void setCourseTitle(String courseTitle) {
this.courseTitle = courseTitle;
}
public void setCourseLevel(String courseLevel) {
this.courseLevel = courseLevel;
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("Example of Code Snippet");
NotWorking myCourse = new NotWorking();
frame.setContentPane(myCourse.createContentPane());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
course.add(new NotWorking("Course1", "Course1 desc", "Level 1"));
course.add(new NotWorking("Course2", "Course2 desc", "Level 2"));
createAndShowGUI();
for(NotWorking item : course)
System.out.println(item);
}
});
}
class ComboBoxRenderer extends JLabel
implements ListCellRenderer {
public ComboBoxRenderer() {
setOpaque(true);
setHorizontalAlignment(CENTER);
setVerticalAlignment(CENTER);
}
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
int selectedIndex = ((Integer)value).intValue();
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setText(getCourseNum());
return this;
}
}
}
As you can see there are 2 additions to the ArrayList. I am limiting the display in the combobox to only show the course number, but Course2 is showing up twice and when I print out the contents of the ArrayList I see all the details for Course2 shown twice and none for Course1. Any help would be greatly appreciated. Cheers
Using a custom renderer is only half of the solution. A custom renderer will break the default items selection by using the keyboard of a combo box. See Combo Box With Custom Renderer for more information and a more complete solution.
The main problem with your code is the NotWorking class. This class should NOT extend JPanel. It is just a class that is used to hold the 3 properties of the class. The course number, title and level should NOT be static variables. There should be no reference to Swing components.
Your design should be one class for the NotWorking class and another class to create the GUI.
Start with the section from the Swing tutorial on How to Use Combo Boxes for a better design. Then customize the ComboBoxDemo.java example code from the tutorial to add your NotWorking class to the combo box instead of adding String data.
I have a ValueAwareEditor that contains a couple of sub editors:
Essentially, an OfferDto is composed of a TariffDto and a Commission. The Commission can be one of 4 sub-types, but there is only ever one. Usually this list of possible commissions inside the TariffDto will only contain one element, but it can sometimes contain two.
public class OfferDto
{
private TariffDto tariff;
// selected from the list in the tariff
private Commission commission;
}
public class TariffDto extends EntityDto
{
// omitted for brevity...
protected List<Commission> commissions = new ArrayList<Commission>();
}
When commissions contains more than one item, I want to display a dropdown with the two optiions, and add allow the user to choose between them, each time resetting the commission in the OfferDto and the CommissionEditor.
The problem is that, when call commission.setValue() for the second time, the editor does not change. What should I be doing here?
public class OfferEditor extends Composite implements ValueAwareEditor<OfferDto>
{
#UiField
TariffRenderer tariff;
#Ignore
#UiField
HTMLPanel panel;
#UiField
CommissionEditor commission;
#Override
public void setValue(final OfferDto value)
{
panel.clear();
List<Commission> commissions = value.getTariff().getCommissions();
if(commissions.size() == 1)
{
value.setCommission(commissions.get(0));
}
else
{
// multiple commissions
ValueListBox<Commission> dropdown = new ValueListBox<Commission>(new Renderer<Commission>()
{
#Override
public String render(Commission object)
{
return object == null ? "" : object.getName();
}
#Override
public void render(Commission object, Appendable appendable) throws IOException
{
appendable.append(render(object));
}
});
dropdown.setValue(value.getCommission());
dropdown.setAcceptableValues(commissions);
dropdown.addValueChangeHandler(new ValueChangeHandler<Commission>()
{
#Override
public void onValueChange(ValueChangeEvent<Commission> event)
{
Commission selected = event.getValue();
// this works, but the CommissionEditor that was first rendered remains
value.setCommission(selected);
}
});
panel.add(dropdown);
}
}
}
Currently, I am rendering the list of commissions in a ValueListBox, then when the value changes I am pushing that value to the OfferDto. The Commission seems to get set right, but the subEditor does not change.
Any help greatly appreciated.
EDIT:
CommissionEditor shows the relevant sub-editor depending on the type.
public class CommissionEditor extends Composite implements Editor<Commission>
{
private static CommissionEditorUiBinder uiBinder = GWT.create(CommissionEditorUiBinder.class);
interface CommissionEditorUiBinder extends UiBinder<Widget, CommissionEditor>
{
}
#UiField
Panel subEditorPanel;
public CommissionEditor()
{
initWidget(uiBinder.createAndBindUi(this));
}
#Ignore
final UnitRateCommissionEditor unitRateCommissionEditor = new UnitRateCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, UnitRateCommission, UnitRateCommissionEditor> unitRateCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, UnitRateCommission, UnitRateCommissionEditor>(
unitRateCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof UnitRateCommission)
{
setValue(value, value instanceof UnitRateCommission);
System.out.println("UnitRateCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(unitRateCommissionEditor);
}
}
};
#Ignore
final StandingChargeCommissionEditor standingChargeCommissionEditor = new StandingChargeCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, StandingChargeCommission, StandingChargeCommissionEditor> standingChargeCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, StandingChargeCommission, StandingChargeCommissionEditor>(
standingChargeCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof StandingChargeCommission)
{
setValue(value, value instanceof StandingChargeCommission);
System.out.println("StandingChargeCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(standingChargeCommissionEditor);
}
}
};
#Ignore
final PerMwhCommissionEditor perMwhCommissionEditor = new PerMwhCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, PerMwhCommission, PerMwhCommissionEditor> perMwhCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, PerMwhCommission, PerMwhCommissionEditor>(
perMwhCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof PerMwhCommission)
{
setValue(value, value instanceof PerMwhCommission);
System.out.println("PerMwhCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(perMwhCommissionEditor);
}
}
};
}
Possible Solution:
I changed OfferEditor as so:
public class OfferEditor extends Composite implements Editor<OfferDto>
{
#UiField
TariffRenderer tariff;
#Path("tariff.commissions")
#UiField
CommissionsEditor commission;
}
New editor CommissionsEditor is a CompositeEditor. It needs to take List tariff.commissions and set the chosen Commission into offer.commission:
public class CommissionsEditor extends Composite implements CompositeEditor<List<Commission>, Commission, CommissionEditor>
{
private static CommissionsEditorUiBinder uiBinder = GWT.create(CommissionsEditorUiBinder.class);
interface CommissionsEditorUiBinder extends UiBinder<Widget, CommissionsEditor>
{
}
private EditorChain<Commission, CommissionEditor> chain;
#UiField
FlowPanel dropdownPanel, subEditorPanel;
#Ignore
CommissionEditor subEditor;
public CommissionsEditor()
{
initWidget(uiBinder.createAndBindUi(this));
}
#Override
public void setValue(List<Commission> valueList)
{
// clear both panels
dropdownPanel.clear();
subEditorPanel.clear();
if(valueList.size() == 1)
{
// set the commission to the first in the list
Commission selected = valueList.get(0);
subEditor = new CommissionEditor();
subEditorPanel.add(subEditor);
chain.attach(selected, subEditor);
}
else if(valueList.size() > 1)
{
ValueListBox<Commission> dropdown = new ValueListBox<Commission>(new Renderer<Commission>()
{
#Override
public String render(Commission object)
{
return object == null ? "" : object.getName();
}
#Override
public void render(Commission object, Appendable appendable) throws IOException
{
appendable.append(render(object));
}
});
dropdownPanel.add(dropdown);
dropdown.setValue(valueList.get(0));
dropdown.setAcceptableValues(valueList);
dropdown.addValueChangeHandler(new ValueChangeHandler<Commission>()
{
#Override
public void onValueChange(ValueChangeEvent<Commission> event)
{
Commission selected = event.getValue();
subEditorPanel.clear();
CommissionEditor subEditor = new CommissionEditor();
subEditorPanel.add(subEditor);
chain.attach(selected, subEditor);
}
});
}
}
#Override
public void flush()
{
}
#Override
public void onPropertyChange(String... paths)
{
// TODO Auto-generated method stub
}
#Override
public void setDelegate(EditorDelegate<List<Commission>> delegate)
{
// TODO Auto-generated method stub
}
#Override
public CommissionEditor createEditorForTraversal()
{
return new CommissionEditor();
}
#Override
public String getPathElement(CommissionEditor subEditor)
{
return null;
}
#Override
public void setEditorChain(EditorChain<Commission, CommissionEditor> chain)
{
this.chain = chain;
}
}
When the CommissionsEditor renders the dropdown and onValueChange() is called, the new editor gets created, but the value for the commission never seems to get set.
For some reason the selected subEditor's value is not pushed into offer.setCommission(). I thought chain.attach() would perform this for me?
I am trying to change JList rows dynamically. I need change nth row colour, highlight it(n is unknown during compilation). I saw a lot of examples with custom ListCellRenderer, but all were "static".
In other words I have JList with x rows. During runtime my "business logic" detects nth row is important. So I want make its background green, wait one second, and then make it white again. One more thing, don't wan change row selection.
What is the best way to do so?
Simple, set a custom ListCellRenderer to your JList using:
list.setCellRenderer(myListCellrenderer);
Now inside the overridden method getListCellRendererComponent() do something like this:
public Component getListCellRendererComponent(.....) {
Component c = super.getListCellRendererComponent();
c.setBackGround(Color.blue)
return c;
}
The above example assumed that your custom renderer overrid DefaultListCellRenderer
Based on ListDemo sample from SUN.
If you enter some text in the textfield which isn't in the list and you hit highlight it gets added.
If the text is in the list and you hit highlight the entry in the list gets temporarily highlighted blue.
Note the solution here with the match field is just for demo. For more correct implementation consider the other ideas proposed and consider using javax.swing.Timer
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class ListDemo extends JPanel {
private JList list;
private DefaultListModel listModel;
public String match = null;
private static final String hireString = "Highlight";
private JTextField employeeName;
public ListDemo() {
super(new BorderLayout());
listModel = new DefaultListModel();
listModel.addElement("Test1");
listModel.addElement("Test2");
listModel.addElement("Test3");
list = new JList(listModel);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setSelectedIndex(0);
list.setVisibleRowCount(5);
list.setCellRenderer(new MyListCellRenderer());
JScrollPane listScrollPane = new JScrollPane(list);
JButton hireButton = new JButton(hireString);
HireListener hireListener = new HireListener(hireButton);
hireButton.setActionCommand(hireString);
hireButton.addActionListener(hireListener);
hireButton.setEnabled(false);
employeeName = new JTextField(10);
employeeName.addActionListener(hireListener);
employeeName.getDocument().addDocumentListener(hireListener);
listModel.getElementAt(list.getSelectedIndex()).toString();
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane,
BoxLayout.LINE_AXIS));
buttonPane.add(Box.createHorizontalStrut(5));
buttonPane.add(employeeName);
buttonPane.add(hireButton);
buttonPane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
add(listScrollPane, BorderLayout.CENTER);
add(buttonPane, BorderLayout.PAGE_END);
}
class MyListCellRenderer extends JLabel implements ListCellRenderer {
public MyListCellRenderer() {
setOpaque(true);
}
public Component getListCellRendererComponent(JList paramlist, Object value, int index, boolean isSelected, boolean cellHasFocus) {
setText(value.toString());
if (value.toString().equals(match)) {
setBackground(Color.BLUE);
SwingWorker worker = new SwingWorker() {
#Override
public Object doInBackground() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) { /*Who cares*/ }
return null;
}
#Override
public void done() {
match = null;
list.repaint();
}
};
worker.execute();
} else
setBackground(Color.RED);
return this;
}
}
class HireListener implements ActionListener, DocumentListener {
private boolean alreadyEnabled = false;
private JButton button;
public HireListener(JButton button) {
this.button = button;
}
public void actionPerformed(ActionEvent e) {
String name = employeeName.getText();
if (listModel.contains(name)) {
match = name;
list.repaint();
employeeName.requestFocusInWindow();
employeeName.selectAll();
return;
}
if (name.equals("")) {
Toolkit.getDefaultToolkit().beep();
employeeName.requestFocusInWindow();
employeeName.selectAll();
return;
}
int index = list.getSelectedIndex();
if (index == -1)
index = 0;
else
index++;
listModel.insertElementAt(employeeName.getText(), index);
employeeName.requestFocusInWindow();
employeeName.setText("");
list.setSelectedIndex(index);
list.ensureIndexIsVisible(index);
}
public void insertUpdate(DocumentEvent e) {
enableButton();
}
public void removeUpdate(DocumentEvent e) {
handleEmptyTextField(e);
}
public void changedUpdate(DocumentEvent e) {
if (!handleEmptyTextField(e))
enableButton();
}
private void enableButton() {
if (!alreadyEnabled)
button.setEnabled(true);
}
private boolean handleEmptyTextField(DocumentEvent e) {
if (e.getDocument().getLength() <= 0) {
button.setEnabled(false);
alreadyEnabled = false;
return true;
}
return false;
}
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("ListDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JComponent newContentPane = new ListDemo();
newContentPane.setOpaque(true);
frame.setContentPane(newContentPane);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() { createAndShowGUI(); }
});
}
}
Your custom ListCellRenderer, which implements the method getListCellRendererComponent, will have access to both the JList and the value that it is redering. This gives you a couple options for how to determine when to paint the nth row green:
You could subclass JList and have the renderer ask it which color to use for the bg. The JList subclass could trigger a repaint when the business logic determines that it is time for the nth row to be green, and then start an Swing Timer to trigger a repaint returning the bg back to normal
When the business logic determines when you should show the row as green, you also have the option of setting state on the backing object of the row, and test it for that state within getListCellRendererComponent, setting the bg green if the state is correct. Again, you have the option of setting an Swing Timer to revert the state on the backing object.