View Full Version : [JAVA] Corretto posizionamento Listener GUI
Hybr1d97
16-01-2015, 22:50
Salve, ho iniziato da poco le GUI in Java e mi chiedevo dove sarebbe meglio implementare i Listener. Ad esempio, se ho una classe MyFrame, che sarebbe quella che gestisce la GUI, così fatta:
public class MyFrame extends JFrame{
private JLabel label;
...
public MyFrame(){
...
}
}
E voglio che ogni volta che muovo il mouse su label vengano visualizzate, sempre su label, le coordinate del mouse, e voglio usare il Listener MouseMotionListener, qual è il miglior modo per implementarlo permettendogli di agire sul campo label dell'oggetto MyFrame?
Che io sappia ci sono tre opzioni...
1. Implementare l'interfaccia MouseMotionListener direttamente nella firma di MyFrame e poi avere tanti metodi in override nella stessa classe:
public class MyFrame extends JFrame implements MouseMotionListener{
...
}
Il problema è che poi, implementando tanti listener, si creerebbe un disordine assurdo e non si saprebbe più a quale interfaccia appartengono i vari metodi.
2. Implementare una inner class in MyFrame per ogni listener:
public class MyFrame extends JFrame{
private class ImplMouseMotionListener implements MouseMotionListener{
...
}
}
risolvendo quindi il problema del "miscuglio" di metodi in override. Tuttavia, c'è ancora il problema che, concentrando tanti listener in un solo file sorgente, il numero delle righe aumenterebbe tanto e sarebbe comunque disordinato.
3. Inner class anonime: come il punto 2, ma peggio.
4. Quello che mi sembra migliore, ovvero implementare una classe per ogni listener in un altro file sorgente ma nello stesso package, in questo modo:
public class ImplMouseMotionListener implements MouseMotionListener{
final MyFrame outer;
ImplMouseListener(MyFrame outer){
this.outer = outer;
}
...
}
In questo modo, i metodi in override avranno comunque accesso ai campi della classe, riferita da outer. Il problema è che per fare ciò i campi su cui si deve agire devono essere necessariamente public, o al massimo protected, non consentendo l'information hiding. Tuttavia, è il modo più "pulito" tra i tre.
Che ne pensate voi? Qual è, secondo voi, il "quinto" metodo più corretto?
[Kendall]
17-01-2015, 00:47
Salve, ho iniziato da poco le GUI in Java e mi chiedevo dove sarebbe meglio implementare i Listener.
Che io sappia ci sono tre opzioni...
1. Implementare l'interfaccia MouseMotionListener direttamente nella firma di MyFrame e poi avere tanti metodi in override nella stessa classe:
public class MyFrame extends JFrame implements MouseMotionListener{
...
}
Il problema è che poi, implementando tanti listener, si creerebbe un disordine assurdo e non si saprebbe più a quale interfaccia appartengono i vari metodi.
In realtà i metodi delle varie interfacce sono abbastanza autoesplicativi, quindi il problema è relativo.
Dato gli strumenti che ti fornisce il Java comunque condivido il fatto che non sia questo il metodo migliore per gestire i listener, e non per una questione di ordine quanto per il fatto che implementando l'interfaccia di un listener direttamente nella classe "root", nel caso in cui lo stesso listener sia usato per più componenti (per esempio nel caso in cui tu voglia tracciare il movimento del mouse su più componenti a schermo), nel metodo che definisci nel listener dovrai ogni volta fare il check per verificare quale sia il sender dell'evento, ed eseguire il codice corretto a seconda del caso.
2. Implementare una inner class in MyFrame per ogni listener:
public class MyFrame extends JFrame{
private class ImplMouseMotionListener implements MouseMotionListener{
...
}
}
risolvendo quindi il problema del "miscuglio" di metodi in override. Tuttavia, c'è ancora il problema che, concentrando tanti listener in un solo file sorgente, il numero delle righe aumenterebbe tanto e sarebbe comunque disordinato.
Questa è la tecnica che preferisco, in quanto ti permette di creare delle classi apposite per ogni evento che vuoi monitorare ed essendo una inner class non statica ti permette di accedere ai parametri privati dell'istanza della classe madre (cosa spesso fondamentale).
La questione dell'ordine è solo un fatto di abitudine.
L'ordine in un listato lo fai costruendoti una tua struttura logica nel disporre i vari pezzi e usando intelligentemente i commenti.
3. Inner class anonime: come il punto 2, ma peggio.
Questo in realtà è un metodo largamente usato, ed ha il vantaggio/svantaggio di permettere una definizione "in loco".
Anche qui: questione di abitudini e preferenze personali.
Con Java 8 e le espressioni lambda è diventato tra l'altro ancora più immediato e veloce il definire un listener in questo modo.
Es°
Classe Anonima
button.addClickListener(new Button.ClickListener() {
@Override
public void buttonClick(Button.ClickEvent event) {
container.addComponent(new Label("Button clicked"));
}
});
Espressione Lambda
button.addClickListener(e -> container.addComponent(new Label("Button clicked")));
4. Quello che mi sembra migliore, ovvero implementare una classe per ogni listener in un altro file sorgente ma nello stesso package, in questo modo:
Questa invece la reputo proprio la peggiore.
Suddividere i listener in più file comporta che:
1) In un qualsiasi applicativo che non sia banale ti ritroverai con una marea di file senza alcun riferimento alla classe nella quale sono utilizzati. La cosa diventa assolutamente impossibile e macchinosa in ottica di manutenzione del codice.
2) Le classe così definite non potranno accedere ai parametri privati delle classi dove andrai ad instanziarle, in quanto si tratterà di classi "esterne".
E, che io sappia, l'unica maniera per ottenere questo è tramite la definizione di una classe interna non statica.
Quindi, per riassumere, il metodo che preferisco è il 2, ma come già detto non c'è un metodo in assoluto migliore di un altro (dipende da diversi fattori e in parte anche dalla preferenza personale). O meglio, sono tutti validi tranne l'ultimo ;)
Hybr1d97
17-01-2015, 01:02
;42028606']Quindi, per riassumere, il metodo che preferisco è il 2, ma come già detto non c'è un metodo in assoluto migliore di un altro (dipende da diversi fattori e in parte anche dalla preferenza personale). O meglio, sono tutti validi tranne l'ultimo ;)
Sì hai ragione, infatti reputavo l'ultimo migliore solo dal punto di vista dell'ordine, ma credo dipenda dal fatto che sono abituato a programmi di poche centinaia di righe, quindi come dici tu basta abituarsi. Quindi la migliore sembra esser la due! Ne approfitto per chiederti un chiarimento: registrare lo stesso listener per più componenti per poi fare il check è sconsigliato quindi? Se sì, perchè, e come dovrei fare invece? Perchè a questo punto sembra quasi più conveniente il 3, implementarne uno per ogni componente..
[Kendall]
17-01-2015, 10:56
Sì hai ragione, infatti reputavo l'ultimo migliore solo dal punto di vista dell'ordine, ma credo dipenda dal fatto che sono abituato a programmi di poche centinaia di righe, quindi come dici tu basta abituarsi. Quindi la migliore sembra esser la due! Ne approfitto per chiederti un chiarimento: registrare lo stesso listener per più componenti per poi fare il check è sconsigliato quindi? Se sì, perchè, e come dovrei fare invece? Perchè a questo punto sembra quasi più conveniente il 3, implementarne uno per ogni componente..
La 2 e la 3 sono soluzioni analoghe, entrambe creano delle implementazioni di uno specifico Listener.
Solo che nella 3 tali implementazioni sono anonime e definite in loco all'assegnazione del listener, mentre nella 2 le definisci in maniera a se stante.
La soluzione 2 perde di senso se utilizzi sempre la stessa implementazione per più componenti (perché a quel punto tanto vale andare di soluzione 1 che è più efficiente e più compatta).
La soluzione 2 torna utile proprio per creare dei Listener specifici per ogni evento che vuoi monitorare, quindi se per esempio vuoi fare il tracking della posizione del mouse all'interno di due componenti visuali, creerai due distinti implementazioni e non andrai ad utilizzare la stessa (perché come detto a quel punto si tornerebbe al punto 1, e dovresti all'interno dell'implementazione fare sempre il check su quale sia il componente che sta generando l'evento ed agire di conseguenza... sempre che l'azione da intraprendere non sia identica).
Non so se sono riuscito a spiegarmi correttamente, nel caso chiedi pure.
Hybr1d97
17-01-2015, 12:22
Sinceramente non ti sto capendo.. Quello che dici tu mi sembra un po' pantagruelico sotto il punto di vista dell'implementazione. Facciamo un esempio banale: una calcolatrice semplice con 10+4 tasti. Quello che dici tu è di implementare un listener per ogni tasto, facendo ridondare il codice di brutto. Una classe Button0Listener, una Button1Listener e così via, ognuna con i suoi metodi in overdrive. (In questo caso i bottoni da 0 a 9 hanno in realtà la stessa funzione, ma ipotizziamo che siano così diversi da rendere obbligatoria la separazione del codice di gestione). Quello che dico io, invece, è di farmi un solo listener per tutti i tasti, con all'interno i vari check, e questa mi sembra la soluzione più "pulita". Non riesco a capire i vantaggi della tua..
E la 2 resta sempre meglio della 1, dato che con la 2 ho tutti gli actionListener da una parte, tutti i mouseListener dall'altra e così via. Con la 1, invece, li ho tutti mischiati.
[Kendall]
17-01-2015, 13:40
Sinceramente non ti sto capendo.. Quello che dici tu mi sembra un po' pantagruelico sotto il punto di vista dell'implementazione. Facciamo un esempio banale: una calcolatrice semplice con 10+4 tasti. Quello che dici tu è di implementare un listener per ogni tasto, facendo ridondare il codice di brutto. Una classe Button0Listener, una Button1Listener e così via, ognuna con i suoi metodi in overdrive. (In questo caso i bottoni da 0 a 9 hanno in realtà la stessa funzione, ma ipotizziamo che siano così diversi da rendere obbligatoria la separazione del codice di gestione). Quello che dico io, invece, è di farmi un solo listener per tutti i tasti, con all'interno i vari check, e questa mi sembra la soluzione più "pulita". Non riesco a capire i vantaggi della tua..
Ho ben specificato "sempre che l'azione non sia identica". Aggiungo (lo davo per scontato ma in effetti la frase era messa giù male) "sempre che l'azione non sia riconducibile ad un "pattern" comune).
Lì sta a te identificare quando un listener sia idoneo ad essere agganciato all'evento di uno o più componenti.
Ti faccio due esempi:
1) Se hai due pulsanti che si occupano di incrementare o decrementare un contatore, dovrai agganciare un listener per entrambi per poter reagire alla pressione dei tasti. L'azione che la pressione comporta è però "comune" a livello funzionale, pertanto è corretto concettualmente volerla gestire con un unica implementazione.
2) Mettiamo invece il caso che tu abbia un pulsante che ti apre una nuova finestra contestuale, ed un altro pulsante che invece, che ne so, esegue il refresh dei dati di un determinato contenuto. Qui le due azioni sono completamente differenti, quindi è concettualmente sbagliato voler gestire i vari casi all'interno di un unico listener.
Questi esempi ti fanno capire come non esista una regola unica, e che sta a te valutare caso per caso se sia il caso di creare un nuovo listener od utilizzare una implentazione già fatta (casomai adattandola).
Dare una corretta organizzazione logica al tuo codice ti salverà da molti mal di testa, e renderà più semplice il refactoring.
E la 2 resta sempre meglio della 1, dato che con la 2 ho tutti gli actionListener da una parte, tutti i mouseListener dall'altra e così via. Con la 1, invece, li ho tutti mischiati.
Come ho detto nel mio primo intervento è generalmente preferibile, si... Ma anche in questo caso è in parte una questione di preferenze personali del programmatore.
Pensa che per molti è la soluzione 3 quella preferita: e in certe circostanze, e sottolineo "in certe", condivido.
Prenditi un libro di android e vedrai che quando si tratta di listener per particolari eventi nel 90% dei casi vengono implementati tramite classi anonime (per lo meno nei libri che ho letto io).
Ripeto, a me non piace molto come metodologia, ma non nascondo che in diverse circostanze l'ho utilizzata, in quanto mi sembrava la cosa più semplice e "pulita" da fare per quel particolare caso.
Hybr1d97
17-01-2015, 13:49
;42030021']Ho ben specificato "sempre che l'azione non sia identica". Aggiungo (lo davo per scontato ma in effetti la frase era messa giù male) "sempre che l'azione non sia riconducibile ad un "pattern" comune).
Lì sta a te identificare quando un listener sia idoneo ad essere agganciato all'evento di uno o più componenti.
Ti faccio due esempi:
1) Se hai due pulsanti che si occupano di incrementare o decrementare un contatore, dovrai agganciare un listener per entrambi per poter reagire alla pressione dei tasti. L'azione che la pressione comporta è però "comune" a livello funzionale, pertanto è corretto concettualmente volerla gestire con un unica implementazione.
2) Mettiamo invece il caso che tu abbia un pulsante che ti apre una nuova finestra contestuale, ed un altro pulsante che invece, che ne so, esegue il refresh dei dati di un determinato contenuto. Qui le due azioni sono completamente differenti, quindi è concettualmente sbagliarlo voler gestire i vari casi all'interno di un unico listener.
Questi esempi ti fanno capire come non esista una regola unica, e che sta a te valutare caso per caso se sia il caso di creare un nuovo listener od utilizzare una implentazione già fatta (casomai adattandola).
Io infatti mi riferivo proprio al secondo caso (l'esempio della calcolatrice in effetti non era idoneo). Quel che io dico è che, avendo 200 pulsanti che fanno tutti cose diverse ma che, alla fine, mi sollevano sempre un ActionEvent, mi sembra più ragionevole fare un check con getSource() per vedere chi sia il sender e da lì gestire i vari casi. Non è meglio un solo listener con 200 selezioni, piuttosto che 200 listener diversi che riconducono sempre ad un ActionEvent? Perchè gli esempi che trovo sui libri e sul web sono in gran parte con la selezione.
[Kendall]
17-01-2015, 14:29
Io infatti mi riferivo proprio al secondo caso (l'esempio della calcolatrice in effetti non era idoneo). Quel che io dico è che, avendo 200 pulsanti che fanno tutti cose diverse ma che, alla fine, mi sollevano sempre un ActionEvent, mi sembra più ragionevole fare un check con getSource() per vedere chi sia il sender e da lì gestire i vari casi. Non è meglio un solo listener con 200 selezioni, piuttosto che 200 listener diversi che riconducono sempre ad un ActionEvent? Perchè gli esempi che trovo sui libri e sul web sono in gran parte con la selezione.
Nel caso tu abbia una moltitudine di oggetti per la quale rispondere allo stesso tipo di evento (fermo restando che avere 200 pulsanti in uno stesso frame non è un caso comune, ma neppure 100, e neppure 50) creare un listener diverso per ogni pulsante sarebbe poco efficiente, visto che devi instanziare parecchi nuovi oggetti.
In tal caso l'unico listener è una soluzione sicuramente preferibile, ma di sicuro non schiaffandoci dentro tutto il codice per ogni singolo caso. Avresti un metodo di centinaia di righe praticamente impossibile da leggere. In quel caso (anche se non mi è mai capitato sinceramente) una soluzione potrebbe essere quella di dividere le azioni dei vari pulsanti in singoli metodi privati dentro l'implementazione del listener, e poi richiamare a seconda del caso il metodo corretto dentro l'override del metodo di interfaccia.
Però vabbè, qui si viaggia con le ipotesi e non smetterò di ripeterlo: il linguaggio dà molti strumenti, sta ad ognuno identificare quale sia quello più idoneo ad un caso o ad un altro, e non ci può essere una soluzione che va bene sempre.
Io personalmente di base uso il metodo 2, e creo una listener diverso per ogni evento... Poi, in base alla situazione specifica improvviso.
Così, come sidestory:
Pensa che su Cocoa (iOS e OSX) la metodologia standard per implementare i delegati (pensali un po' come le interfacce dei listener) è proprio il metodo 1.
Hybr1d97
17-01-2015, 14:39
;42030260']Nel caso tu abbia una moltitudine di oggetti per la quale rispondere allo stesso tipo di evento (fermo restando che avere 200 pulsanti in uno stesso frame non è un caso comune, ma neppure 100, e neppure 50) creare un listener diverso per ogni pulsante sarebbe poco efficiente, visto che devi instanziare parecchi nuovi oggetti.
In tal caso l'unico listener è una soluzione sicuramente preferibile, ma di sicuro non schiaffandoci dentro tutto il codice per ogni singolo caso. Avresti un metodo di centinaia di righe praticamente impossibile da leggere. In quel caso (anche se non mi è mai capitato sinceramente) una soluzione potrebbe essere quella di dividere le azioni dei vari pulsanti in singoli metodi privati dentro l'implementazione del listener, e poi richiamare a seconda del caso il metodo corretto dentro l'override del metodo di interfaccia.
Però vabbè, qui si viaggia con le ipotesi e non smetterò di ripeterlo: il linguaggio dà molti strumenti, sta ad ognuno identificare quale sia quello più idoneo ad un caso o ad un altro, e non ci può essere una soluzione che va bene sempre.
Io personalmente di base uso il metodo 2, e creo una listener diverso per ogni evento... Poi, in base alla situazione specifica improvviso.
Così, come sidestory:
Pensa che su Cocoa (iOS e OSX) la metodologia standard per implementare i delegati (pensali un po' come le interfacce dei listener) è proprio il metodo 1.
Capito, sei stato chiarissimo :) In sostanza, qualunque sia il caso, è da evitare assolutamente il 4 :D Poi vabbè, so che 200 pulsanti in un singolo frame sono poco probabili, era solo per sapere cosa è meglio fare nei casi limite. Grazie per le spiegazioni :)
[Kendall]
17-01-2015, 14:44
Comunque questo è solo il mio modo di vedere.
Se alcuni Java-Ninja del forum vogliono dire la loro sarei curioso anche io di vedere eventuali diversi approcci alla questione.
Hybr1d97
17-01-2015, 16:46
Ne approfitto per togliermi un dubbio (tanto non è off-topic).
Come faccio a riferirmi ad una classe outer? Mi spiego:
ipotizziamo di avere una classe che estende JFrame. Ci metto poi una JMenuBar "menuBar", in cui ci metto un JMenu "help", in cui ci metto infine un JMenuItem "about". Voglio che, cliccando su about, appaia una dialog, e voglio usare il metodo delle classi anonime. Quindi:
public class MyFrame extends JFrame{
private JMenuBar menuBar;
private JMenu help;
private JMenuItem about;
public MyFrame(){
super("MyFrame");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
menuBar = new JMenuBar();
help = new JMenu("Help");
about = new JMenuItem("About...");
about.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
JOptionPane.showMessageDialog(this," ... ")
}
});
...
}
}
Ovviamente non funzionerà, perchè quel "this" si riferirà all'istanza di ActionListener, non a quella di MyFrame. Come posso fare, quindi, per riuscire a riferirmi alla classe "esterna"? C'è qualche keyword apposita?
[Kendall]
17-01-2015, 17:16
Ne approfitto per togliermi un dubbio (tanto non è off-topic).
Come faccio a riferirmi ad una classe outer? Mi spiego:
ipotizziamo di avere una classe che estende JFrame. Ci metto poi una JMenuBar "menuBar", in cui ci metto un JMenu "help", in cui ci metto infine un JMenuItem "about". Voglio che, cliccando su about, appaia una dialog, e voglio usare il metodo delle classi anonime. Quindi:
public class MyFrame extends JFrame{
private JMenuBar menuBar;
private JMenu help;
private JMenuItem about;
public MyFrame(){
super("MyFrame");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
menuBar = new JMenuBar();
help = new JMenu("Help");
about = new JMenuItem("About...");
about.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
JOptionPane.showMessageDialog(this," ... ")
}
});
...
}
}
Ovviamente non funzionerà, perchè quel "this" si riferirà all'istanza di ActionListener, non a quella di MyFrame. Come posso fare, quindi, per riuscire a riferirmi alla classe "esterna"? C'è qualche keyword apposita?
Devi utilizzare il nome della classe madre in questa maniera:
MyFrame.this
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.