2012-09-19 42 views
5

我是新來的Swing,我有一種情況。我正在設計一個基於xml文件輸入(元數據)動態呈現GUI組件的應用程序。現在我的大部分JTextFields都爲它們設置了InputVerifier,用於驗證目的。輸入驗證器會在出現無效輸入時彈出JOptionPane。JButton保持按下當焦點被偷了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; 
      } 
     } 
    } 
} 
+4

請嘗試將showMessageDialog調用包裝到Runnable中,並將它提供給SwingUtilities :: invokeLater(Runnable) – gd1

+0

@ gd14嗨,我試過了你陳述的方法,但它似乎不起作用。修改後的代碼如下: - final String message =「illegal value:」+ tf.getText(); \t javax.swing.SwingUtilities.invokeLater(新的Runnable(){ \t \t公共無效的run(){ \t \t \t JOptionPane.showMessageDialog(空,消息, \t \t \t \t \t 「非法值」,JOptionPane.ERROR_MESSAGE ); \t \t} \t}); \t return false; – dareurdream

+0

我明白了。什麼是您的操作系統和Java版本?我在OSX上使用Java 1.6,它工作得很好。 – gd1

回答

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的一個好方法如果你想彈出一個對話框詢問用戶他是否確定他想退出你的應用程序)。

+0

謝謝我會牢記這一點,並做出必要的修正。同時我找到了一個解決方案,我將在下面詳細介紹。你能否確認這是否是一個好的解決方案。謝謝 – dareurdream

+0

感謝這個解決方案完美運作。還有一個問題,我想基於點擊按鈕(稱爲Next)在我的卡布局上切換面板(動態確定並創建)。在那種情況下,我應該如何前進?謝謝 – dareurdream

+0

@dareurdream我沒有看到您的卡片佈局,但基本思路是調用CardLayout上的next()和previous()。如果您想要顯示特定組件,請將其添加到具有String約束的容器中,並使用方法show()和對應於您所需組件的String。查看更多[這裏](http://docs.oracle.com/javase/tutorial/uiswing/layout/card.html) –

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

GUI也適用於我,但真正的問題在於按鈕的外觀。如果我們觀察控制檯,那麼我們會發現系統輸出「Button Clicked」從不顯示。這意味着該按鈕從不被點擊。但仍按鈕看起來像它被按下...反正我已經找到了解決方案,並希望你的意見。謝謝 – dareurdream

+0

InputVerifier的實施是_invalid_ – kleopatra

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

雖然這種解決方案可能工作,你實際上是打破了[InputVerifier的合同] (http://docs.oracle.com/javase/7/docs/api/javax/swing/InputVerifier.html#verify(javax.swing.JComponent)):這個方法應該沒有副作用,所以它應該做的就是驗證組件輸入並返回true或false。此外,您不必「手動」修改按鈕的狀態(除非這是真正意想不到的效果) –

+0

@GuillaumePolet - 我同意您所說的每一個字,並且我會將我的解決方案修改爲上面提出的解決方案。 我想知道你對這個問題的看法 http://stackoverflow.com/questions/12541879/design-architecture-for-a-tool – dareurdream

+0

+ 1 - 手動吸出按鈕從其奇怪的越野車狀態是_can_在這裏完成的唯一的事情,如果mouseListener正常工作,那麼狀態是好的 – kleopatra

1

第一:InputVerifier的所有實現其打開驗證對話框()是無效。他們違反了他們的合同,API文檔:

這種方法應該沒有副作用。

與「應該」真正的含義「不得」。副作用的正確位置是shouldYieldFocus。

二:移動副作用(顯示信息對話框)正確地插入shouldYieldFocus不工作,以及...由於bug (THEY call it feature request ;-),這比十年老,in the top 10 RFEs

身爲hack-圍繞一個錯誤,@ dareurdrem的MouseListener的一樣好,因爲任何可行的黑客可以得到:-)

更新

打了一下,有不同的選項來破解周圍的錯誤後,這裏的另一個黑客 - 這是脆性像所有黑客一樣E(並且不生存LAF肘節,具有如果需要動態反覆重新安裝)

對於黑客鼠標行爲的基本方法是將鉤到由UI安裝在聽者:

  • 發現,原來
  • 實現自定義的監聽器,代表大多數事件直接以原
  • 表示按下事件要求首先關注:如果產生代表原始,如果不是什麼都不做

因爲焦點事件可能是異步的,所以最後一個項目符號稍微有點牽扯,所以我們必須調用檢查以便集中。反過來,調用需要發送一個釋放,以防無人反對。

另一個怪癖是爲rootPane的壓制作用(其defaultButton):它不受無條件調用doClick尊重任何inputVerifiers完成。

  • 找到的rootPane的壓制作用
  • 實現哪些檢查潛在的否決inputVerifier自定義操作:可以掛接到行動,按照相同的模式鉤住的MouseListener被攻破委託給原如果沒有,什麼也不做,否則

沿着這些線路的修改的例子:

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

其實真正PROBL em在於聚焦系統和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); 
       } 
      } 
     } 
} 

在該邏輯的膽是簡單的問題。按鈕:您是否已經關注所有者。如果不是:你可以(按鈕)可能增益焦點(記住 - shouldFieldInfo()調用當前焦點持有者調用shouldYieldFocus()並將返回假如果無效,ALWAYS)

這也有副作用彈出錯誤對話框!

此邏輯在焦點系統停止完成時停止Java庫processMouseEvent邏輯處理一半事件。

很明顯,您需要在所有不同的JComponents上執行這種類型的邏輯,這些JComponents對點擊執行操作。