简体   繁体   English

InputVerifier 更改按钮背景

[英]InputVerifier changes button background

  • The code below shows just a textfield and a button.下面的代码只显示了一个文本字段和一个按钮。

  • The textfield has got an inputVerifier which doesn't accept an empty field.文本字段有一个不接受空字段的 inputVerifier。

  • As long as the verifier's "false" result is signalled by an optionPane, the button's background turns gray after closing the optionPane and turns "pressed" on mouseOver (and only if the button was clicked; not if the textfield was left with the tab key).只要验证器的“假”结果由 optionPane 发出信号,按钮的背景在关闭 optionPane 后变为灰色并在 mouseOver 上变为“按下”(并且仅当按钮被单击时;如果文本字段留有 tab 键则不会).

    Now remove the comment slashes for the button's MouseListener and run the program again.现在删除按钮的 MouseListener 的注释斜线并再次运行程序。 You'll see that the button returns to its regular background once the optionPane is closed.一旦关闭 optionPane,您会看到该按钮返回到其常规背景。

    This solution works in many cases, but is not free from getting instable when it comes to real programs beyond the scope of an SSCCE.这个解决方案在很多情况下都有效,但当涉及到 SSCCE 的 scope 之外的实际程序时,它并不能避免变得不稳定。 With instable I mean that sometimes everything works as expected and sometimes although the inputVerifier signals the error and returns "false", the focus is released from the textField and thus the missing input is accepted.对于不稳定,我的意思是有时一切都按预期工作,有时尽管 inputVerifier 发出错误信号并返回“false”,焦点从 textField 释放,因此丢失的输入被接受。 I assume this is due to the invokeLater in the MouseListener.我假设这是由于 MouseListener 中的 invokeLater。

    I could reduce my current actual code to the minimum to demonstrate the problem, but I'm afraid that I end up with several pages of code.我可以将我当前的实际代码减少到最少来演示这个问题,但恐怕我最终会得到几页代码。 So I'd first like to ask, whether someone has already dealt with the problem and can give a hint.所以我首先想问一下,有没有人已经处理过这个问题,可以给个提示。 Thanks.谢谢。

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



EDIT编辑
Surprisingly I could reduce my actual code to a small portion to demonstrate the misbehaviour.令人惊讶的是,我可以将我的实际代码减少到一小部分来演示不当行为。

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

So first we can say that Camickr was right in doubting that length/complexity of code was of any influence.所以首先我们可以说 Camickr 怀疑代码的长度/复杂性有任何影响是正确的。
And second that in this demo, too, removing the MouseListener stops the focus from being released inappropriately.其次,在此演示中,删除 MouseListener 也会阻止焦点被不当释放。
So why is program ButtonBackground2 working and program Y only at times?那么,为什么程序ButtonBackground2有时会工作而程序Y有时只能工作? Sometimes the incorrect input is accepted on the very first button click, sometimes one has to repeat the click several times.有时错误的输入在第一次点击按钮时被接受,有时必须重复点击几次。
By the way I'm running jdk 18, build 18+36-2087.顺便说一句,我正在运行 jdk 18,build 18+36-2087。

I can reproduce what you are seeing in Java 8 too.我也可以重现您在 Java 8 中看到的内容。 The rest of this answer is going to work with Java 8.此答案的 rest 将与 Java 8 一起使用。

The problem lies in the implementation of BtnBackgroundListener .问题在于BtnBackgroundListener的实现。

The created JButton in the Y class (ie reference bOK ) uses a DefaultButtonModel which is a ButtonModel . Y class 中创建的JButton (即引用bOK )使用DefaultButtonModel ,它是ButtonModel But in general any AbstractButton uses a ButtonModel instance.但通常任何AbstractButton都使用ButtonModel实例。

According to the documentation of the ButtonModel interface:根据ButtonModel接口的文档:

...pressing and releasing the mouse over a regular button triggers the button and causes and ActionEvent to be fired. ...在常规按钮上按下和释放鼠标会触发按钮并导致 ActionEvent 被触发。

Consider the following code:考虑以下代码:

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

If you run this, you will see the following output:如果运行它,您将看到以下 output:

Before release...
Action!

and then the program will terminate.然后程序将终止。 This demonstrates that when releasing the button model (after it is pressed+armed), then an ActionEvent is fired on the given ActionListener .这表明当释放按钮 model 时(在按下+激活之后),然后在给定的ActionListener上触发ActionEvent In your code the setPressed(false) is invoked inside the implementation of BtnBackgroundListener (within invokeLater but I will note this a bit later).在您的代码中, setPressed(false)BtnBackgroundListener的实现中被调用(在invokeLater中,但稍后我会注意到这一点)。

So who is calling setArmed(true) and setPressed(true) beforehand (which are required for initiating the pressed state)?那么谁在事先调用setArmed(true)setPressed(true) (这是启动按下状态所必需的)? According to the source or a simple expreriment (eg System.out.println(BasicButtonUI.class.isInstance(button.getUI())); ), one can see that the installed ButtonUI on the button is (a subclass) of type BasicButtonUI , which in turn installs the default MouseListener which does what you can imagine it does: it makes the button function properly by changing the model's state to armed and pressed when the user clicks the mouse inside the button bounds.根据来源或一个简单的实验(例如System.out.println(BasicButtonUI.class.isInstance(button.getUI())); ),可以看到按钮上安装的ButtonUIBasicButtonUI类型的(子类) ,这又会安装默认的MouseListener ,它会执行您可以想象的操作:当用户在按钮范围内单击鼠标时,它通过将模型的 state 更改为武装和按下来正确地使按钮 function 正确。 It also enables rollover effects, releasing, and other stuff, but those are irrelevant for the sake of the problem.它还可以启用翻转效果、释放和其他功能,但这些与问题无关。

BtnBackgroundListener is a MouseListener too, which is also installed on the button (along with the default one that the UI installed). BtnBackgroundListener也是一个MouseListener ,它也安装在按钮上(连同 UI 安装的默认按钮)。 So when you click on the button then both MouseListener s are invoked (note MouseListener s also work on components which do not currently have focus).因此,当您单击按钮时,两个MouseListener都会被调用(注意MouseListener也适用于当前没有焦点的组件)。 So the code internally calls all the MouseListener s sequencially, but it doesn't really matter in what sequence, because by calling the setPressed(false) inside the SwingUtilities#invokeLater method you make sure that the release of the model will happen after all MouseListener s have been invoked.所以代码在内部按顺序调用所有MouseListener s,但按什么顺序并不重要,因为通过在SwingUtilities#invokeLater方法中调用setPressed(false)可以确保在所有MouseListener之后释放 model已被调用。 Thus the default MouseListener first sets the button to armed and pressed, and some time later you release the model, which in turn fires an ActionEvent on each ActionListener (including the one which accepts the input).因此,默认的MouseListener首先将按钮设置为 armed 和 pressed,一段时间后您释放 model,它依次在每个ActionListener (包括接受输入的 ActionListener)上触发一个ActionEvent

Calling your MouseListener always happens.总是会调用您的MouseListener Releasing the model though in your MouseListener doesn't always trigger an ActionEvent and I can't think of a possible explanation for this right now.尽管在您的MouseListener中释放 model 并不总是会触发ActionEvent ,我现在想不出可能的解释。

To prevent this...为了防止这种...

Don't use a MouseListener for listening on action events on buttons.不要使用MouseListener来监听按钮上的动作事件。 This whole logic is already implemented and you only have to register an ActionListener alone.整个逻辑已经实现,您只需单独注册一个ActionListener

Since you want to use an InputVerifier then I would suggest to pass a flag on the button's action listener (from the InputVerifier ) which will indicate the sanity of the input.既然你想使用InputVerifier那么我建议在按钮的动作侦听器(来自InputVerifier )上传递一个标志,这将指示输入的完整性。 For example:例如:

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

This suggestions builds on gthanop's suggestion and incorporates the OP's approach to reset the button model state when validation fails:此建议建立在 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