Forum >> Programmazione Python >> GUI >> [tkinter] Passaggio di eventi tra classi: consigli?

Pagina: 1

I miei saluti

In fase di progettazione di una applicazione ho sub-classato tkinter.Entry in maniera che gestisca direttamente gli eventi di focus e tastiera, il codice costituente il sub-classamento è il seguente :

class ActivableEntry(tk.Entry):
    '''Una Enty inibibile alla scrittura senza disabilitazione.'''
    key_none = ['Alt_L', 'Alt_R', 'BackSpace', 'Caps Lock',
                'Control_L', 'Control_R', 'Delete', 'Down', 'End',
                'Escape', 'Execute', 'F1', 'F2', 'Fi', 'F12', 'Home',
                'Insert', 'Left', 'Linefeed', 'KP Add', 'KP Begin',
                'KP Delete', 'KP Divide', 'KP Down', 'KP End', 'KP Enter',
                'KP Home', 'KP Insert', 'KP Left', 'KP Next', 'KP Prior',
                'KP Right', 'KP Up', 'Next', 'Num Lock', 'Pause', 'Print',
                'Prior', 'Return', 'Right', 'Scroll Lock', 'Shift_L',
                'Shift_R', 'Tab', 'Up']
    def __init__(self, parent: callable, active: bool=False, *args, **kwargs) -> None:
        super().__init__(parent, *args, **kwargs)
        self.parent = parent
        self._active = active
        self._bg = self['background']
        self.bind('<KeyPress>', self._on_key)
        self.bind('<FocusIn>', self._on_focus)
        self.bind('<FocusOut>', self._out_focus)


    def _on_key(self, evt: callable) -> None:
        if not self._active and not evt.keysym in self.key_none:
            return 'break'
    
    def _on_focus(self, evt: callable) -> None:
        self.configure(bg='#ffffc0')
    
    def _out_focus(self, evt: callable) -> None:
        self.configure(bg=self._bg)
    
    def activate(self) -> None:
        self._active = True
    
    def disable(self) -> None:
        self._active = False
ed è pensato tanto per "colorare" una entry che ha ottenuto il focus quanto per impedire modifiche al contenuto (a meno che non sia stata esplicitamente "attivata") senza disabilitarla.

Nella costruzione di una finestra utilizzante oggetti di detto sub-classamento, per ragioni di navigazione nella GUI, mi trovo a gestire nuovamente il focus e la tastiera nella finestra.
Per sopperire alla gestione limitato alla sola finestra che si avrebbe ho provveduto, nelle funzioni di callback della finestra, ad invocare i callback degli oggetti instanziati passando l'oggetto "evento" della finestra quale parametro per l'oggetto ... esempi:

    def _on_keytitle(self, evt: callable) -> None:
        if evt.keysym in ['Return', 'KP Enter']:
            if self.state == 'def_logo':
                self.e_den.focus_set()
            else:
                self.e_func.focus_set()
        else:
            w = evt.widget
            if w._on_key(evt) == 'break':
                return 'break'
...
    def _on_logofocus(self, evt: callable) -> None:
        w = evt.widget
        w._on_focus(evt)
        self.selected = 'logo_element'

Ovviamente, gli eventi nella finestra sono necessari per stabilire alcune disponibilità nella finestra stessa.

... il metodo adottato funziona ma mi sembra poco "elegante", è un paio di giorni che cerco in giro come fare di meglio ma non mi è riuscito di trovare niente di opportuno tra docs e codici ... avreste qualche suggerimento in merito?

Grazie della attenzione

Fatti non foste a viver come bruti...
Nel corso dei tentativi per la soluzione delle problematiche indicate del precedente post mi son reso conto che in realtà si trattava di un falso problema e che l'impressione di "brutto" scaturiva dal fatto che conoscevo già le risposte opportune ma non le avevo adottate; la soluzione si basa su due step:

1° - per la gestione del focus - è sufficiente sfruttare la progressione di trasmissione dell'evento "widget => Frame => window", intercettando nel frame contenitore di un oggetto "ActivableEntry" per regolare li le operazioni necessarie a livello "finestra", tale gestione non interferirà con la gestione del focus a livello del widget sub-classato;

2° - per la gestione della tastiera - mi serviva una doppia intercettazione della tastiera su oggetti di classe "ActivableEntry" e mi ero fissato a definire tale gestione con l'evento "<KeyPress>" ... in realtà sono disponibili anche gli eventi di tastiera "<KeyRelease>" e "<Key>", concatenati al precedente evento e che non interferiscono con esso, è sufficiente gestire una diversa fase della catena di eventi per risolvere.

... il brutto è che conoscevo tali faccende ma non mi erano venute in mente, cosa da cui scaturiva la brutta sensazione sul codice che stavo sviluppando.




Nel caso qualcuno fosse interessato, segue il codice del test di verifica effettuato, in esso un oggetto di classe "ActivableEntry" che riceve il focus se disabilitato avrà colorazione grigio-azzurra e non potrà esservi inserito testo, se attivato avrà colore giallo chiaro ed il test potrà essere inserito.

import tkinter as tk


class ActivableEntry(tk.Entry):
    '''Una Enty inibibile alla scrittura senza disabilitazione.'''
    key_none = ['Alt_L', 'Alt_R', 'Caps_Lock', 'Control_L', 'Control_R', 'Down', 'End',
                'Escape', 'Execute', 'F1', 'F2', 'Fi', 'F12', 'Home', 'Insert', 'Left',
                'Linefeed', 'KP_Add', 'KP_Begin', 'KP_Divide', 'KP_Down', 'KP_End',
                'KP_Enter', 'KP_Home', 'KP_Insert', 'KP_Left', 'KP_Next', 'KP_Prior',
                'KP_Right', 'KP_Up', 'Next', 'Num_Lock', 'Pause', 'Print', 'Prior',
                'Return', 'Right', 'Scroll_Lock', 'Shift_L', 'Shift_R', 'Tab', 'Up']
    def __init__(self, parent: callable, active: bool=False, *args, **kwargs) -> None:
        super().__init__(parent, *args, **kwargs)
        self.parent = parent
        self._active = active
        self._bg = self['background']
        self.bind('<KeyPress>', self._on_key)
        self.bind('<FocusIn>', self._on_focus)
        self.bind('<FocusOut>', self._out_focus)


    def _on_key(self, evt: callable) -> None:
        if not self._active and not evt.keysym in self.key_none:
            return 'break'
    
    def _on_focus(self, evt: callable) -> None:
        color = '#ffffc0' if self._active else '#bfe5f1'
        self.configure(bg=color)
    
    def _out_focus(self, evt: callable) -> None:
        self.configure(bg=self._bg)
    
    def activate(self) -> None:
        self._active = True
    
    def disable(self) -> None:
        self._active = False


class Win(tk.Tk):
    def __init__(self) -> None:
        super().__init__()
        self.resizable(width=True, height=False)
        self
        self.dida = tk.Label(self, justify='left')
        self.dida.grid(row=0, column=0, padx=5, pady=5, sticky='w')
        self.pnl = []
        self.ae = []
        for i in range(3):
            pnl = tk.Frame(self)
            pnl.grid(row=i+1, column=0, padx=5, pady=5, sticky='ew')
            ae = ActivableEntry(pnl)
            ae.disable()
            ae.grid(row=0, column=0, padx=5, pady=5, sticky='ew')
            pnl.grid_columnconfigure(0, weight=1)
            pnl.bind('<FocusIn>', self._on_focus)
            pnl.bind('<FocusOut>', self._off_focus)
            ae.bind('<Key>', self._on_key)
            self.pnl.append(pnl)
            self.ae.append(ae)
        pn_cmd = tk.Frame(self)
        pn_cmd.grid(row=4, column=0, padx=5, pady=5, sticky='ew')
        self.bt = []
        for i in range(3):
            bt = tk.Button(pn_cmd, text=f'Attiva {i+1}')
            bt.grid(row=0, column=i, padx=5, pady=5, sticky='nsew')
            bt.bind('<Button>', self._on_button)
            self.bt.append(bt)
            pn_cmd.grid_columnconfigure(i, weight=1, uniform='bt')
        bt_close = tk.Button(pn_cmd, text='Esci', command=self.destroy)
        bt_close.grid(row=0, column=3, padx=5, pady=5, sticky='nsew')
        pn_cmd.grid_columnconfigure(3, weight=1, uniform='bt')

        self.grid_columnconfigure(0, weight=1)

    def _on_focus(self, evt: callable) -> None:
        w = evt.widget
        idx = self.pnl.index(w)
        self.dida.configure(text=f'Focus su pannello {idx+1}')

    def _off_focus(self, evt: callable) -> None:
        self.dida.configure(text='')

    def _on_key(self, evt) -> None:
        w = evt.widget
        if evt.keysym in ['Return', 'KP_Enter']:
            idx = self.ae.index(w)
            if idx < len(self.ae) - 1:
                self.ae[idx+1].focus_set()
            else:
                self.ae[0].focus_set()

    def _on_button(self, evt: callable) -> None:
        w = evt.widget
        idx = self.bt.index(w)
        if self.ae[idx]._active:
            self.ae[idx].disable()
            self.dida.configure(text=f'Entry {idx+1} disattivata')
            w.configure(text=f'Attiva {idx+1}')
        else:
            self.ae[idx].activate()
            self.dida.configure(text=f'Entry {idx+1} attivata')
            w.configure(text=f'Disattiva {idx+1}')
        
if __name__ == '__main__':
    app = Win()
    app.mainloop()
Ciao

Fatti non foste a viver come bruti...
Che bello quando tutte le risposte sono già dentro di te, ma purtroppo sono sbagliate [cit.]

L'assist era troppo invitante, scusa la mia facile ironia, comunque la soluzione che proponi mi sembra non solo funzionante, ma per nulla brutta ed anzi la trovo molto leggibile.

Cya

Daniele aka Palmux said @ 2024-01-22 20:13:26:
Che bello quando tutte le risposte sono già dentro di te, ma purtroppo sono sbagliate [cit.]
L'assist era troppo invitante, scusa la mia facile ironia,...

Scusato, mi son detto molto di peggio quando, dopo tre-quattro giorni che guardavo perplesso il codice esposto nel primo post (e perfettamente funzionante) son giunti alla coscienza i "perché" dell'impressione che fosse sbagliato, perché che conoscevo e non mi erano sopravvanuti nel momento della implementazione ... ho voluto darne cenno, nel precedente post, anche per indicare ad eventuali lettori che non necessariamente basti scrivere un codice che funziona.

Riguardo al codice ultimo proposto, il grosso era un abbozzo di test scritto per sperimentare degli eventi virtuali mirati alla problematica, che ho deciso di re-implementare nella maniera "logica" trovata e spiegata all'inizio, a beneficio dei numerosi utenti che pongono domande rispetto a tkinter. Certamente non è usabile direttamente ma mostra una modalità "carina" di utilizzo delle entry, magari spingerà qualche utente ad approfondire le classi.

Fatti non foste a viver come bruti...
Scusato, mi son detto molto di peggio quando, dopo tre-quattro giorni che guardavo perplesso il codice esposto nel primo post (e perfettamente funzionante) son giunti alla coscienza i "perché" dell'impressione che fosse sbagliato, perché che conoscevo e non mi erano sopravvanuti nel momento della implementazione ... ho voluto darne cenno, nel precedente post, anche per indicare ad eventuali lettori che non necessariamente basti scrivere un codice che funziona.

Riguardo al codice ultimo proposto, il grosso era un abbozzo di test scritto per sperimentare degli eventi virtuali mirati alla problematica, che ho deciso di re-implementare nella maniera "logica" trovata e spiegata all'inizio, a beneficio dei numerosi utenti che pongono domande rispetto a tkinter. Certamente non è usabile direttamente ma mostra una modalità "carina" di utilizzo delle entry, magari spingerà qualche utente ad approfondire le classi.


Guarda ti ringrazio, è proprio questo lo spirito corretto. Quando si raggiungono dei traguardi, siano essi grandi o piccoli, la condivisione è proprio il motivo perché queste risorse esistono (vabbé anche se stanno sparendo, qualcuno tiene duro).

Ancora grazie.

Daniele aka Palmux said @ 2024-01-24 08:59:13:
..., la condivisione è proprio il motivo perché queste risorse esistono (vabbé anche se stanno sparendo, qualcuno tiene duro).

Ed il ringraziamento vada a chi permette che tali risorse siano disponibili.

Da parte mia è solo un restituire l'enorme aiuto che mi è stato elargito con tale mezzo che pur se in declino non ha perduto nulla della sua validità.

Fatti non foste a viver come bruti...


Pagina: 1



Esegui il login per scrivere una risposta.