当焦点被JOptionPane窃取时,JButton保持按下状态

时间:2012-09-19 18:13:03

标签: java swing jtextfield joptionpane inputverifier

我是Swing的新手,我有一个情况。我正在设计一个应用程序,它根据xml文件输入(元数据)动态呈现GUI组件。现在,我的大多数JTextField都将InputVerifier设置为它们,以进行验证。只要输入无效,输入验证程序就会弹出JOptionPane。

现在,如果用户输入无效数据并向前移动并单击面板上的按钮,则会弹出一个对话框,用户必须对其进行响应。但在此之后,按钮也不会绘制以释放状态。它仍然看起来像被按下但实际上它不是。由于整个代码非常混乱,我将问题场景放在下面的代码中: -

我该怎么做才能让JButton看起来没穿#?如果逻辑也被解释,我将不胜感激。

提前致谢。

package test;

import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;

public class VerifierTest extends JFrame {

    private static final long serialVersionUID = 1L;

    public VerifierTest() {
        JTextField tf;
        tf = new JTextField("TextField1");

        getContentPane().add(tf, BorderLayout.NORTH);
        tf.setInputVerifier(new PassVerifier());

        final JButton b = new JButton("Button");
        b.setVerifyInputWhenFocusTarget(true);
        getContentPane().add(b, BorderLayout.EAST);
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (b.hasFocus())
                    System.out.println("Button clicked");
            }
        });

        addWindowListener(new MyWAdapter());
    }

    public static void main(String[] args) {
        Frame frame = new VerifierTest();
        frame.setSize(400, 200);
        frame.setVisible(true);
        //frame.pack();
    }

    class MyWAdapter extends WindowAdapter {

        public void windowClosing(WindowEvent event) {
            System.exit(0);
        }
    }

    class PassVerifier extends InputVerifier {

        public boolean verify(JComponent input) {
            JTextField tf = (JTextField) input;
            String pass = tf.getText();
            if (pass.equals("Manish"))
                return true;
            else {
                String message = "illegal value: " + tf.getText();
                JOptionPane.showMessageDialog(tf.getParent(), message,
                        "Illegal Value", JOptionPane.ERROR_MESSAGE);

                return false;
            }
        }
    }
}

5 个答案:

答案 0 :(得分:3)

方法verify实际上不是打开JOptionPane的好地方。

您可以考虑采用几种方法来解决问题:

  1. 每当文本字段失去焦点并且输入不正确时,您希望此JOptionPane出现:在JTextField上使用FocusListener并根据适当的事件进行操作
  2. 每次按下按钮时都希望显示此JOptionPane:如果输入不正确,请使用ActionListener执行此操作。
  3. 以下是后一个选项的小片段:

    import java.awt.BorderLayout;
    import java.awt.Frame;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    import javax.swing.InputVerifier;
    import javax.swing.JButton;
    import javax.swing.JComponent;
    import javax.swing.JFrame;
    import javax.swing.JOptionPane;
    import javax.swing.JTextField;
    
    public class VerifierTest extends JFrame {
    
        private static final long serialVersionUID = 1L;
    
        public VerifierTest() {
            final JTextField tf = new JTextField("TextField1");
    
            getContentPane().add(tf, BorderLayout.NORTH);
            tf.setInputVerifier(new PassVerifier());
    
            final JButton b = new JButton("Button");
            b.setVerifyInputWhenFocusTarget(true);
            getContentPane().add(b, BorderLayout.EAST);
            b.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    if (!tf.getInputVerifier().verify(tf)) {
                        JOptionPane.showMessageDialog(tf.getParent(), "illegal value: " + tf.getText(), "Illegal Value",
                                JOptionPane.ERROR_MESSAGE);
                    }
                    if (b.hasFocus()) {
                        System.out.println("Button clicked");
                    }
                }
            });
            setDefaultCloseOperation(EXIT_ON_CLOSE);
        }
    
        public static void main(String[] args) {
            Frame frame = new VerifierTest();
            frame.setSize(400, 200);
            frame.setVisible(true);
        }
    
        class PassVerifier extends InputVerifier {
    
            @Override
            public boolean verify(JComponent input) {
                final JTextField tf = (JTextField) input;
                String pass = tf.getText();
                return pass.equals("Manish");
            }
        }
    }
    

    还要考虑设置JFrame的默认关闭操作,而不是添加窗口监听器(但是如果你想弹出一个对话框询问用户是否确定要退出你的话,这是一个使用WindowListener的好方法应用程序)。

答案 1 :(得分:1)

我添加了对SwingUtilities的调用,以确保GUI在事件线程上,并删除了对Frame的引用。

GUI在Windows XP上适用于我。

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

public class VerifierTest implements Runnable {

    private static final long serialVersionUID = 1L;

    public VerifierTest() {

    }

    @Override
    public void run() {
        JFrame frame = new JFrame();
        frame.setSize(400, 200);

        JTextField tf;
        tf = new JTextField("TextField1");
        tf.setInputVerifier(new PassVerifier());
        frame.getContentPane().add(tf, BorderLayout.NORTH);

        final JButton b = new JButton("Button");
        b.setVerifyInputWhenFocusTarget(true);
        frame.getContentPane().add(b, BorderLayout.EAST);
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (b.hasFocus())
                    System.out.println("Button clicked");
            }
        });

        frame.addWindowListener(new MyWAdapter());
        frame.setVisible(true);
    }

    public static void main(String[] args) {
       SwingUtilities.invokeLater(new VerifierTest());
    }

    class MyWAdapter extends WindowAdapter {
        @Override
        public void windowClosing(WindowEvent event) {
            System.exit(0);
        }
    }

    class PassVerifier extends InputVerifier {
        @Override
        public boolean verify(JComponent input) {
            JTextField tf = (JTextField) input;
            String pass = tf.getText();
            if (pass.equals("Manish"))
                return true;
            else {
                String message = "illegal value: " + tf.getText();
                JOptionPane.showMessageDialog(tf.getParent(), message,
                        "Illegal Value", JOptionPane.ERROR_MESSAGE);

                return false;
            }
        }
    }
}

答案 2 :(得分:1)

我已经为按钮添加了一个新的鼠标监听器,如下所示,它现在似乎对我来说很好,但我不确定它是否是纠正按钮选择状态的好方法。

package test;

import java.awt.BorderLayout;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.InputVerifier;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextField;
import javax.swing.plaf.basic.BasicButtonListener;

public class VerifierTest extends JFrame {

    private static final long serialVersionUID = 1L;

    public VerifierTest() {
        JTextField tf;
        tf = new JTextField("TextField1");

        getContentPane().add(tf, BorderLayout.NORTH);
        tf.setInputVerifier(new PassVerifier());

        final JButton b = new JButton("Button");
        b.setVerifyInputWhenFocusTarget(true);
        getContentPane().add(b, BorderLayout.EAST);
        b.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (b.hasFocus())
                    System.out.println("Button clicked");
            }
        });

        b.addMouseListener(new BasicButtonListener(b) {
            @Override
            public void mouseExited(MouseEvent e) {
                ((JButton)e.getSource()).getModel().setArmed(false);
                ((JButton)e.getSource()).getModel().setPressed(false);
            }

        });

        addWindowListener(new MyWAdapter());
    }

    public static void main(String[] args) {
        Frame frame = new VerifierTest();
        frame.setSize(400, 200);
        frame.setVisible(true);
        // frame.pack();
    }

    class MyWAdapter extends WindowAdapter {

        public void windowClosing(WindowEvent event) {
            System.exit(0);
        }
    }

    class PassVerifier extends InputVerifier {

        public boolean verify(JComponent input) {
            JTextField tf = (JTextField) input;
            String pass = tf.getText();
            if (pass.equals("Manish"))
                return true;
            else {
                final String message = "illegal value: " + tf.getText();
                        JOptionPane.showMessageDialog(null, message,
                                "Illegal Value", JOptionPane.ERROR_MESSAGE);

                return false;
            }
        }
    }
}

答案 3 :(得分:1)

首先:所有实现的InputVerifier在verify()中打开对话框无效。他们违反了他们的合同,API doc:

  

这种方法应该没有副作用。

用“应该”的意思是“绝对不能”。副作用的正确位置是shouldFieldFocus。

第二:将副作用(显示消息对话框)正确地移动到shouldYieldFocus中也不起作用......由于bug (THEY call it feature request ;-),这比十年前的in the top 10 RFEs < / p>

作为一个黑客攻击的bug,@ dareurdrem的mouseListener和任何可行的黑客一样好: - )

<强>更新

玩了一些不同的选项来破解这个bug之后,这是另一个黑客 - 它和所有黑客一样脆弱(并且不能在LAF切换中存活,如果需要动态切换则必须重新安装)< / p>

对于黑客攻击鼠标的行为,基本方法是挂钩ui所安装的监听器:

  • 找到原文
  • 实现自定义侦听器,该侦听器将大多数事件直接委派给原始
  • 对于按下的事件请求首先关注:如果委托给原始委托,如果没有做任何事情

由于焦点事​​件可能是异步的,因此我们必须调用检查以进行聚焦。反过来,调用需要在没有人反对的情况下发送一个版本。

另一个怪癖是rootPane的按下动作(对于它的defaultButton):通过无条件地调用doClick来完成它而不尊重任何inputVerifiers。这可以通过挂钩到动作来攻击,遵循与挂钩到mouseListener相同的模式:

  • 找到rootPane的按下操作
  • 实现一个自定义操作,用于检查可能否决的inputVerifier:如果没有,则委托给原始,否则不执行任何操作

这些例子修改了这个例子:

public class VerifierTest implements Runnable {

    private static final long serialVersionUID = 1L;

    @Override
    public void run() {
        InteractiveTestCase.setLAF("Win");
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(400, 200);

        JTextField tf = new JTextField("TextField1");
        tf.setInputVerifier(new PassVerifier());
        frame.add(tf, BorderLayout.NORTH);

        final JButton b = new JButton("Button");
        frame.add(b);
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
               System.out.println("Button clicked");
            }
        });
        // hook into the mouse listener
        replaceBasicButtonListener(b);
        frame.add(new JTextField("not validating, something else to focus"),
                BorderLayout.SOUTH);
        frame.getRootPane().setDefaultButton(b);
        // hook into the default button action
        Action pressDefault = frame.getRootPane().getActionMap().get("press");
        frame.getRootPane().getActionMap().put("press", new DefaultButtonAction(pressDefault));
        frame.setVisible(true);
    }

    protected void replaceBasicButtonListener(AbstractButton b) {
        final BasicButtonListener original = getButtonListener(b);
        if (original == null) return;
        Hacker l = new Hacker(original);
        b.removeMouseListener(original);
        b.addMouseListener(l);
    }

    public static class Hacker implements MouseListener {
        private BasicButtonListener original;

        /**
         * @param original the listener to delegate to.
         */
        public Hacker(BasicButtonListener original) {
            this.original = original;
        }

        /**
         * Hook into the mousePressed: first request focus and
         * check its success before handling it.
         */
        @Override
        public void mousePressed(final MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e)) {
                if(e.getComponent().contains(e.getX(), e.getY())) {
                    // check if we can get the focus
                    e.getComponent().requestFocus();
                    invokeHandleEvent(e);
                    return;
                }
            }
            original.mousePressed(e);
        }

        /**
         * Handle the pressed only if we are focusOwner.
         */
        protected void handlePressed(final MouseEvent e) {
            if (!e.getComponent().hasFocus())  {
                // something vetoed the focus transfer
                // do nothing
                return;
            } else {
                original.mousePressed(e);
                // need a fake released now: the one from the
                // original cycle might never has reached us
                MouseEvent released = new MouseEvent(e.getComponent(), MouseEvent.MOUSE_RELEASED,
                        e.getWhen(), e.getModifiers(), 
                        e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger()
                        );
                original.mouseReleased(released);
            }
        }


        /**
         * focus requests might be handled
         * asynchronously. So wrap the check 
         * wrap the block into an invokeLater.
         */
        protected void invokeHandleEvent(final MouseEvent e) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    handlePressed(e);
                }
            });
        }

        @Override
        public void mouseClicked(MouseEvent e) {
            original.mouseClicked(e);
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            original.mouseReleased(e);
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            original.mouseEntered(e);
        }

        @Override
        public void mouseExited(MouseEvent e) {
            original.mouseExited(e);
        }
    }
    public static class DefaultButtonAction extends AbstractAction {

        private Action original;

        /**
         * @param original
         */
        public DefaultButtonAction(Action original) {
            this.original = original;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            JRootPane root = (JRootPane) e.getSource();
            JButton owner = root.getDefaultButton();
            if (owner != null && owner.getVerifyInputWhenFocusTarget()) {
                Component c = KeyboardFocusManager
                        .getCurrentKeyboardFocusManager()
                         .getFocusOwner();
                if (c instanceof JComponent && ((JComponent) c).getInputVerifier() != null) {
                    if (!((JComponent) c).getInputVerifier().shouldYieldFocus((JComponent) c)) return;
                }


            }
            original.actionPerformed(e);
        }

    }
    /**
     * Returns the ButtonListener for the passed in Button, or null if one
     * could not be found.
     */
    private BasicButtonListener getButtonListener(AbstractButton b) {
        MouseMotionListener[] listeners = b.getMouseMotionListeners();

        if (listeners != null) {
            for (MouseMotionListener listener : listeners) {
                if (listener instanceof BasicButtonListener) {
                    return (BasicButtonListener) listener;
                }
            }
        }
        return null;
    }

    public static void main(String[] args) {
       SwingUtilities.invokeLater(new VerifierTest());
    }


    public static class PassVerifier extends InputVerifier {
        /**
         * Decide whether or not the input is valid without
         * side-effects.
         */
        @Override
        public boolean verify(JComponent input) {
            final JTextField tf = (JTextField) input;
            String pass = tf.getText();
            if (pass.equals("Manish"))
                return true;
            return false;
        }

        /**
         * Implemented to ask the user what to do if the input isn't valid.
         * Note: not necessarily the best usability, it's mainly to
         * demonstrate the different effects on not/agreeing with
         * yielding focus transfer.
         */
        @Override
        public boolean shouldYieldFocus(final JComponent input) {
            boolean valid = super.shouldYieldFocus(input);
            if (!valid) {
                String message = "illegal value: " + ((JTextField) input).getText();
                int goAnyWay = JOptionPane.showConfirmDialog(input, "invalid value: " +
                        message + " - go ahead anyway?");
                valid = goAnyWay == JOptionPane.OK_OPTION;
            }
            return valid;
        }
    }
}

答案 4 :(得分:0)

实际上真正的问题在于焦点系统和awt听众如何互动。在Java中声明了一些错误,开发人员会在负责人身上反复询问。 鼠标监听器执行:processMouseEvent并在该逻辑中,要求当前的FocusOwner产生Focus。它失败。但由于已经处理了一半的事件,因此该按钮变为待命状态,焦点仍然保留在该字段中。

我终于看到一位开发者发表评论:如果不允许该字段失去焦点,请不要让听众继续。

例如: 定义一个JTextfield,其编辑只允许值&lt; 100。 失去焦点时会弹出一条消息。 我覆盖了我的基本JButton类的processMouseEvent(MouseEvent e) 代码:

protected void processMouseEvent(MouseEvent e) {
    if ( e.getComponent() != null && e.getComponent().isEnabled() ) { //should not be processing mouse events if it's disabled.
            if (e.getID() == MouseEvent.MOUSE_RELEASED && e.getClickCount() == 1) {
                // The mouse button is being released as per normal, and it's the first click. Process it as per normal.
                super.processMouseEvent(e);

                // If the release occured within the bounds of this component, we want to simulate a click as well
                if (this.contains(e.getX(), e.getY())) {
                    super.processMouseEvent(new MouseEvent(e.getComponent(),
                                                            MouseEvent.MOUSE_CLICKED,
                                                            e.getWhen(),
                                                            e.getModifiers(),
                                                            e.getX(),
                                                            e.getY(),
                                                            e.getClickCount(),
                                                            e.isPopupTrigger(),
                                                            e.getButton()));
                }
            }
            else if (e.getID() == MouseEvent.MOUSE_CLICKED && e.getClickCount() == 1) {
                // Normal clicks are ignored to prevent duplicate events from normal, non-moved events
            }
            else if (e.getID() == MouseEvent.MOUSE_PRESSED && e.getComponent() != null && (e.getComponent().isFocusOwner() || e.getComponent().requestFocusInWindow())) {// if already focus owner process mouse event
                super.processMouseEvent(e); 
            }
            else {
                // Otherwise, just process as per normal.
                if (e.getID() != MouseEvent.MOUSE_PRESSED) {
                    super.processMouseEvent(e); 
                }
            }
        }
}

这个逻辑的内容是简单的问题。 按钮:你是否已经成为焦点所有者。 如果不是:你可以(按钮)可能GAIN焦点(记住 - 在requestFocusInWindow()调用内的当前焦点持有者上调用shouldYieldFocus(),如果无效则返回false ALWAYS)

这也有弹出错误对话框的副作用!

此逻辑停止Java库processMouseEvent逻辑处理半个事件,而Focus System阻止它完成。

显然,你需要在所有不同的JComponents上使用这种类型的逻辑来执行点击操作。