2012-09-19 14 views
5

Sono nuovo di Swing e ho una situazione. Sto progettando un'applicazione che esegue il rendering dinamico dei componenti della GUI in base a un input di file xml (meta-dati). Ora la maggior parte dei miei JTextField ha InputVerifier impostato su di essi, a scopo di validazione. Il verificatore di input fa apparire JOptionPane ogni volta che c'è un input non valido.JButton rimane premuto quando il focus viene rubato da JOptionPane

Ora, se un utente immette dati non validi e avanza e fa clic su un pulsante nel Pannello, viene visualizzata una finestra di dialogo e l'utente deve rispondere. ma dopo ciò anche il pulsante non dipinge per rilasciare lo stato. Sembrava ancora premuto, ma in realtà non lo è. Poiché l'intero codice è piuttosto disordinato, inserisco lo scenario del problema nel seguente codice: -

Cosa devo fare in modo che JButton non sia visualizzato? Apprezzerei se la logica fosse anche spiegata.

Grazie in anticipo.

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

Per favore, prova a racchiudere la chiamata showMessageDialog in Runnable e assegnala a SwingUtilities :: invokeLater (Runnable) – gd1

+0

@ gd14 Ciao, ho provato l'approccio che hai affermato ma non sembra funzionare. Il codice modificato è il seguente: - string Messaggio finale = "valore non valido:" + tf.getText(); \t javax.swing.SwingUtilities.invokeLater (new Runnable() { \t \t public void run() { \t \t \t JOptionPane.showMessageDialog (null, messaggio, \t \t \t \t \t "valore non valido", JOptionPane.ERROR_MESSAGE \t \t} \t}); \t return false; – dareurdream

+0

Capisco. Qual è la tua versione del sistema operativo e di Java? Sono su OSX con Java 1.6 e funziona perfettamente. – gd1

risposta

3

Procedimento verify non è in realtà un buon posto per aprire una JOptionPane.

Ci sono diversi approcci che si potrebbe prendere in considerazione per risolvere il problema:

  1. si desidera che questa JOptionPane appaia ogni volta che il campo di testo perde la messa a fuoco e l'ingresso non è corretto: utilizzare un focusListener sul JTextField e agire appropriata eventi
  2. Si desidera che JOptionPane venga visualizzato ogni volta che si premono i pulsanti: utilizzare ActionListener per eseguirlo se l'input non è corretto.

Ecco un piccolo frammento di quest'ultima opzione:

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

Considera anche l'impostazione di default operazione di chiusura del JFrame invece di aggiungere un ascoltatore finestra (ma è un buon approccio per utilizzare un WindowListener se vuoi far apparire una finestra di dialogo chiedendo all'utente se è sicuro che vuole uscire dalla tua applicazione).

+0

Grazie vorrei tenerlo a mente e rendere necessario chnages . Nel frattempo ho trovato una soluzione che sto descrivendo di seguito. Potrebbe per favore confermare se questa è una buona soluzione. Grazie – dareurdream

+0

Grazie a questa soluzione funziona perfettamente. Ancora una domanda, vorrei cambiare pannello (determinato e creato dinamicamente) sul layout della mia carta in base al clic del pulsante (chiamato Avanti). In tal caso, come dovrei andare avanti? Grazie – dareurdream

+0

@dareurdream Non ho visto il tuo cardlayout, ma l'idea di base è di chiamare next() e previous() sul CardLayout. Se si desidera visualizzare un componente specifico, aggiungerlo al contenitore con un vincolo String e utilizzare il metodo show() con la stringa corrispondente al componente desiderato. Vedi di più [qui] (http://docs.oracle.com/javase/tutorial/uiswing/layout/card.html) –

1

Ho aggiunto una chiamata allo SwingUtilities per garantire che la GUI si trovi sul thread eventi e ho rimosso il riferimento a Frame.

La GUI funziona per me su 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

La GUI funziona anche per me, ma il vero problema è con l'aspetto del pulsante. Se osserviamo la console, scopriremmo che il sistema "Button Clicked" non viene mai visualizzato. Ciò significa che il pulsante non viene mai fatto clic. Ma il pulsante sembra ancora premuto ... Comunque ho trovato una soluzione e vorrei la tua opinione su questo. Grazie a – dareurdream

+0

l'implementazione di InputVerifier è _invalid_ – kleopatra

1

ho aggiunto un nuovo listener del mouse sul tasto come qui di seguito e la sua sembra funzionare bene per me ora, ma non sono sicuro se è un buon modo di correggere lo stato di selezione pulsanti.

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

Mentre questa soluzione potrebbe funzionare, stai effettivamente violando il contratto [InputVerifier] (http://docs.oracle.com/javase/7/docs/api/javax/swing/InputVerifier.html#verify(javax.swing.JComponent)): _Questo metodo non dovrebbe avere effetti collaterali_ quindi tutto quello che dovrebbe fare è verificare l'input del componente e restituire vero o falso. Inoltre, non dovresti modificare "manualmente" lo stato del pulsante (a meno che non sia davvero un effetto desiderato) –

+0

@GuillaumePolet - Sono d'accordo con ogni parola che hai detto e modificherei la mia soluzione alla soluzione suggerita da te sopra. Mi piacerebbe conoscere le tue opinioni anche su questo- http://stackoverflow.com/questions/12541879/design-architecture-for-a-tool – dareurdream

+0

+ 1 - succhiare manualmente il pulsante fuori dal suo buggy strano lo stato è l'unica cosa che può essere eseguita qui, mouseListener va bene se funziona – kleopatra

1

Primo: tutti implementazioni di InputVerifier che aprono la finestra di dialogo nella verifica() sono validi . Hanno violato il loro contratto, doc dell'API:

Questo metodo non dovrebbe avere effetti collaterali.

con il "dovrebbe" che realmente significa "non deve". Il posto giusto per gli effetti collaterali è shouldYieldFocus.

Secondo: spostare l'effetto collaterale (che mostra la finestra di messaggio) correttamente nella shouldYieldFocus non funziona così ... a causa di una bug (THEY call it feature request ;-), che è più vecchio di un decennio e in the top 10 RFEs

Essendo un hack- attorno a un bug, mouseListener @ di dareurdrem è buono come qualsiasi mod praticabile può ottenere :-)

Aggiornamento

Dopo aver giocato un po 'con diverse opzioni per hack in giro il bug, ecco un altro trucco - è come fragili come tutti gli hack ar e (e non sopravvive una ginocchiera LAF, deve essere reinstallato se è richiesto toggling dinamico)

Per hacking del comportamento del mouse l'approccio di base è quello di collegare in all'ascoltatore installato dal ui:

  • trovare l'originale
  • implementare un listener personalizzato che delega la maggior parte degli eventi direttamente al originale
  • per gli eventi pressati richiesta concentrarsi prima: se ceduto delegato originale, se non fare nulla

L'ultimo punto è leggermente più coinvolto perché gli eventi di messa a fuoco possono essere asincroni, quindi dobbiamo invocare il controllo per essere focalizzati. L'invocazione, a sua volta, richiede l'invio di una liberatoria nel caso nessuno si opponga.

Un'altra stranezza è l'azione urgente di rootPane (per il suo defaultButton): è fatta senza rispettare alcun inputVerifiers chiamando incondizionatamente doClick. Ciò può essere violato agganciando in azione, seguendo lo stesso schema aggancio nella mouseListener:

  • trovare l'azione premuto del rootPane
  • attuare un'azione personalizzata, che verifica la presenza di un inputVerifier potenzialmente veto: conferire al originale se no, non fare nulla altrimenti

l'esempio modificato in questo senso:

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

In realtà il vero probl em è nel modo in cui il sistema di messa a fuoco e gli ascoltatori awt interagiscono. Ci sono alcuni bug dichiarati in Java che gli sviluppatori stanno andando avanti e indietro su chi è responsabile. Il listener del mouse esegue: processMouseEvent e all'interno di quella logica, al FocusOwner corrente viene chiesto di fornire Focus. fallisce. Ma poiché metà dell'evento viene già elaborato, il pulsante diventa armato e il focus rimane sul campo.

Alla fine ho visto un commento dello sviluppatore: non lasciare che l'ascoltatore proceda se il campo non è autorizzato a perdere la concentrazione.

Per esempio: Definire un JTextField con le modifiche per consentire valori solo < 100. un messaggio si apre quando si perdere la concentrazione. Mi ha calpestato la mia base di classi JButton processMouseEvent (MouseEvent e) con il codice:

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

nelle viscere di questa logica è la domande semplici. Pulsante : sei già proprietario del focus. in caso contrario: puoi (pulsante) possibilmente mettere a fuoco GAIN (ricorda - shouldYieldFocus() viene chiamato sull'attuale focus holder all'interno della richiesta requestFocusInWindow() e restituirà false SEMPRE se non valido)

Questo ha anche l'effetto secondario di far apparire la tua finestra di errore!

Questa logica Arresta le librerie Java processMouseEvent logic dall'elaborazione di metà evento mentre il sistema di messa a fuoco lo interrompe dal completamento.

Ovviamente è necessario questo tipo di logica su tutti i diversi JComponent che eseguono un'azione con un clic.

Problemi correlati