根据用户选择更改 JLabel

Change JLabel based on what user choose

我的任务是做一个简单的订单系统。我想要 JLabel(金额:0.00 美元)显示用户为他的汉堡和调味品选择的相应金额。例如,如果用户点击牛肉,标签将变为“金额:4.00 美元”,当他选择一种调味品时,它会根据他选择的调味品数量在总额中增加 0.50 美元,反之亦然。此外,当用户取消选中调味品 (JCheckBox) 时,它会从总金额中扣除 $0.50 美元。

我的牛肉码JRadioButton:

private void beef_radioBtnActionPerformed(java.awt.event.ActionEvent evt) {
    total_amount.setText("Amount: .00");
    ketchup_Checkbox.setEnabled(true);
    mustard_Checkbox.setEnabled(true);
    pickles_Checkbox.setEnabled(true);
}  

番茄酱代码 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));
        }
    }
 }

好的,系好安全带,这将是一段旅程。

您可以使用的最强大的概念之一是“模型”的概念。模型只是对某些东西进行“建模”的东西,并且是一种分隔程序不同区域的方法。因此,模型对数据建模(将其视为容器),然后视图将使用这些模型将数据格式化给用户(关注点分离)。模型还可能包含业务逻辑或根据其要求执行计算。

这使您可以集中概念,这样您就不会重复自己或忘记做事。这也是一种改变程序部分工作方式的方法,也称为“委托”

起点,一些接口

嗯,说了很多“废话”,让我们开始吧。我更喜欢用interface来描述事物,它提供了很大的自由度,因为你可以将不同的interface放在一起以适应不同的需求。

菜单

好的,简单的概念,这将是一个可供出售的物品列表

public interface Menu {
    public List<MainMenuItem> getMenuItems();
}

菜单项

菜单项的描述,非常基础

public interface MenuItem {
    public String getDescription();
    public double getPrice();
}

“主要”菜单项

这些都是“顶级”、“独立”菜单项,在我们的例子中,可以有调味品:D

public interface MainMenuItem extends MenuItem {
    public List<Condiment> getCondiments();
}

调味品

调味品是“特殊的”MenuItem,因为它们与 MainMenuItem

public interface Condiment extends MenuItem {
}

汉堡

这只是您可以做的一些事情的演示,Burger 没有什么特别的,但是正如您将看到的,我们可以使用这个概念来做不同的事情

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();
}

这很好地展示了模型的力量。 Order有一个getTally方法,用来计算欠的。该模型的不同实现可能会应用不同的计算,例如税收或折扣

实施

好的,既然你可能已经知道,我们无法创建 interface 的实例,我们需要一些“默认”实现才能使用...

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);
    }

}

好吧,不要太纠结于此,但请看一下 abstract 的用法。 AbstractMenuItem 封装了所有 MenuItem 实现都需要的许多“通用”功能,所以我们不需要重复自己,亲爱的。

其中一些实现已经在制定决策或应用规则。例如,DefaultOrder 将在 MainMenuItem 更改时清除 condiments。它还可以确保所应用的调味品实际上可用于该项目。

另请注意,tally 方法不是存储的 属性,而是在您每次调用它时重新计算。这是设计决定,将其改为存储 属性 并不难,因此每次更改 MenuMenuItem、添加 and/or 删除调味品时, 属性已更新,但我感到懒惰。但是你可以看到这些东西是如何改变的,它会影响这些模型的所有用户,亲爱的:D

好的,但这实际上是如何回答问题的?好吧,实际上相当多。

所以,我们的想法是,您从空白开始 Order。用户选择一个“主要项目”(即汉堡),将其设置为 Order 然后更新 UI 作为响应。 UI 要求 Order 计算 tally 并将其呈现给用户。

此外,同样的概念也适用于调味品。每次用户添加或删除调味品时,Order 都会更新,而您会更新 UI.

好的,但也许,通过示例更容易理解...

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);
        }

    }
}

好吧,要理解的东西太多了。让我们仔细看看 buildBurgerMenu 方法。每当主菜单更改时都会调用此方法。

注意这个方法中用到的actionListener,只有一个,所有按钮共用

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);
    }
}

每当 actionListener 被触发时(例如通过用户交互),它都会做出一系列决定,如果一切顺利,最终会更新 Order,已调用 buildCondimentsorderDidChange 方法,更新可用调味品并更新 UI 的计数。

这一切都是以“抽象”的方式完成的。 actionListener 不关心用户选择的 MainMenuItem 是什么类型,这不会改变它的工作流程,它只需要将项目应用到 Order。同样,Order 并不关心,因为它只需要 price 信息来计算计数。

因此您可以添加新的菜单项 and/or 更改价格并且一切正常 ()。

让我们看看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()));
}

不是超级复杂吧!所有的工作都是由 MODEL!

完成的

我遗漏了什么

为简洁起见,我省略了另一个经常与模型一起使用的概念,即“观察者模式”的概念。

观察者模式允许感兴趣的一方在其他对象发生变化时得到通知。这是一个很常见的概念,你已经使用过,ActionListener 是观察者模式的一个例子。它允许您在给定对象触发时“观察”“动作事件”。

当然可以,但这有什么用?

好吧,现在想象一下,如果不是每次您想更新 UI 时都必须手动调用 orderDidChange(或者甚至忘记并花几个小时调试原因),MenuPane 可以直接将自己注册为 Order 的观察者,并在顺序更改时收到通知!!超级甜!

这进一步帮助您解耦代码,并使 UI 以多种独立于模型或其他代码要求的真实方式变得超级容易。

型号