Python\Tkinter - problemi con Focus su widget Entry

Linguaggi di programmazione: php, perl, python, C, bash, ecc.

Python\Tkinter - problemi con Focus su widget Entry

Messaggioda robdevo » giovedì 12 settembre 2019, 16:07

Buon giorno a tutti, sto cercando di realizzare un'applicazione in Python appoggiandomi a Tkinter come GUI.
Ho un problema su un controllo (widget) di tipo Entry (testo con una sola riga), nel caso di errore nel testo vorrei riuscire a evidenziare tutto il testo errato.
Leggendo un reference Tkinter 8.5 e confrontadolo con Internet, mi sembra di aver capito che bisogna usare il metodo ".select_range(0, END)".
Quindi:
Codice: Seleziona tutto
    #  definizione Entry
    self.txtNetto = Entry(self.frm2, textvariable=self.netto, width=6).grid(column=1, row=10, padx=10, sticky=W)
 
    # caso di errore
    if (errNum == 1):
            self.errore = TRUE
            self.messaggio.set('Errore - valore digitato NON numerico: ' + self.netto.get())
            [b]self.txtNetto.select_range(0, END)[/b]

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Program Files (x86)\Python37-32\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
  File "F:\VBasicMio\6 - Python\SpeseInserimento.py", line 110, in Esegui
    self.Controlla()
  File "F:\VBasicMio\6 - Python\SpeseInserimento.py", line 117, in Controlla
    self.CheckImporti()
  File "F:\VBasicMio\6 - Python\SpeseInserimento.py", line 130, in CheckImporti
    self.txtNetto.select_range(0, END)
[b]AttributeError: 'NoneType' object has no attribute 'select_range'[/b]                                            <---------------  errore di ritorno


Qualcuno mi sa aiutare, grazie mille.
Ultima modifica di robdevo il domenica 22 settembre 2019, 14:37, modificato 1 volta in totale.
Portatile con Ubuntu 18.04 - fisso con Windows 10
robdevo
Prode Principiante
 
Messaggi: 42
Iscrizione: ottobre 2009

Re: Python\Tkinter

Messaggioda nuzzopippo » venerdì 13 settembre 2019, 8:26

Guarda, i Tuoi stralci di codice lasciano arguire che c'è un bel po' di "altro" da cui possa scaturire un errore, per altro non si evince cosa genera "errNum"; comunque il messaggio
'NoneType' object has no attribute 'select_range'

è chiaro : stai operando su di un oggetto non istanziato.

Il perché ciò accada è difficile indovinarlo, può dipendere tanto da errori di digitazione quanto da una invocazione di metodo al momento sbagliato o da visibilità di variabili, ma guarda molto bene il Tuo codice, è possibile vi sia qualche errore di logica.

Una prova "al volo" potresti farla verificando preliminarmente se la entry contenga qualcosa
Codice: Seleziona tutto
   # caso di errore
if self.txtNetto.get():
            if errNum == 1:
               self.errore = TRUE
               self.messaggio.set('Errore - valore digitato NON numerico: ' + self.netto.get())
               self.txtNetto.select_range(0, END)
else:
   codice in caso non ci siano valori

Se anche così Ti da errore su self.txtNetto è un problema di visibilità delle variabili
Avatar utente
nuzzopippo
Entusiasta Emergente
Entusiasta Emergente
 
Messaggi: 1310
Iscrizione: ottobre 2006

Re: Python\Tkinter

Messaggioda robdevo » venerdì 13 settembre 2019, 14:37

Si hai ragione l'oggetto in questione (self.txtNetto) non risulta istanziato.

Però non capisco: il campo in questione corrisponde ad un campo di mappa dove io digito ad esempio: 'abcd' per far succedere l'errore testato (Imp.Netto digitato non numerico) ed in effetti sulla mappa viene visualizzata la descrizione prevista in questi casi. Il campo self.netto ( che è il campo textvariable del campo di mappa) rimane valorizzato anche nelle routines dove io verifico se esistono errori, mentre il campo di mappa risulta 'non istanziato' quindi non valorizzato.

Non riesco a capire come si possa gestire questa situazione, cioè a fronte di un test sul campo textvariable come faccio ad istanziare il relativo campo della mappa e quindi ad attivare i metori previsti?
Grazie.
Portatile con Ubuntu 18.04 - fisso con Windows 10
robdevo
Prode Principiante
 
Messaggi: 42
Iscrizione: ottobre 2009

Re: Python\Tkinter

Messaggioda nuzzopippo » sabato 14 settembre 2019, 9:04

robdevo Immagine ha scritto:Si hai ragione l'oggetto in questione (self.txtNetto) non risulta istanziato.

Però non capisco: il campo in questione corrisponde ad un campo di mappa dove io digito ad esempio: 'abcd' per far succedere l'errore testato (Imp.Netto digitato non numerico) ed in effetti sulla mappa viene visualizzata la descrizione prevista....


Guarda, da quanto dici su ed in precedenza mi sembra si sia di fronte ad un problema di visibilità delle variabili e/o un problema di design dell'applicazione.
Come già detto, personalmente non mi sembra possa indovinarsi il "perché" dallo stralcio di codice da te esemplificato che, per altro, potrebbe anche essere prodotto dal modo e dal momento di intercettazione di un qualche evento da Te definito.

Per abitudine, quando devo accedere al contenuto di una entry uso raramente oggetti StringVar, in genere accedo direttamente al contenuto della enty ... Ti faccio un esempio tratto da una applicazione che sto sviluppando per mero studio :
definita una entry in un oggetto "finetra" implemento il bind del tasto invio :
Codice: Seleziona tutto
        self.txt_index = tk.Entry(self)
        self.txt_index.grid(row=1, column=1, columnspan=2, sticky='ew', pady=3)
        self.txt_index.bind('<Return>', self._set_index_ev)
        self.txt_index.bind('<KP_Enter>', self._set_index_ev)

e nella funzione di callback verifico il testo e lo contesto se non conforme
Codice: Seleziona tutto
    def _set_index_ev(self, evt):
        if self.stato != 'aperto': return 'break'
        val = self.txt_index.get()
        if not val: return
        try:
            i_val = int(val)
            self.txt_index.delete(0, tk.END)
        except:
            mbdlg.showerror('Valore non valido',
                            'Il valore inserito non è un numero intero')
            self.txt_index.set_focus()
            return 'break'
        self.master.set_indice(i_val)
        return 'break'

Ovviamente la funzione di callback è un metodo della classe "finestra"
Come puoi vedere, il mio "modo" di programmare (amatoriale-autodidatta come Te) è molto diverso dalla modalità da Te utilizzata ... ciò per farTi comprendere quanto sia difficile dare una mano in assenza di indicazioni adeguate.

Come detto prima, suppongo che Tu incorra in un qualche problema di visibilità di variabile ma potrebbe essere altro, valuta bene il Tuo codice e se proprio non ne vieni a capo considera di postare l'intero codice della classe che genera l'errore, se non io qualcun altro potrà darti una mano.

Ciao :ciao:
Avatar utente
nuzzopippo
Entusiasta Emergente
Entusiasta Emergente
 
Messaggi: 1310
Iscrizione: ottobre 2006

Re: Python\Tkinter

Messaggioda robdevo » sabato 14 settembre 2019, 16:58

Ciao e grazie.
Ho eseguito la modifica da Te proposta, cioè inserire la gestione dei tasti, facendo così le cose sono migliorate.
Ti posso dire che si può anche testare il campo indicato nella StringVar, l'unica cosa che NON funziona è il Focus a fronte del campo errato, mentre ad esempio funziona le Delete dei dati introdotti, inoltre volevo chiederti ma in che ambiente e su quale release di Python lavori, perchè a me da errore se uso il set_focus (non esiste il metodo) mentre accetta il focus_set anche se poi non posiziona il cursore nel campo errato.
Ti posto il codice di definizione e di trattamento (il sorgente è lungo)
Codice: Seleziona tutto
------------------- definizione campo

        self.txtData = Entry(self.frm2, textvariable=self.data, width=8)
        self.txtData.grid(column=1, row=1, padx=10, sticky=W)
        self.txtData.bind('<Tab>', self.CheckData)
        self.txtData.bind('<Return>', self.CheckData)
        self.txtData.focus()                                                                                      <--------- qui posiziona correttamente il cursore quando appare la mappa

------------------- trattamento campo data
    def CheckData(self, evt):
        aaMax = self.tempoattuale[0] + 1               
        oggi = datetime.date.today()
        periodo = datetime.timedelta(days=20)
        dtMinima = oggi - periodo
        dtMassima = oggi + periodo
        if len(self.data.get()) < 8 or len(self.data.get()) > 8:
            self.errore = TRUE
            self.messaggio.set('Errore - La data operazione deve avere il seguente formato GGMMAAAA ' + self.data.get())
            self.txtData.delete(0,END)                                                                  <--------- qui cancella il contenuto errato del campo-
            self.txtData.focus_set()                                                                       <---------- qui NON posiziona il cursore sul campo errato ma rimane sul campo successivo
        else:
            dtDigitata = self.data.get()
            check = dtDigitata.isnumeric()
            if not check:
                self.errore = TRUE
                self.messaggio.set('Errore - La data operazione deve essere numerica ' + self.data.get())
            else:
                gg = dtDigitata[:2]
                ggI = int(gg)
                mm = dtDigitata[2:4]
                mmI = int(mm)
                aa = dtDigitata[4:]
                aaI = int(aa)
                if aaI < 2019 or aaI > aaMax:
                    self.errore = TRUE
                    self.messaggio.set('Errore - Anno non valido - range ammesso: 2019 - ' + str(aaMax))
                else:
                    if mmI < 1 or mmI > 12:
                        self.errore = TRUE
                        self.messaggio.set('Errore - Mese non valido - range ammesso: 01 - 12')
                    else:
                        if ggI < 1 or ggI > 31:
                            self.errore = TRUE
                            self.messaggio.set('Errore - Giorno non valido - range ammesso: 01 - 31')
                        else:
                            self.dataDb = datetime.date(aaI, mmI, ggI)
                            if self.dataDb < dtMinima or self.dataDb > dtMassima:
                                self.errore = TRUE
                                self.messaggio.set('Errore - Data non valida - range ammesso: Oggi + o - 20 gg.')


Ciao.
Portatile con Ubuntu 18.04 - fisso con Windows 10
robdevo
Prode Principiante
 
Messaggi: 42
Iscrizione: ottobre 2009

Re: Python\Tkinter

Messaggioda nuzzopippo » domenica 15 settembre 2019, 14:31

Ciao

anche dal Tuo ultimo stralcio non si evince un "perché" della segnalazione "'NoneType' object has no attribute 'select_range" iniziale ... Se il problema persiste ancora, dovresti ingegnarTi ad emularlo con codice ridotto e completo come efficacia su cui discorrere.

Per altro :
robdevo Immagine ha scritto:... l'unica cosa che NON funziona è il Focus a fronte del campo errato,...
...inoltre volevo chiederti ma in che ambiente e su quale release di Python lavori, perchè a me da errore se uso il set_focus (non esiste il metodo) mentre accetta il focus_set anche se poi non posiziona il cursore nel campo errato.

In effetti, un metodo java/swing non è che vada bene in tkinter :shy: chiedo venia, mi capita spesso di pensare in java e, come detto prima, il codice proposto è in sviluppo e non ancora testato adeguatamente, era una routine recente che mi son ricordato analoga alle Tue problematiche.
Riguardo alle altre Tue domande : utilizzo 7 computer diversi (3 in ufficio e 4 a casa) con diverse versioni di python, da 3.5 a 3.7 (N.B. - non faccio il programmatore, anche se diverse cose al lavoro sono opera mia), dipendentemente dalla macchina uso prevalentemente gedit o thonny, sto guardando in giro per IDE adeguati, ben documentati, sin ora non ho trovato roba che mi convinca ... con un po' di pudore devo dire che son tentato dal 'Visual Studio Code' della Microsoft ma ancora non mi son convinto ad installarlo.

Detto questo, passiamo al Tuo codice.
Mi perdonerai l'opinione ma gestire nella Tua modalità le date è una brutta idea ... per i bisestili come ti metti? E per i mesi di 30gg? Ritengo che Ti convenga utilizzare i mezzi messi a disposizione da datetime.
Un po' per farmi perdonare quel "set_focus()" mi son permesso, questa mattina, di sviluppare un piccolo esempio di "valutazione di data" con una finestra creata sub-classando Tk ed utilizzando le metodologie che abitualmente uso, giusto per esempio provvista di una gestione del focus per le entry e selezione del testo in caso di data incompatibile, questo il codice completo (~150 righe) :
Codice: Seleziona tutto
# -*- coding: utf-8 -*-

import datetime
import tkinter as tk
import tkinter.messagebox as msgb

class MyTopWin(tk.Tk):
    '''
Finestra di esempio per controllo immissione data - versione 1.0

In questo scenario non uso oggetti StringVar ne' validatori, si affrontano la
verifica della data immessa, gestione del focus e selezione del testo.

La verifica dati viene scatenata da un evento esterno al widget dedicato.
'''
    def __init__(self):
        super().__init__()
        self.title('CTRL Input data per @robdevo')
        # blocco per la data
        lbl_1 = tk.Label(self, text='data (gg/mm/yyyy) : ', justify='left')
        lbl_1.grid(row=0, column=0, sticky='w', padx=4)
        self.e_data = tk.Entry(self, bg='white')
        self.e_data.grid(row=0, column=1, sticky='ew', pady=4, padx=4)
        # blocchi testo per riempimento
        lbl_2 = tk.Label(self, text='testo 1 : ', justify='left')
        lbl_2.grid(row=0, column=2, sticky='w', padx=4)
        self.e_txt1 = tk.Entry(self, bg='white')
        self.e_txt1.grid(row=0, column=3, sticky='ew', pady=4, padx=4)
        lbl_3 = tk.Label(self, text='testo 2 : ', justify='left')
        lbl_3.grid(row=1, column=0, sticky='w', padx=4)
        self.e_txt2 = tk.Entry(self, bg='white')
        self.e_txt2.grid(row=1, column=1, sticky='ew', pady=4, padx=4)
        lbl_4 = tk.Label(self, text='testo 3 : ', justify='left')
        lbl_4.grid(row=1, column=2, sticky='w', padx=4)
        self.e_txt3 = tk.Entry(self, bg='white')
        self.e_txt3.grid(row=1, column=3, sticky='ew', pady=4, padx=4)
        # pulsanti di comando
        self.bt_evaluate = tk.Button(self, text='Valuta data')
        self.bt_evaluate.grid(row=2, column=0, columnspan=2, sticky='ew',
                              pady=4, padx=4)
        self.bt_end = tk.Button(self, text='Chiudi programma')
        self.bt_end.grid(row=2, column=2, columnspan=2, sticky='ew',
                         pady=4, padx=4)
        # configurazione colonne
        self.grid_columnconfigure(1, weight=1)
        self.grid_columnconfigure(3, weight=1)
        # binders
        # *** cambio Focus ***
        self.e_data.bind('<FocusIn>', self.__in_focus)
        self.e_data.bind('<FocusOut>', self.__out_focus)
        self.e_txt1.bind('<FocusIn>', self.__in_focus)
        self.e_txt1.bind('<FocusOut>', self.__out_focus)
        self.e_txt2.bind('<FocusIn>', self.__in_focus)
        self.e_txt2.bind('<FocusOut>', self.__out_focus)
        self.e_txt3.bind('<FocusIn>', self.__in_focus)
        self.e_txt3.bind('<FocusOut>', self.__out_focus)
        # tasti invio
        self.e_data.bind('<Return>', self.__move_to_entry)
        self.e_data.bind('<KP_Enter>', self.__move_to_entry)
        self.e_txt1.bind('<Return>', self.__move_to_entry)
        self.e_txt1.bind('<KP_Enter>', self.__move_to_entry)
        self.e_txt2.bind('<Return>', self.__move_to_entry)
        self.e_txt2.bind('<KP_Enter>', self.__move_to_entry)
        self.e_txt3.bind('<Return>', self.__move_to_entry)
        self.e_txt3.bind('<KP_Enter>', self.__move_to_entry)
        # pulsanti
        self.bt_end.bind('<Button>', self.__on_close)
        self.bt_evaluate.bind('<Button>', self.__evaluate_time)
        # visualizzazione finale
        self.update()
        self.minsize(self.winfo_reqwidth(), self.winfo_reqheight())
        win_center(self)
   
    def __in_focus(self, evt):
        evt.widget.configure(bg='#ffffc0')
   
    def __out_focus(self, evt):
        evt.widget.configure(bg='white')
   
    def __move_to_entry(self, evt):
        if evt.widget == self.e_data:
            self.e_txt1.focus_set()
        elif evt.widget == self.e_txt1:
            self.e_txt2.focus_set()
        elif evt.widget == self.e_txt2:
            self.e_txt3.focus_set()
        elif evt.widget == self.e_txt3:
            self.e_data.focus_set()
   
    def __on_close(self, evt):
        self.destroy()
   
    def __evaluate_time(self, evt):
        str_time = self.e_data.get()
        if not str_time:
            msgb.showerror('Data non valida',
                           'Non è stata inserita nessuna data')
            self.e_data.focus_set()
            return
        try:
            my_date = datetime.datetime.strptime(str_time,'%d/%m/%Y')
        except ValueError as e:
            msgb.showerror('Data non valida',
                           e)
            self.e_data.focus_set()
            self.e_data.select_range(0, tk.END)
            return
        # blocco controlli personalizzati robdevo
        to_day = datetime.date.today()
        interv = datetime.timedelta(days=20)
        if my_date.date() < to_day - interv or my_date.date() > to_day + interv:
            msgb.showerror('Data fuori intervallo',
                           'ammesso : ' + to_day.strftime('%d/%m/%Y') + ' ± 20 gg.')
            self.e_data.focus_set()
            self.e_data.select_range(0, tk.END)
            return
        # codice se la data è buona, esempio       
        msgb.showinfo('Data OK', my_date.strftime('%d/%m/%Y') + ' va bene...')
        # così le combini come vuoi, p.e.
        gg = my_date.day
        mm = my_date.month
        yy = my_date.year
        msg = 'Un esempio di combinazione : ' + str(yy) + ' ' + str(mm) + ' ' + str(gg)
        msgb.showinfo('Esempio', msg)


# ****************************
# *** FUNZIONI DI SERVIZIO ***
# ****************************


def win_center(gui):
    ''' Centra una finestra, passata come parametro, sullo schermo '''
    l = gui.winfo_screenwidth()
    a = gui.winfo_screenheight()
    wx = gui.winfo_reqwidth()
    wy = gui.winfo_reqheight()
    gui.geometry('{}x{}+{}+{}'.format(wx, wy, (l-wx)//2, (a-wy)//2))
   

# ****************************
# *** LANCIO APPLICAZIONE  ***
# ****************************

if __name__ == '__main__':
    root_win = MyTopWin()
    root_win.mainloop()

Vedi che Ti sembra la gestione alternativa della data immessa che Ti propongo.

... di solito i miei "esercizi" sono più complessi ma questa è solo una base di partenza, dato che a tempo perso ho intenzione di vedermi, finalmente, le StringVar() et similia oltre che i validatori, se Ti va potremmo anche parlarne in merito,

Ciao
Avatar utente
nuzzopippo
Entusiasta Emergente
Entusiasta Emergente
 
Messaggi: 1310
Iscrizione: ottobre 2006

Re: Python\Tkinter

Messaggioda robdevo » venerdì 20 settembre 2019, 14:53

Grazie per la Tua risposta ed il Tuo esempio.
Lo sto studiando, ci sentiamo a breve, grazie.
Portatile con Ubuntu 18.04 - fisso con Windows 10
robdevo
Prode Principiante
 
Messaggi: 42
Iscrizione: ottobre 2009

Re: Python\Tkinter

Messaggioda tokijin » sabato 21 settembre 2019, 8:00

@robdevo
modifica per favore il titolo del primo messagigo mettendone uno meno generico che spieghi meglio la situazione.

Ciao
Sei abbruzzese se dopo che ti sei strafogato un chilogrammo di pasta, hai il coraggio di dire alla cuoca "cacc ch'è cott" - Se entra un piccione in casa..chiudi le finestre!
Ubuntu User #28657 - Il mio vecchio hardware - Tag Codice - Giusto per occupare un po' di spazio..
Avatar utente
tokijin
Moderatore Globale
Moderatore Globale
 
Messaggi: 4232
Iscrizione: giugno 2009
Località: Abruzzo
Desktop: Plasma 5.14.5
Distribuzione: KDE Neon User Edition 5.14 x86_64


Torna a Programmazione

Chi c’è in linea

Visualizzano questa sezione: 0 utenti registrati e 4 ospiti