JButton 未链接到 ActionLister

JButton not linked to ActionLister

public class OpenFrame extends JFrame implements ActionListener {

private static final int WIDTH = 500;
private static final int HEIGHT = 500;
private BudgetPlanner budgetPlanner;

private JFrame openFrame;

private JLabel welcomeMessage;
private JPanel openPanel = new JPanel();
private JPanel inputSpace;

private JButton loadButton = new JButton();
private JButton resetButton = new JButton();
private JButton addIncomeButton;
private JButton addExpenseButton;
private JButton viewExpenseButton;
private JButton viewIncomeButton;

private JTextField inputIncome;

public BudgetPlanner callBudgetPlanner() {
    try {
        budgetPlanner = new BudgetPlanner();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    return budgetPlanner;
}

public OpenFrame() {
    openFrame = new JFrame();
    openFrame.setTitle("BudgetPlanner");
    openFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    openFrame.setResizable(false);
    openFrame.setLayout(null);
    openFrame.setLocationRelativeTo(null);
    openFrame.setSize(new Dimension(WIDTH, HEIGHT));
    openFrame.getContentPane().setBackground(Color.GRAY);
    openPanel();
    openFrame.setContentPane(openPanel);

    openFrame.setVisible(true);
}

public void openPanel() {
    openPanel.setLayout(null);

    welcomeMessage = new JLabel("Hello there! Welcome back. Choose an option below:");
    welcomeMessage.setVerticalTextPosition(TOP);
    welcomeMessage.setHorizontalTextPosition(SwingConstants.CENTER);
    welcomeMessage.setBounds(50, 20, 500, 200);
    welcomeMessage.setFont(new Font("Arial", Font.PLAIN, 17));

    openPanel.setBounds(0, 0, 500, 500);
    openPanel.add(welcomeMessage);

    loadButton.setBounds(200, 170, 100, 50);
    loadButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));
    loadButton.setText("Load Data");
    loadButton.addActionListener(this);
    loadButton.setFocusable(false);

    resetButton.setBounds(200, 230, 100, 50);
    resetButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));
    resetButton.setText("Reset Data");
    resetButton.addActionListener(this);
    resetButton.setFocusable(false);

    openPanel.add(loadButton);
    openPanel.add(resetButton);
}

public void plannerScreen() {
    JPanel loadPanel = new JPanel();
    openFrame.setContentPane(loadPanel);
    loadPanel.setBounds(0, 0, 500, 500);
    JLabel loadCompleteMessage = new JLabel("Welcome to Budget Planner App.");
    loadCompleteMessage.setBounds(138, TOP, 500, 50);
    loadCompleteMessage.setFont(new Font("Arial", Font.BOLD, 14));
    loadPanel.add(loadCompleteMessage);
    JLabel optionMessage = new JLabel("What would you like to do?");
    optionMessage.setBounds(175, TOP + 40, 500, 10);
    optionMessage.setFont(new Font("Arial", Font.ITALIC, 13));
    loadPanel.add(optionMessage);
    openButton();
    loadPanel.add(addIncomeButton);
    loadPanel.add(addExpenseButton);
    loadPanel.add(viewIncomeButton);
    loadPanel.add(viewExpenseButton);
}

public void openButton() {
    addIncomeButton = new JButton("Add Income");
    addIncomeButton.setBounds(180, 110, 140, 45);
    addIncomeButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));
    addExpenseButton = new JButton("Add Expense");
    addExpenseButton.setBounds(180, 160, 140, 45);
    addExpenseButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));
    viewIncomeButton = new JButton("View Income");
    viewIncomeButton.setBounds(180, 210, 140, 45);
    viewIncomeButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));
    viewExpenseButton = new JButton("View Expense");
    viewExpenseButton.setBounds(180, 260, 140, 45);
    viewExpenseButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));

    addIncomeButton.addActionListener(new InputData());

}

@Override
public void actionPerformed(ActionEvent e) {
    if (e.getSource() == loadButton) {
        JOptionPane.showMessageDialog(null, "Loading Complete!");
        plannerScreen();
    }

    if (e.getSource() == resetButton) {
        JOptionPane.showMessageDialog(null, "Saved data erased.");
        plannerScreen();
    }
}

public class InputData implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
        inputSpace = new JPanel();
        inputSpace.setBounds(0, 300, 500, 200);
        inputIncome = new JTextField("How much is your income?", 20);
        openPanel.add(inputSpace);
        inputSpace.add(inputIncome);
    }
}

我的代码如上所示。问题是当我点击 addIncomeButton 时,它没有链接到我创建的 InputData 动作列表。单击 addIncomeButton 后,它不会执行任何操作。我该如何解决这个问题? ;-;

我的目标是单击按钮后,将出现一个 JTextField 并将添加到同一面板上。

任何帮助将不胜感激!非常感谢!

解决方案并不完美,您可以根据需要改进代码。按钮似乎正在响应点击以验证我在 actionPerformed 方法中添加了 System.out.println("Clicked") 语句。

package swingButton;


import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileNotFoundException;

import static javax.swing.SwingConstants.TOP;

public class OpenFrame extends JFrame implements ActionListener {

    private static final int WIDTH = 500;
    private static final int HEIGHT = 500;
//    private BudgetPlanner budgetPlanner;

    private JFrame openFrame;

    private JLabel welcomeMessage;
    private JPanel openPanel = new JPanel();
    private JPanel inputSpace;

    private JButton loadButton = new JButton();
    private JButton resetButton = new JButton();
    private JButton addIncomeButton;
    private JButton addExpenseButton;
    private JButton viewExpenseButton;
    private JButton viewIncomeButton;

    private JTextField inputIncome;

//    public BudgetPlanner callBudgetPlanner() {
//        try {
//            budgetPlanner = new BudgetPlanner();
//        } catch (FileNotFoundException e) {
//            e.printStackTrace();
//        }
//        return budgetPlanner;
//    }

    public OpenFrame() {
        openFrame = new JFrame();
        openFrame.setTitle("BudgetPlanner");
        openFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        openFrame.setResizable(false);
        openFrame.setLayout(null);
        openFrame.setLocationRelativeTo(null);
        openFrame.setSize(new Dimension(WIDTH, HEIGHT));
        openFrame.getContentPane().setBackground(Color.GRAY);
        openPanel();
        openFrame.setContentPane(openPanel);

        openFrame.setVisible(true);
    }

    public void openPanel() {
        openPanel.setLayout(null);

        welcomeMessage = new JLabel("Hello there! Welcome back. Choose an option below:");
        welcomeMessage.setVerticalTextPosition(TOP);
        welcomeMessage.setHorizontalTextPosition(SwingConstants.CENTER);
        welcomeMessage.setBounds(50, 20, 500, 200);
        welcomeMessage.setFont(new Font("Arial", Font.PLAIN, 17));

        openPanel.setBounds(0, 0, 500, 500);
        openPanel.add(welcomeMessage);

        loadButton.setBounds(200, 170, 100, 50);
        loadButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));
        loadButton.setText("Load Data");
        loadButton.addActionListener(this);
        loadButton.setFocusable(false);

        resetButton.setBounds(200, 230, 100, 50);
        resetButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));
        resetButton.setText("Reset Data");
        resetButton.addActionListener(this);
        resetButton.setFocusable(false);

        openPanel.add(loadButton);
        openPanel.add(resetButton);
    }

    public void plannerScreen() {
        JPanel loadPanel = new JPanel();
        openFrame.setContentPane(loadPanel);
        loadPanel.setBounds(0, 0, 500, 500);
        JLabel loadCompleteMessage = new JLabel("Welcome to Budget Planner App.");
        loadCompleteMessage.setBounds(138, TOP, 500, 50);
        loadCompleteMessage.setFont(new Font("Arial", Font.BOLD, 14));
        loadPanel.add(loadCompleteMessage);
        JLabel optionMessage = new JLabel("What would you like to do?");
        optionMessage.setBounds(175, TOP + 40, 500, 10);
        optionMessage.setFont(new Font("Arial", Font.ITALIC, 13));
        loadPanel.add(optionMessage);
        openButton();
        loadPanel.add(addIncomeButton);
        loadPanel.add(addExpenseButton);
        loadPanel.add(viewIncomeButton);
        loadPanel.add(viewExpenseButton);
    }

    public void openButton() {
        addIncomeButton = new JButton("Add Income");
        addIncomeButton.setBounds(180, 110, 140, 45);
        addIncomeButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));
        addExpenseButton = new JButton("Add Expense");
        addExpenseButton.setBounds(180, 160, 140, 45);
        addExpenseButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));
        viewIncomeButton = new JButton("View Income");
        viewIncomeButton.setBounds(180, 210, 140, 45);
        viewIncomeButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));
        viewExpenseButton = new JButton("View Expense");
        viewExpenseButton.setBounds(180, 260, 140, 45);
        viewExpenseButton.setFont(new Font("Arial", Font.CENTER_BASELINE, 15));

        addIncomeButton.addActionListener(new InputData(this));

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == loadButton) {
            JOptionPane.showMessageDialog(null, "Loading Complete!");
            plannerScreen();
        }

        if (e.getSource() == resetButton) {
            JOptionPane.showMessageDialog(null, "Saved data erased.");
            plannerScreen();
        }
    }

    public class InputData implements ActionListener {
        JFrame parent;

        public InputData(JFrame parent) {
            this.parent = parent;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            System.out.println("Clicked");
            JDialog dialog = new JDialog(parent);
            JPanel inputSpace = new JPanel();
            inputSpace.setLayout(new FlowLayout());
//            inputSpace.setBounds(0, 300, 500, 200);
            var incomeLabel = new JLabel("How much is your income?");
            inputIncome = new JTextField("How much is your income?", 20);
            inputSpace.add(incomeLabel);
            inputSpace.add(inputIncome);
            inputSpace.add(new JButton("OK"));
            inputSpace.add(new JButton("Cancel"));
//            openPanel.add(inputSpace);
//            openFrame.removeAll();
//            openFrame.setContentPane(inputSpace);
//            openFrame.add(inputSpace);
//            parent.setContentPane(inputSpace);
            dialog.add(inputSpace);
            System.out.println("Removing all");
            dialog.setSize(500, 300);
            dialog.setVisible(true);
        }
    }

    public static void main(String... $) {
        var of = new OpenFrame();
    }
}

基本建议:

  1. 避免使用空布局和使用 setBounds(...) 来放置组件,因为这会导致 GUI 非常不灵活,虽然它们在一个平台上看起来不错,但在大多数其他平台或屏幕分辨率上看起来很糟糕,而且非常难以更新和维护。
  2. 用 Swing 交换或 show/hide 组件的最佳方法是使用 CardLayout。请查看 CardLayout tutorial.
  3. 不同的view应该用不同的JPanels表示
  4. 不同的观点应该有自己的观点class
  5. 将程序的数据和逻辑(程序的模型)从视图中分离到单独的 class 中,至少分离到模型和视图中 class 中,或者如果你愿意,一个 model-view-controller (MVC) 模式。

例如:

在下面的示例代码中,我在 SwapModel class:

中建立了模型和一些控制代码
import java.beans.*;
import java.util.*;
import javax.swing.event.*;

public class SwapModel {
    public static String INCOME = "income";
    private PropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);
    private SwapGuiMainPanel mainGui;
    private Deque<String> cardStack = new LinkedList<>();
    private double income = 0;

    public SwapModel(SwapGuiMainPanel mainGui) {
        this.mainGui = mainGui;
    }

    // when income is changed, update it and notify listeners.
    public void addIncome(double value) {
        double oldValue = this.income;
        double newValue = this.income + value;
        this.income = newValue;
        pcSupport.firePropertyChange(INCOME, oldValue, newValue);
    }
    
    public double getIncome() {
        return income;
    }

    // go to the next CardLayout view by calling show and pushing the view's constraint String onto the cardStack
    public void swap(String name) {
        mainGui.getCardLayout().show(mainGui.getHolderPanel(), name);
        cardStack.push(name);
    }
    
    // go to the previous CardLayout view by popping the cardStack.
    public String popCardStack() {
        String name = null;
        if (cardStack.size() > 1) {
            cardStack.pop();
            name = cardStack.peek();
            mainGui.getCardLayout().show(mainGui.getHolderPanel(), name);           
        }
        return name;
    }
    
    public void addPropertyChangeListener(String propName, PropertyChangeListener propListener) {
        pcSupport.addPropertyChangeListener(propName, propListener);
    }
}

这包含一个收入值,并通过使用 JavaBeans 属性 更改支持通知任何 class 想要注册有关收入值更改的侦听器的实体。它还通过控制 CardLayout 对象来帮助控制在什么时间显示哪个视图。

此示例程序代码的视图部分用于多个 JPanel,包括:

  • 一个 WelcomePanel,显示欢迎文本并具有帮助用户加载其他 JPanel 的按钮。它目前只允许显示一个 JPanel,即 LoadPanel。
  • 一个加载面板,它是一个允许用户添加或查看支出或收入的菜单。 JPanel 嵌套了多个布局管理器,包括 BoxLayout、GridLayout 和 GridBagLayout,以提供灵活的 GUI。 buttonFactory 方法允许创建具有统一外观的更大的 JButton。
  • 允许用户向程序模型添加额外收入的 AddIncomePanel。
  • 向用户显示当前收入的 ViewIncomePanel。 class 将使用 PropertyChangeListener 监听模型收入的变化 属性。
  • 最后一个 SwapGuiMainPanel 来保存 CardLayout 和使用此布局的 holderPanel,并将所有内容放在一起:
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.*;
import java.beans.*;
import java.text.NumberFormat;
import javax.swing.*;
import javax.swing.border.Border;

@SuppressWarnings("serial")
public class SwapGuiMainPanel extends JPanel {
    private CardLayout cardLayout = new CardLayout();
    private JPanel holderPanel = new JPanel(cardLayout);
    private SwapModel swapModel = new SwapModel(this);
    private WelcomePanel welcomePanel = new WelcomePanel(swapModel);
    private LoadPanel loadPanel = new LoadPanel(swapModel);
    private AddIncomePanel addIncomePanel = new AddIncomePanel(swapModel);
    private ViewIncomePanel viewIncomePanel = new ViewIncomePanel(swapModel);
    
    public SwapGuiMainPanel() {
        holderPanel.add(welcomePanel, WelcomePanel.NAME);
        holderPanel.add(loadPanel, LoadPanel.NAME);
        holderPanel.add(addIncomePanel, AddIncomePanel.NAME);
        holderPanel.add(viewIncomePanel, ViewIncomePanel.NAME);
        
        swapModel.addPropertyChangeListener(SwapModel.INCOME, new IncomeListener());
        
        int gap = 10;
        setBorder(BorderFactory.createEmptyBorder(gap, gap, gap, gap));
        setLayout(new BorderLayout());
        add(holderPanel);
    }
    
    private class IncomeListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            Double income = (Double) evt.getNewValue();
            if (income != null) {
                viewIncomePanel.setIncomeValue(income);
            }
        }
    }
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            SwapGuiMainPanel mainPanel = new SwapGuiMainPanel();

            JFrame frame = new JFrame("GUI");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(mainPanel);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        });
    }

    public void swap(String constraint) {
        cardLayout.show(holderPanel, constraint);
    }
    
    public CardLayout getCardLayout() {
        return cardLayout;
    }
    
    public JPanel getHolderPanel() {
        return holderPanel;
    }
    
}
@SuppressWarnings("serial")
class WelcomePanel extends JPanel {
    public static final String NAME = "welcome panel";
    private static final String WELCOME_TEXT = "Hello and Welcome! Choose an Option Below:";
    private SwapModel model;

    public WelcomePanel(SwapModel model) {
        this.model = model;
        
        int gap = 20;
        JPanel buttonPanel = new JPanel(new GridLayout(0, 1, gap, gap));
        JButton loadButton = new JButton("Load Data");
        loadButton.addActionListener(e -> loadData());
        JButton resetButton = new JButton("Reset Data");
        Font btnFont = loadButton.getFont().deriveFont(Font.BOLD, 16f);
        loadButton.setFont(btnFont);
        resetButton.setFont(btnFont);       
        buttonPanel.add(loadButton);
        buttonPanel.add(resetButton);
        
        setLayout(new GridBagLayout());
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.insets = new Insets(gap, gap, gap, gap);
        add(new JLabel(WELCOME_TEXT), gbc);     
        gbc.gridy++;
        add(buttonPanel, gbc);
    }
    
    private void loadData() {
        model.swap(LoadPanel.NAME);
    }
    
}
@SuppressWarnings("serial")
class LoadPanel extends JPanel {
    public static final String NAME = "load panel";
    private SwapModel model;

    public LoadPanel(SwapModel model) {
        this.model = model;
        
        JLabel title = new JLabel("Welcome to Budget Planner App");
        title.setHorizontalAlignment(SwingConstants.CENTER);
        title.setAlignmentX(JLabel.CENTER_ALIGNMENT);
        title.setFont(title.getFont().deriveFont(Font.BOLD, 16f));
        
        JLabel subTitle = new JLabel("What would you like to do?");
        subTitle.setHorizontalAlignment(SwingConstants.CENTER);
        subTitle.setAlignmentX(JLabel.CENTER_ALIGNMENT);
        subTitle.setFont(subTitle.getFont().deriveFont(Font.ITALIC));
        
        JPanel titlePanel = new JPanel();
        titlePanel.setLayout(new BoxLayout(titlePanel, BoxLayout.PAGE_AXIS));
        titlePanel.add(title);
        titlePanel.add(subTitle);
        titlePanel.add(Box.createVerticalStrut(30));
        
        JPanel buttonPanel = new JPanel(new GridLayout(0, 1, 10, 10));
        buttonPanel.add(buttonFactory("Add Income", e -> addIncome()));
        buttonPanel.add(buttonFactory("Add Expense", null));
        buttonPanel.add(buttonFactory("View Income", e -> viewIncome()));
        buttonPanel.add(buttonFactory("View Expense", null));
        
        JPanel wrapperPanel = new JPanel(new GridBagLayout());
        wrapperPanel.add(buttonPanel);
                
        int gap = 10;
        setBorder(BorderFactory.createEmptyBorder(gap, gap, gap, gap));
        setLayout(new BorderLayout());
        add(titlePanel, BorderLayout.PAGE_START);
        add(wrapperPanel);
    }
    
    private void addIncome() {
        model.swap(AddIncomePanel.NAME);
    }
    
    private void viewIncome() {
        model.swap(ViewIncomePanel.NAME);
    }
    
    private JButton buttonFactory(String text, ActionListener listener) {
        JButton button = new JButton(text);
        button.addActionListener(listener);
        button.setFont(button.getFont().deriveFont(Font.BOLD, 16f));
        Border originalBorder = button.getBorder();
        int gap = 10;
        Border innerBorder = BorderFactory.createEmptyBorder(gap, gap, gap, gap);
        button.setBorder(BorderFactory.createCompoundBorder(originalBorder, innerBorder));
        return button;
    }
    
}
@SuppressWarnings("serial")
class AddIncomePanel extends JPanel {
    public static final String NAME = "add income panel";
    private SwapModel swapModel;
    private JTextField incomeField = new JTextField(20);
    private JButton submitButton = new JButton("Submit");
    private JButton cancelButton = new JButton("Cancel");

    public AddIncomePanel(SwapModel swapModel) {
        this.swapModel = swapModel;
        JPanel topPanel = new JPanel();
        topPanel.add(new JLabel("Enter Income:"));
        topPanel.add(incomeField);
        
        submitButton.addActionListener(e -> submitData());
        cancelButton.addActionListener(e -> cancel());
        JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 5, 5));
        buttonPanel.add(submitButton);
        buttonPanel.add(cancelButton);
        
        JPanel innerPanel = new JPanel();
        innerPanel.setLayout(new GridLayout(0, 1, 5, 5));
        innerPanel.add(topPanel);
        innerPanel.add(buttonPanel);
        
        JButton backButton = new JButton("Back");
        backButton.addActionListener(e -> back());
        JPanel bottomButtonPanel = new JPanel();
        bottomButtonPanel.add(backButton);
        
        setLayout(new BorderLayout());
        add(innerPanel, BorderLayout.PAGE_START);
        add(bottomButtonPanel, BorderLayout.PAGE_END);
    }

    private void back() {
        swapModel.popCardStack();
    }

    private void submitData() {
        String text = incomeField.getText();
        try {
            double value = Double.parseDouble(text);
            swapModel.addIncome(value);
            back();
        } catch (NumberFormatException e) {
            // TODO: show error message
        }
    }
    
    private void cancel() {
        back();
    }
    
}
@SuppressWarnings("serial")
class ViewIncomePanel extends JPanel {
    public static final String NAME = "view income panel";
    private SwapModel swapModel;
    private JLabel incomeValue = new JLabel();
    private NumberFormat format = NumberFormat.getCurrencyInstance();

    public ViewIncomePanel(SwapModel swapModel) {
        this.swapModel = swapModel;
        
        JPanel topPanel = new JPanel();
        topPanel.add(new JLabel("Income: "));
        topPanel.add(incomeValue);

        JButton backButton = new JButton("Back");
        backButton.addActionListener(e -> back());
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(backButton);
        
        setLayout(new BorderLayout());
        add(topPanel, BorderLayout.PAGE_START);
        add(buttonPanel, BorderLayout.PAGE_END);
    }
    
    public void setIncomeValue(double income) {
        incomeValue.setText(format.format(income));
    }
    
    private void back() {
        swapModel.popCardStack();
    }
}