Scoprire quale componente ha il fuoco

di il
6 risposte

Scoprire quale componente ha il fuoco

Buongiorno a tutti,
in una applicazione con varie finestre, ho implementato, tramite KeyEventDispatcher il meccanismo per spostarmi da una form ad un'altra.
Succede però che quando ritorno alla form precedente il fuoco non ce l'ha più alcun componente.
Per esempio se prima ce l'aveva una JTable in cui mi stavo spostando da una riga ad un'altra, ora non ce l'ha più, e non ce l'ha nessun altro componente, come campi di testo o altro.

Io vorrei, se possibile, usare l'evento formWindowLostFocus per salvarmi il componente che ha il fuoco per poi ripristinarlo all'interno dell'evento formWindowGainedFocus.
Non riesco però a capire come ottenere il componente che ha il fuoco.

Qualche dritta?

6 Risposte

  • Re: Scoprire quale componente ha il fuoco

    ZioCrick ha scritto:


    Io vorrei, se possibile, usare l'evento formWindowLostFocus per salvarmi il componente che ha il fuoco per poi ripristinarlo all'interno dell'evento formWindowGainedFocus.
    Non riesco però a capire come ottenere il componente che ha il fuoco.
    getFocusOwner() di KeyboardFocusManager (il "current" KeyboardFocusManager)

    E se a ciascuna finestra assegni una distinta implementazione di WindowFocusListener, puoi mantenere questa informazione del focus "owner" proprio lì nel listener in modo che poi sia usabile dal windowGainedFocus(). Se hai fatto una implementazione "condivisa", ovviamente no, va tenuta altrove.

    EDIT:

    ZioCrick ha scritto:


    Succede però che quando ritorno alla form precedente il fuoco non ce l'ha più alcun componente.
    Sicuro? Mi pare strano ... Su quale SO?
    Come hai implementato lo spostamento "custom" di finestra?
  • Re: Scoprire quale componente ha il fuoco

    andbin ha scritto:


    getFocusOwner() di KeyboardFocusManager (il "current" KeyboardFocusManager)
    Eh, qui siamo ancora nel mondo dei misteri.
    Come faccio a sapere il "current" KeyboardFocusManager?

    andbin ha scritto:


    E se a ciascuna finestra assegni una distinta implementazione di WindowFocusListener, puoi mantenere questa informazione del focus "owner" proprio lì nel listener in modo che poi sia usabile dal windowGainedFocus(). Se hai fatto una implementazione "condivisa", ovviamente no, va tenuta altrove.
    Allora, a livello di Form principale ho dichiarato:
    
      KeyEventDispatcher MioControlloTasti;
    
    e nel costruttore:
    
        MioControlloTasti = new KeyEventDispatcher() {
          @Override
          public boolean dispatchKeyEvent(KeyEvent e) {
            if (e.getID() == KeyEvent.KEY_RELEASED) {
              if (e.isControlDown()) {
                if (e.getKeyCode() == KeyEvent.VK_TAB) {
                  sCurForm = clWin.SwitchForm(sCurForm, e.isShiftDown()); 
                  return true;
                }
              }
            }
            return false;
          }
        };
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this.MioControlloTasti);
    
    dove clWin.SwitchForm è un mio metodo che scorre l'elenco delle <form attive e cerca la successiva o la precedente alla sCurForm e ne riassegna il valore.

    andbin ha scritto:


    Sicuro? Mi pare strano ... Su quale SO?
    Sia su Ubuntu che su Win 8.1

    andbin ha scritto:


    Come hai implementato lo spostamento "custom" di finestra?
    Non so se risponde il codice che ho riportato sopra.
  • Re: Scoprire quale componente ha il fuoco

    ZioCrick ha scritto:


    Come faccio a sapere il "current" KeyboardFocusManager?
    Esiste una sola istanza di KeyboardFocusManager definita "corrente". Di KeyboardFocusManager se ne può creare e impostare un altro (se ci sono le motivazioni giuste per farlo), ma appunto ne esiste solo uno per il framework. La istanza current la si tira fuori dal metodo statico.

    KeyboardFocusManager.getCurrentKeyboardFocusManager()

    ZioCrick ha scritto:


    
                  sCurForm = clWin.SwitchForm(sCurForm, e.isShiftDown()); 
    
    dove clWin.SwitchForm è un mio metodo che scorre l'elenco delle <form attive e cerca la successiva o la precedente alla sCurForm e ne riassegna il valore.
    Il punto è quello: COME attivi un form?
    Per dare programmaticamente l'attivazione ad un altro frame, bisogna considerare (se ben ricordo) diverse operazioni: va riportato in stato NORMAL (se era iconizzato), va riportato in "front" e infine va richiesto il focus.
  • Re: Scoprire quale componente ha il fuoco

    Mi sto perdendo... magari nel classico bicchier d'acqua... ma non ne vengo a capo.

    Allora nella SwitchForm dopo aver individuato la form a cui passare il fuoco, che è frames[ii], faccio:
    
    if (frames[ii].isDisplayable()) {
       frames[ii].setState(Frame.NORMAL);
       frames[ii].toFront();
    //   frames[ii].requestFocus();
       frames[ii].requestFocusInWindow();
    }
    
    la requestFocus è commentata perché anche provandola al posto della requestFocusInWindow il risultato non cambia.

    Invece per cercare di individuare il componente che ha il fuoco:

    Dichiaro:
    
    KeyboardFocusManager myFocusMgr;
    
    poi nel formWindowOpened:
    
    myFocusMgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
    System.out.println("myFocusMgr = " + myFocusMgr.toString());
    curComponent = myFocusMgr.getFocusOwner();
    System.out.println("Componente col fuoco = " + curComponent.getName());
    
    Qui il risultato della prima Println è:
    myFocusMgr = java.awt.DefaultKeyboardFocusManager@4c97a966
    ma non viene eseguita la seconda println perché mi da errore sulla formWindowGainedFocus:
    "Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException"

    e nella formWindowGainedFocus ho messo:
    
    curComponent.requestFocusInWindow();
    
    così come nella formWindowLostFocus:
    
    curComponent.requestFocusInWindow();
    
    Perché non mi esegue l'assegnamento della curComponent?
  • Re: Scoprire quale componente ha il fuoco

    Allora: ho fatto delle prove e i risultati sono questi, vedi il sorgente sotto.

    Alcune note:
    1) osserva dove ho messo il return true nel dispatchKeyEvent. L'ho messo solo quando è Control+TAB, NON Control+Tab released. Il motivo è semplice: il TAB (indipendentemente dal Ctrl) fa spostare di componente. Quindi Ctrl+TAB va "consumato" anche per il pressed. Se non lo consumi, il risultato è che sposta di componente PRIMA di fare lo switch di finestra.

    2) Nel activateWindow al fondo NON ho fatto né requestFocus() né requestFocusInWindow(). Il motivo è che sono proprio questi che danno il focus alla finestra ma tolgono il focus al componente interno. Questo è il risultato che mi avevi detto, cioè "sparisce" il focus dal componente (e non è il comportamento voluto né standard).

    Ora: è sufficiente fare toFront() ? Sì, anzi . La documentazione di toFront() è molto chiara:

    If this Window is visible, brings this Window to the front and may make it the focused Window.

    E c'è tutta la spiegazione sotto. Insomma, non è detto "universalmente" che portare "sù" una finestra voglia dire dargli il focus e attivarla. Questi purtroppo sono aspetti che dipendono dai vari S.O.
    Io ho fatto le prove su Windows 10 e su una VM Xubuntu e funziona correttamente come mi aspettavo. Non ho modo di provare su Mac o altri SO.
    import java.awt.Component;
    import java.awt.FlowLayout;
    import java.awt.Frame;
    import java.awt.KeyEventDispatcher;
    import java.awt.KeyboardFocusManager;
    import java.awt.Window;
    import java.awt.event.KeyEvent;
    import java.awt.event.WindowEvent;
    import java.awt.event.WindowFocusListener;
    
    import javax.swing.JDialog;
    import javax.swing.JFrame;
    import javax.swing.JTextField;
    import javax.swing.SwingUtilities;
    
    public class FrameProva extends JFrame {
        public FrameProva(String title, int x, int y) {
            super(title);
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setSize(400, 200);
            setLocation(x, y);
    
            setLayout(new FlowLayout());
            add(new JTextField("A", 6));
            add(new JTextField("B", 6));
            add(new JTextField("C", 6));
            add(new JTextField("D", 6));
            setVisible(true);
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> {
                WindowSwitcher.install();
    
                FrameProva frame1 = new FrameProva("Frame 1", 100, 100);
                FrameProva frame2 = new FrameProva("Frame 2", 150, 150);
                FrameProva frame3 = new FrameProva("Frame 3", 200, 200);
                FrameProva frame4 = new FrameProva("Frame 4", 250, 250);
    
                JDialog dialog4 = new JDialog(frame4, "Dialog di Frame 4", false);
                dialog4.setSize(300, 200);
                dialog4.setLocationRelativeTo(frame4);
                dialog4.setVisible(true);
            });
        }
    }
    
    
    class WindowSwitcher implements KeyEventDispatcher {
        private static WindowSwitcher instance;
    
        public static synchronized boolean install() {
            if (instance == null) {
                instance = new WindowSwitcher();
                KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(instance);
                return true;
            }
    
            return false;
        }
    
        // TODO uninstall
    
        @Override
        public boolean dispatchKeyEvent(KeyEvent e) {
            if (e.isControlDown()) {
                if (e.getKeyCode() == KeyEvent.VK_TAB) {
                    if (e.getID() == KeyEvent.KEY_RELEASED) {
                        if (e.isShiftDown()) {
                            WindowUtilities.activateNextWindow(false);
                        } else {
                            WindowUtilities.activateNextWindow(true);
                        }
                    }
    
                    return true;
                }
            }
    
            return false;
        }
    }
    
    
    class WindowUtilities {
        private WindowUtilities() {}
    
        public static boolean activateNextWindow(boolean forward) {
            Window focusedWindow = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
            Window[] windows = Window.getWindows();
    
            for (int i = 0; i < windows.length; i++) {
                if (windows[i] == focusedWindow) {
                    for (int k = 0; k < windows.length; k++) {
                        i = ((forward ? ++i : --i) + windows.length) % windows.length;
    
                        if (activateWindow(windows[i])) {
                            return true;
                        }
                    }
                }
            }
    
            return false;
        }
    
        public static boolean activateWindow(Window window) {
            if (window.isEnabled() && window.isVisible() && window.isFocusableWindow()) {
                if (window instanceof Frame) {
                    ((Frame) window).setState(Frame.NORMAL);
                }
    
                window.toFront();
                return true;
            }
    
            return false;
        }
    }

    Se nel activateWindow si volesse fare window.requestFocus() dopo il toFront() sarebbe anche (più) corretto ma così toglie il focus da un qualunque componente nella finestra.

    Ho trovato comunque un modo per sistemare anche questo scenario, basta usare un WindowFocusListener che sia "stateful" (che mantiene uno stato):
    class WindowComponentFocusKeeper implements WindowFocusListener {
        private Component lastFocusedComp;
    
        @Override
        public void windowGainedFocus(WindowEvent e) {
            if (lastFocusedComp != null) {
                lastFocusedComp.requestFocusInWindow();
            }
        }
    
        @Override
        public void windowLostFocus(WindowEvent e) {
            lastFocusedComp = e.getWindow().getMostRecentFocusOwner();
        }
    }
    Funziona grazie al quel getMostRecentFocusOwner() (di cui non avevo mai fatto molto caso...). Al lostFocus salva il componente che aveva il focus e al gainedFocus gli ridà il focus.
    L'unica questione/difetto è che bisogna registrare un distinto (perché stateful!) WindowComponentFocusKeeper per ciascuna delle tue finestre che crei e di cui hai il controllo.


    Alternativa:

    Invece di usare un WindowFocusListener, ho trovato un'altra possibilità che è più semplice:
        public static boolean activateWindow(Window window) {
            if (window.isEnabled() && window.isVisible() && window.isFocusableWindow()) {
                if (window instanceof Frame) {
                    ((Frame) window).setState(Frame.NORMAL);
                }
    
                window.toFront();
                Component lastFocusedComp = window.getMostRecentFocusOwner();
                window.requestFocus();
    
                if (lastFocusedComp != null) {
                    SwingUtilities.invokeLater(() -> {
                        lastFocusedComp.requestFocusInWindow();
                    });
                }
    
                return true;
            }
    
            return false;
        }
    Si invoca window.requestFocus() però attenzione, questa è solo la richiesta, la applicazione della richiesta avverrà dopo la terminazione di quel evento. Quindi è necessario schedulare più avanti (con il solito invokeLater) il requestFocusInWindow del componente "last focused".



    P.S. nota come ho anche separato i vari concetti.
  • Re: Scoprire quale componente ha il fuoco

    Ciao Andrea.
    Grazie per i preziosi consigli.
    Al momento ho applicato quello sul controllo del TAB e sull'uso del requestFocus() e requestFocusInWindow() e sembra funzionare, ovvero quando ritorno su una form precedente mantiene il fuoco sul componente che ce l'aveva prima del cambio di form.
    Siccome sono in ferie ho solo il portatile con Win8.1 e qui funziona.

    Voglio però implementare anche la memorizzazione del componente con il fuoco perché mi serve per una situazione particolare, ovvero parto da una form A con un elenco principale in una JTable J1 e in cui il fuoco è sul componente X (che non è la JTable).
    Poi con lo switch vado su un'altra form B che contiene un'altra JTable J2 con sottoinsieme dell'elenco J1 della form A e da cui scegliendo un elemento di J2 me lo imposta automaticamente anche nella J1.
    Questo però mi sposta il fuoco sulla J1, mentre io vorrei mantenere il fuoco sul componente X.

    Aggiornamento:
    Ho scoperto che la questione del TAB funziona nell'esempio che hai creato ma non nei miei programmi.
    Ovvero ogni vota che mi sposto di finestra il fuoco nella finestra che sto lasciando si sposta sul campo successivo.

    All'inizio non l'avevo notato perché ho provato su delle form che avevano tutti i campi di testo disabilitati e solo le liste e/o tabelle abilitate, e stranamente questo spostamento di focus non succede.
    Poi l'ho provato con applicazioni che hanno campi, pulsanti e liste abilitati contemporaneamente e il focus si sposta da un componente all'altro ogni volta che mi sposto di finestra, e anche nelle liste!
    E non capisco il perché! Mi sembrano avere le proprietà impostate nello stesso modo.

    Anzi addirittura nella form in cui funziona, le liste e tabelle le imposto per reagire alla navigazione con il TAB, poiché in passato ho scoperto che altrimenti quando prendevano il fuoco, poi non riuscivo ad uscirne tramite il tab o il Ctrl TAB.
      
      public void SetTab(Component comp) {
        Set<KeyStroke> 
        strokes = new HashSet<KeyStroke>(Arrays.asList(KeyStroke.getKeyStroke("pressed TAB")));
        comp.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, strokes);
        strokes = new HashSet<KeyStroke>(Arrays.asList(KeyStroke.getKeyStroke("shift pressed TAB")));
        comp.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, strokes);
      }
    
    quindi in teroria dovrebbe funzionare al contrario.

    Ho provato i due metodi che hai suggerito per ricavare il componente con il fuoco, ma non cambia nulla.
    Sono disperato.
    Purtroppo postare i codici delle form è un macello perché sono troppo piene di roba.

    Mi è venuto il dubbio che nell'esempio che hai creato, non c'é traccia di impostazioni sulla gestione del tab e del fuoco, mentre in quelle che uso io, fatte con Netbeans ci sono tutte le proprietà, ma non sono riuscito a capire se e quali influiscono su questo comportamento.
Devi accedere o registrarti per scrivere nel forum
6 risposte