繁体   English   中英

InputVerifier 更改按钮背景

[英]InputVerifier changes button background

  • 下面的代码只显示了一个文本字段和一个按钮。

  • 文本字段有一个不接受空字段的 inputVerifier。

  • 只要验证器的“假”结果由 optionPane 发出信号,按钮的背景在关闭 optionPane 后变为灰色并在 mouseOver 上变为“按下”(并且仅当按钮被单击时;如果文本字段留有 tab 键则不会).

    现在删除按钮的 MouseListener 的注释斜线并再次运行程序。 一旦关闭 optionPane,您会看到该按钮返回到其常规背景。

    这个解决方案在很多情况下都有效,但当涉及到 SSCCE 的 scope 之外的实际程序时,它并不能避免变得不稳定。 对于不稳定,我的意思是有时一切都按预期工作,有时尽管 inputVerifier 发出错误信号并返回“false”,焦点从 textField 释放,因此丢失的输入被接受。 我假设这是由于 MouseListener 中的 invokeLater。

    我可以将我当前的实际代码减少到最少来演示这个问题,但恐怕我最终会得到几页代码。 所以我首先想问一下,有没有人已经处理过这个问题,可以给个提示。 谢谢。

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 怀疑代码的长度/复杂性有任何影响是正确的。
其次,在此演示中,删除 MouseListener 也会阻止焦点被不当释放。
那么,为什么程序ButtonBackground2有时会工作而程序Y有时只能工作? 有时错误的输入在第一次点击按钮时被接受,有时必须重复点击几次。
顺便说一句,我正在运行 jdk 18,build 18+36-2087。

我也可以重现您在 Java 8 中看到的内容。 此答案的 rest 将与 Java 8 一起使用。

问题在于BtnBackgroundListener的实现。

Y class 中创建的JButton (即引用bOK )使用DefaultButtonModel ,它是ButtonModel 但通常任何AbstractButton都使用ButtonModel实例。

根据ButtonModel接口的文档:

...在常规按钮上按下和释放鼠标会触发按钮并导致 ActionEvent 被触发。

考虑以下代码:

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

如果运行它,您将看到以下 output:

Before release...
Action!

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

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

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

总是会调用您的MouseListener 尽管在您的MouseListener中释放 model 并不总是会触发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 在验证失败时重置按钮 model state 的方法:

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM