7

I am new to Swing and I have a situation. I am designing an application that renders the GUI components dynamically based on an xml file input(meta-data) . Now most of my JTextFields have InputVerifier set to them, for validation purpose. The input verifier pops up JOptionPane whenever there is an invalid input.

Now, if a user enter an invalid data and moves ahead and clicks a button on the Panel, then a dialog pops up and the user have to respond to it. but after that also the button does not paint to release state. It still looked like it is pressed but actually it is not. As the whole code is pretty messy, I am putting the problem scenario in the code below:-

What should I do so that the JButton looks unpressed? I would appreciate if the logic is also explained.

Thanks in advance.

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

5 回答 5

3

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

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

  1. 您希望每次文本字段失去焦点并且输入不正确时出现此 JOptionPane:在 JTextField 上使用 FocusListener 并根据适当的事件采取行动
  2. 您希望每次按下按钮时都会出现此 JOptionPane:如果输入不正确,请使用 ActionListener 执行此操作。

这是后一个选项的一小部分:

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 是一种好方法)。

于 2012-09-19T18:55:24.863 回答
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;
            }
        }
    }
}
于 2012-09-19T20:33:25.737 回答
1

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

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

“应该”的真正意思是“不得”。副作用的正确位置是 shouldYieldFocus。

第二:将副作用(显示消息对话框)正确移动到 shouldYieldFocus 中效果不佳......由于一个错误(他们称之为功能请求;-),这已经超过十年并且在前 10 名RFE

作为一个绕过漏洞的黑客,@dareurdrem 的 mouseListener 与任何可行的黑客一样好:-)

更新

在尝试了一些不同的选项来破解这个错误之后,这里是另一个 hack - 它和所有 hack 一样脆弱(并且不能在 LAF 切换中存活,如果需要动态切换,则必须重新安装)

为了破解鼠标行为,基本的方法是连接到 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;
        }
    }
}
于 2012-09-22T10:10:13.287 回答
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;
            }
        }
    }
}
于 2012-09-19T18:56:29.157 回答
0

实际上,真正的问题在于焦点系统和 awt 侦听器如何交互。在 Java 中声明了一些错误,开发人员正在反复讨论谁负责。鼠标侦听器执行:processMouseEvent 并且在该逻辑中,当前 FocusOwner 被要求产生焦点。它失败。但是因为已经处理了一半的事件,所以按钮变为武装并且焦点仍然在该字段上。

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

例如:定义一个带有编辑的 JTextfield,只允许值 < 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); 
                }
            }
        }
}

这个逻辑的核心是简单的问题。按钮:你已经是焦点所有者了吗?如果不是:你(按钮)是否可能获得焦点(记住 - shouldYieldFocus() 在 requestFocusInWindow() 调用中的当前焦点持有者上调用,如果无效,则始终返回 false )

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

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

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

于 2013-04-10T13:43:31.613 回答