InputVerifier 更改按钮背景

InputVerifier changes button background

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
 
public class ButtonBackground2 extends JFrame {

    public ButtonBackground2() {
        setSize(350, 200);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);

        JPanel p= new JPanel();
        JTextField tf = new JTextField();
        tf.setPreferredSize(new Dimension(100, 20));
        tf.setInputVerifier(new NonEmptyVerifier());
        p.add(tf);
        add(p, BorderLayout.CENTER);

        p= new JPanel();
        JButton btn = new JButton("Button");
        btn.setPreferredSize(new Dimension(80, 30));
//        btn.addMouseListener(new BtnBackgroundListener());
        p.add(btn);
        add(p, BorderLayout.SOUTH);
        setVisible(true);
    }
 
 
    public static void main(String arg[]) {
        EventQueue.invokeLater(ButtonBackground2::new);
    }
 
 
    class NonEmptyVerifier extends InputVerifier {
/*
        public boolean shouldYieldFocus(JComponent source, JComponent target) {
            return verify(source);
        }
*/
        public boolean verify(final JComponent input) {
            JTextField tf = (JTextField) input;
            if (tf.getText().trim().length()>0) {
                System.out.println("OK");
                return true;
            }
            JOptionPane.showMessageDialog(ButtonBackground2.this,
                        "Enter at least one character.",
                        "Missing input", JOptionPane.ERROR_MESSAGE);
            return false;
        }
    }


    class BtnBackgroundListener extends MouseAdapter {
        public void mousePressed(final MouseEvent e) {
            SwingUtilities.invokeLater(() -> {
                JButton btn= (JButton)e.getSource();
                if (!btn.hasFocus()) btn.getModel().setPressed(false);
            });
        }
    }
 
}



编辑
令人惊讶的是,我可以将我的实际代码减少到一小部分来证明不当行为。

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import javax.swing.*;

public class Y extends JFrame {
  public static final long serialVersionUID = 100L;

  public Y() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setSize(300, 240);
    setLocationRelativeTo(null);

    add(createTextFieldPanel(), BorderLayout.CENTER);
    JButton bOK= new JButton("OK");
    bOK.addActionListener(e -> System.out.println("OK, input accepted."));
/*  Adding the following listener makes in case of erroneous input the focus
    locking of the textfield's InputVerifier shaky. The InputVerifier itself,
    however, works alright, as one sees from the unfailingly displayed error
    message.
*/
    bOK.addMouseListener(new BtnBackgroundListener());
    add(bOK, BorderLayout.SOUTH);
    setVisible(true);
  }


  static public void main(String args[]) {
    EventQueue.invokeLater(Y::new);
  }


  private JPanel createTextFieldPanel() {
    JPanel p= new JPanel(new FlowLayout(FlowLayout.LEFT));
    p.add(new JLabel("Input:"));
    MyTextField tf= new MyTextField(this);
    tf.setPreferredSize(new Dimension(95, 20));
    tf.setFont(new Font("Monospaced", Font.PLAIN, 13));
    p.add(tf);
    return p;
  }
}

-----------------------------------------------------------


import java.awt.*;
import javax.swing.*;

public class MyTextField extends JTextField {
  public static final long serialVersionUID = 50161L;

  Component parent;

  public MyTextField(Component parent) {
    this.parent= parent;
    setInputVerifier(new InputVerifier() {
/*
      public boolean shouldYieldFocus(JComponent source, JComponent target) {
        return verify(source);
      }
*/
      public boolean verify(JComponent comp) {
        if (getText().equals("pass")) return true;
        JOptionPane.showMessageDialog(parent,
            "Input does not match the requested format.\n"+getText(),
            "Input error", JOptionPane.ERROR_MESSAGE);
        return false;
      }
    });
  }
}

所以首先我们可以说 Camickr 怀疑 length/complexity 代码有任何影响是正确的。
其次,在此演示中,删除 MouseListener 也会阻止焦点被不适当地释放。
那么为什么程序 ButtonBackground2 工作而程序 Y 有时只能工作?有时在第一次点击按钮时接受了不正确的输入,有时必须重复点击几次。
顺便说一句,我 运行宁 jdk 18,构建 18+36-2087.

我也可以重现您在 Java 8 中看到的内容。这个答案的其余部分将与 Java 8.

一起使用

问题出在执行BtnBackgroundListener.

Yclass中创建的JButton(即引用bOK)使用了一个DefaultButtonModel,这是一个ButtonModel。但通常任何 AbstractButton 使用 ButtonModel 实例。

根据ButtonModel接口的文档:

...pressing and releasing the mouse over a regular button triggers the button and causes and ActionEvent to be fired.

考虑以下代码:

import javax.swing.JButton;
import javax.swing.SwingUtilities;

public class Test {
    
    private static void runExperiment() {
        final JButton button = new JButton("Test");
        button.addActionListener(e -> System.out.println("Action!"));
        button.getModel().setArmed(true);
        button.getModel().setPressed(true);
        System.out.println("Before release...");
        button.getModel().setPressed(false);
    }
    
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(Test::runExperiment);
    }
}

如果你运行这个,你会看到以下输出:

Before release...
Action!

然后程序将终止。这表明当释放按钮模型时(在按下+激活之后),然后在给定的 ActionListener 上触发 ActionEvent。在您的代码中, setPressed(false)BtnBackgroundListener 的实现中被调用(在 invokeLater 中,但稍后我会注意到这一点)。

那么是谁在预先调用 setArmed(true)setPressed(true)(启动按下状态所必需的)?根据源码或简单的实验(例如System.out.println(BasicButtonUI.class.isInstance(button.getUI()));),可以看出按钮上安装的ButtonUIBasicButtonUI类型的(子class),这反过来安装默认的 MouseListener,它可以做你能想象到的事情:当用户在按钮范围内单击鼠标时,它通过将模型的状态更改为 armed 和 pressed 来使按钮正常工作。它还可以启用翻转效果、释放和其他功能,但这些与问题无关。

BtnBackgroundListener 也是一个 MouseListener,它也安装在按钮上(连同 UI 安装的默认按钮)。因此,当您单击该按钮时,两个 MouseListener 都会被调用(注意 MouseListener 也适用于当前没有焦点的组件)。所以代码在内部按顺序调用所有 MouseListeners,但是按什么顺序并不重要,因为通过在 SwingUtilities#invokeLater 方法中调用 setPressed(false) 可以确保释放该模型将在所有 MouseListener 被调用后发生。因此,默认的 MouseListener 首先将按钮设置为武装并按下,一段时间后你释放模型,这又会在每个 ActionListener 上触发一个 ActionEvent (包括接受输入)。

总是会打电话给您的 MouseListener。尽管在您的 MouseListener 中发布模型并不总是会触发 ActionEvent,我现在想不出可能的解释。

为了防止这种情况...

不要使用 MouseListener 来监听按钮上的动作事件。整个逻辑已经实现,你只需要单独注册一个ActionListener

既然您想使用 InputVerifier,那么我建议在按钮的动作侦听器(来自 InputVerifier)上传递一个标志,这将指示输入的健全性。例如:

import java.awt.GridLayout;
import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class Main {
    
    private static class MyInputVerifier extends InputVerifier {
        
        private boolean validInput = false;
        
        @Override
        public boolean verify(final JComponent input) {
            validInput = ((JTextField) input).getText().equals("pass");
            return validInput;
        }
    }
    
    private static void createAndShowGUI() {
        
        final JTextField field1 = new JTextField(12),
                         field2 = new JTextField("Anything");
        final JButton accept = new JButton("Submit");
        
        final MyInputVerifier miv = new MyInputVerifier();
        field1.setInputVerifier(miv);
        
        accept.addActionListener(e -> {
            if (miv.validInput)
                System.out.println("Accepted!");
            else
                JOptionPane.showMessageDialog(accept, "Invalid input!");
        });
        
        final JPanel form = new JPanel(new GridLayout(0, 1));
        form.add(field1);
        form.add(field2);
        form.add(accept);
        
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(form);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(Main::createAndShowGUI);
    }
}

此建议基于 gthanop 的建议,并结合了 OP 在验证失败时重置按钮模型状态的方法:

import java.awt.GridLayout;
import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class Main5 {

    private static class MyInputVerifier extends InputVerifier {

        private boolean validInput = false;
        private JButton button;

        public MyInputVerifier (JButton button)
        {
            this.button = button;
        }

        @Override
        public boolean verify(final JComponent input)
        {
            validInput = ((JTextField) input).getText().equals("pass");

            if (!validInput)
            {
                JOptionPane.showMessageDialog(input, "Verifier detected invalid input!");

                button.getModel().setPressed(false);
            }

            return validInput;
        }
    }

    private static void createAndShowGUI() {

        final JTextField field1 = new JTextField(12),
                         field2 = new JTextField("Anything");
        final JButton accept = new JButton("Submit");

        final MyInputVerifier miv = new MyInputVerifier(accept);
        field1.setInputVerifier(miv);

        accept.addActionListener(e -> {
            if (miv.validInput)
                System.out.println("Accepted!");
//            else
//                JOptionPane.showMessageDialog(accept, "Invalid input!");
        });

        final JPanel form = new JPanel(new GridLayout(0, 1));
        form.add(field1);
        form.add(field2);
        form.add(accept);
        
        final JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(form);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    public static void main(final String[] args) {
        SwingUtilities.invokeLater(Main5::createAndShowGUI);
    }
}