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

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

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

我添加了对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; } } } } 

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

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

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

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

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

第二:将副作用(显示消息对话框)正确地移动到shouldYieldFocus中也不起作用……由于一个错误(他们称之为function请求;-) ,这已经超过了十年并进入了前十名RFEs

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

更新

在玩了一些不同的选项来破解bug后,这是另一个黑客 – 它和所有黑客一样脆弱(并且不能在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; } } } 

实际上,真正的问题在于焦点系统和awt听众如何相互作用。 在Java中声明了一些错误,开发人员会负责谁负责。 鼠标监听器执行:processMouseEvent,并且在该逻辑中,要求当前的FocusOwner产生Focus。 它失败。 但由于已经处理了一半的事件,按钮变为武装状态,焦点仍然在现场。

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

例如:定义一个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); } } } } 

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

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

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

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