Topic: Gui con oggetti variabili  (Letto 1674 volte)

0 Utenti e 1 Visitatore stanno visualizzando questo topic.

Offline Dr.Faustus

  • python unicellularis
  • *
  • Post: 42
  • Punti reputazione: 0
    • Mostra profilo
Gui con oggetti variabili
« il: Maggio 21, 2016, 08:09 »
Ciao a tutti,
vorrei creare una gui che in base ad una variabile, crea una matrice NxN di entribox.. ora al variare della variabile voglio che con un comando, il programma crei o distrugga le entribox lasciando inalterate le prime.
Per capirci se la variabile ha valore 2, avrò ( la a sono dei valori che inserisco io nella entry)
a a
a a
ma se cambio la variabile a 5
a a _ _ _
a a _ _ _
_ _ _ _ _
_ _ _ _ _
_ _ _ _ _

senza quindi toccare le a.
Questo chiaramente a ritroso..
Ora, avevo provato già a fare qualcosa del genere con tkinter, ma la faccenda diventava particolarmente fastidiosa in primis perchè  la logica con cui creavo e distruggevo gli oggetti era particolarmente triky..
poi perchè non avevo trovato modo di creare oggetti con differenti nomi in un loop.
Vi chiederei quindi se poteste darmi delle dritte.. anche con interfacce differenti da tkinter, per mettermi sulla buona strada.. Grazie!!! 8)

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 3.154
  • Punti reputazione: 9
    • Mostra profilo
Re: Gui con oggetti variabili
« Risposta #1 il: Maggio 21, 2016, 10:29 »
Mah sai, non avrai modo di creare oggetti con nomi diversi in un loop, ma hai comunque modo di inserire dinamicamente dentro un dizionario (o qualcosa del genere) dei riferimenti a questi oggetti. In python queste cose sono un gioco da ragazzi.
Per esempio, lasciando fuori per un attimo il discorso gui, immagina di avere una classe qualsiasi, per esempio
[codice]
class Entry(object):
    pass
[/codice]
se decidi per esempio di crearne un tot e metterli in una struttura a griglia, potresti usare una lista di liste per contenere i riferimenti agli oggetti che crei:
[codice]
def fai_una_griglia_di_entry(num_righe, num_colonne):  # rozzo ma chiaro
    griglia = []
    for i in range(1, len(num_righe)):
        riga = []
        for j in range(1, len(num_colonne)):
            e = Entry()
            riga.append(e)
        griglia.append(riga)
    return griglia
[/codice]
Ora, se fai girare questa funzione (per esempio griglia = fai_una_griglia_di_entry(4, 6), ti restituisce un gruppo di Entry "anonime". E' vero che nessuna di loro puoi "chiamarla per nome" con una variabile, ma puoi comunque riferirti a ciascuna Entry attraverso gli indici della griglia: griglia[2][4] e così via.
Ficcare tutto in un dizionario è ancora più semplice (basta ricordare che le tuple vanno bene come chiavi di dizionario, e sperare che il tuo oggetto Entry sia hashabile, cosa che di solito è):
[codice]
def fai_un_dizionario_di_entry(num_righe, num_colonne): # rozzo ma chiaro
    dizionario = {}
    for i in range(1, len(num_righe)):
        for j in range(1, len(num_colonne)):
            dizionario[(i, j)] = Entry()
    return dizionario
[/codice]


Ora venendo al discorso gui, io non sono pratico di tk e magari la logica di impacchettamento è diversa, quindi ti faccio l'esempio con le wx (ma è talmente ovvio da seguire che non devi conoscere le wx per capirlo).
Nelle wx potresti facilmente scrivere una factory come
[codice]
def make_entry_grid(rows, cols, parent=None): #"parent" è la finestra dove vanno messi
    d = {}
    p = wx.Panel(parent)  # uno "sfondo" di appoggio
    s = wx.GridSizer(rows, cols)   # un sizer per incolonnare fisicamente le entry
    for i in range(1, len(rows)):
        for j in range(1, len(cols)):
            e = wx.TextCtrl(p, -1, 'hello')  # creo una entry
            s.Add(e)  # impacchetto la entry nel sizer
            d[(i, j)] = e
    p.SetSizer(s)  # attacco il sizer con le entry allo sfondo
    return d, p
[/codice]
Quando esegui questa funzione (per esempio self.entries, self.panel_entries = make_entry_grid(5, 4, self), restituisce due cose: il panel "contenitore" con tutte le entry già disegnate, che potrai a tua volta appiccicare da qualche parte nella tua finestra; e un dizionario pieno di riferimenti alle singole entry che hai creato.
Così potrai fare cose come self.entries[(2,3)].SetValue('foo') oppure self.entries[(2, 2)].GetValue() eccetera.
Ora, con le tk non dovrebbe essere difficile fare qualcosa del genere.

Per quanto riguarda la distruzione / ricreazione in seguito della griglia... non saprei, io consiglio sempre di NON mettersi a fare queste cose, per l'utente sono molto disturbanti. Comunque, nell'esempio di cui sopra, a questo punto sarebbe facile distruggere semplicemente tutto il panel contenitore, e chiamare daccapo la funzione per crearne un altro, e ri-appiccicarlo al suo posto nel layout della finestra. Come fare esattamente queste operazioni dipende dall'api del tuo framework (saprei farlo facilmente con le wx, dovrei studiarci un attimo per le tk).

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.658
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Re: Gui con oggetti variabili
« Risposta #2 il: Maggio 21, 2016, 13:12 »
Solo una precisazione: ma non è richiesto solo che le chiavi di un dizionario siano hashabili?

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 3.154
  • Punti reputazione: 9
    • Mostra profilo
Re: Gui con oggetti variabili
« Risposta #3 il: Maggio 21, 2016, 14:25 »
Sì infatti... volevo scrivere una cosa e poi me la sono dimenticata.
Di solito, quando uso questa tecnica, mi piace fare la "partita doppia", ovvero:
[codice]
# bla bla
d[(i, j)] = e
d[e] = (i, j)
# bla bla
[/codice]
In questo modo il dizionario può essere usato sia per recuperare l'oggetto a partire dalla posizione, sia per recuperare la posizione a partire dall'oggetto. Occasionalmente può essere utile. Ma per fare questo, hai appunto bisogno che anche gli oggetti siano hashabili.
Questa però è una ulteriore raffinatezza che probabilmente all'OP non interessa molto.

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 3.154
  • Punti reputazione: 9
    • Mostra profilo
Re: Gui con oggetti variabili
« Risposta #4 il: Maggio 21, 2016, 21:50 »
Uhm, e giusto così per completezza, aggiungo anche che occasionalmente il framework che usi potrebbe darti delle soluzioni tutte sue per questo problema.
Per esempio, in wxPython tutti i widget hanno un id univoco. Di solito nessuno usa davvero questo id, e si lascia che il framework li assegni per conto suo e amen.
Tuttavia, potresti assegnarli tu stesso secondo un pattern definito (magari anche solo in ordine crescente funziona!), tipo
[codice]
id_ = 100
for i in range(1, len(num_righe)): 
    for j in range(1, len(num_colonne)): 
        e = wx.TextCtrl(p, id_, 'hello')
        s.Add(e)
        id_ += 1
[/codice]
Fatto questo, puoi recuperare la entry che ti serve con wx.FindItemById(id)...
Ovviamente il modo "pure python" è più generale ed elegante, ma occasionalmente è più veloce usare gli strumenti che il framework ti mette a disposizione.

Offline Dr.Faustus

  • python unicellularis
  • *
  • Post: 42
  • Punti reputazione: 0
    • Mostra profilo
Re: Gui con oggetti variabili
« Risposta #5 il: Maggio 22, 2016, 11:31 »
Ok grazie! ci lavoro un po su..
 :ok:  :fingers-crossed:

Offline riko

  • python deus
  • *
  • moderatore
  • Post: 7.453
  • Punti reputazione: 12
    • Mostra profilo
    • RiK0 Tech Temple
Re: Gui con oggetti variabili
« Risposta #6 il: Maggio 22, 2016, 21:19 »
Sì infatti... volevo scrivere una cosa e poi me la sono dimenticata.
Di solito, quando uso questa tecnica, mi piace fare la "partita doppia", ovvero:
[codice]
# bla bla
d[(i, j)] = e
d[e] = (i, j)
# bla bla
[/codice]
In questo modo il dizionario può essere usato sia per recuperare l'oggetto a partire dalla posizione, sia per recuperare la posizione a partire dall'oggetto. Occasionalmente può essere utile.

Mi pare veramente brutto.

In primo luogo stai sottovalutando l'implementazione di rendere ste Entry ben hashabili... cioe' se prendi l'implementazione di default, hai di fatto la semantica dell'identita'. Che puo' essere o meno quello che vuoi. Mi vengono in mente casi in cui lo e', ma vuole dire che pensi di avere in giro delle handle. Ora siccome io mi aspetto che l'unico modo in cui hai delle handle e' quando le tiri fuori dal dizionario, puoi avere comunque le chiavi (e quindi non ti serve il doppio dizionario).

Diverso invece se le tue Entry avessero l'hash con una semantica diversa. In questo caso sarebbe molto piu' utile.

Poi aggiungo; spesso quando voglio un doppio dizionario, mi chiedo se non sto sbagliando il design. In generale e' un sintomo che la chiave e il valore siano troppo accoppiati, e allora forse vuoi qualcosa di diverso.

Detto questo... il secondo problema e' che e' parecchio sgradevole avere a che fare con un dizionario non uniforme. Un dizionario che ha per chiavi sia delle tuple che degli oggetti arbitrari e' davvero molto scomodo da usare per praticamente tutte le operazioni che non siano esclusivamente basati su un search. E in aggiunta e' una struttura dati seccante da tenere in modo consistente.

Offline RicPol

  • python sapiens sapiens
  • ******
  • Post: 3.154
  • Punti reputazione: 9
    • Mostra profilo
Re: Gui con oggetti variabili
« Risposta #7 il: Maggio 23, 2016, 12:03 »
> Mi pare veramente brutto.

Mah, più o meno. Non bisogna abusarne, ovvio.
Per esempio:

> e' parecchio sgradevole avere a che fare con un dizionario non uniforme.
> (...) e' davvero molto scomodo da usare per praticamente tutte le operazioni che non siano esclusivamente basati su un search.

E infatti nel mio scenario il dizionario deve servire *esclusivamente* per operazioni di search. (1)
E in questo scenario, non ci vedo alcun male. Voglio dire, è Python in primo luogo che ti permette di avere liste e dizionari non omogenei. Se ti serve L = [2, "mamma", math.root, None], che male c'è? Ovvio, non devi poi chiamare sum su una lista fatta così... ma se l'hai fatta così è perché non pensavi di farci sum in ogni caso.
In particolare, i dizionari "bifronti" sono comodi per le tabelle di conversione etc.

periodicity = {'quotidiano':1, 1:'quoditiano', 'settimanale':7, 7:'settimanale', 'quindicinale':15, 15:'quindicinale', 'mensile':30, 30:'mensile', 'bimestrale':60, 60:'bimestrale'}

Poi come sempre ci sono dei limiti, e se tocchi quei limiti allora cambi struttura.

Però non capisco questo:

> quando voglio un doppio dizionario, mi chiedo se non sto sbagliando il design.
> In generale e' un sintomo che la chiave e il valore siano troppo accoppiati,

Beh, sì, ma talvolta è proprio così che deve andare. Per esempio, il mondo delle gui è pieno di accoppiamenti del genere. In wxPython ci sono situazioni in cui ti ritrovi in mano un handle del widget implicato in un evento, e situazioni in cui hai l'indice dello slot del sizer implicato nell'evento. Se ti serve, puoi convertire dall'uno all'altro con gli strumenti del framework, volendo, o puoi farti una struttura MCV apposita che gestisce il casino. Ora, a dire il vero è molto raro che tu abbia bisogno di monitorare entrambi i canali di eventi (in genere i sizer non ti interessano più dopo che hai finito di disegnare la gui), ma nel caso, un dizionario bifronte (o una lista ordinata di cui ricavi gli indici) può servirti al volo.


Detto questo, poi per carità: nella pratica, non mi capita praticamente mai di mappare widget creati dinamicamente con un doppio dizionario. In genere uso un dizionario a una sola via (le chiavi sono gli indici del sizer, i valori le istanze dei widget; ma ci sono delle varianti anche più comode, a dire il vero). Mi tengo la possibilità del doppio dizionario solo quando voglio tagliar corto attraverso qualche noioso bizantinismo delle wx.



----------------
(1) In particolare, il dizionario si costruisce al momento iniziale di disegnare la finestra, e poi resta sempre invariato. Mi viene in mente però che l'OP accennava alla possibilità successiva di creare/distruggere nuovi oggetti... E allora il suo scenario in effetti cambierebbe, ma in questo caso onestamente consiglierei di usare qualcosa di più robuso e MCV, e in ogni caso ripeterei per l'ennesima volta che creare/distruggere elementi della gui a runtime è una pessima idea.