OptionMenu ändert sich nicht bei Laufzeit?

Fragen zu Tkinter.
Antworten
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Hallo Python-Experten,

ich versuche mit mehreren Optionmenues zu arbeiten, wobei sich die Inhalte jedoch
aendern sollen.

Folgendes Minimalbeispiel zeigt das Problem:
Wenn ich das obere Menu aendere wird eine Funktion aufgerufen (change_second_optionmenu),
die den Inhalt des unteren Optionmenues aendern soll.
Jedoch wird nur der default Wert (c_01) gesetzt, rolle ich jedoch das Optionmenu auf,
sehe ich immer noch den alten Inhalt (b_01, b_02).

Was muss ich da tun, dass er den neuen Inhalt auch sieht?
Ich definier doch self.option_03_list in der Funktion, aber das scheint nicht zu reichen.

Code: Alles auswählen

#!/usr/bin/env python

import Tkinter as tk
import string, re, os, sys, subprocess

class MyApplication():

   def __init__(self, master): 
   
      frame_entries      = tk.LabelFrame(master)
      
      option_01_list     = ["a_01","a_02"]      
      self.option_01_var = tk.StringVar(frame_entries)
      self.option_01_var.set(option_01_list[0]) 
      self.option_01     = tk.OptionMenu(frame_entries,self.option_01_var, \
                           *option_01_list, command=self.change_second_optionmenu)

      self.option_03_list= ["b_01","b_02"]
      self.option_03_var = tk.StringVar(frame_entries)
      self.option_03_var.set(self.option_03_list[0])
      self.option_03     = tk.OptionMenu(frame_entries,self.option_03_var, \
                           *self.option_03_list)
                           
      self.option_01.grid(row=0, column=1)                           
      self.option_03.grid(row=2, column=1)
      frame_entries.grid( row=0, column=0)       
            
    
   def change_second_optionmenu(self, *arg):
   
      self.option_03_list= ["c_01","c_02"]      
      self.option_03_var.set(self.option_03_list[0])
                         
# ------------------------------------------------------------------------------

if __name__ == "__main__":
   root = tk.Tk()
   root.geometry("200x200")
   root.config(background="#123456")
   root.option_add('*font',("courier",8,"bold"))
   root.title("lsf_submitting")
   display = MyApplication(root)
   root.mainloop()
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Weil du nur den Wert der Control-Variable aktualisiert hast, du musst auch noch das GUI-Objekt aktualisieren.

Edit: Sry, den das da oben vergessen - die Control-Variable updated die GUI ja bei änderung.
Zuletzt geändert von Xynon1 am Dienstag 19. Oktober 2010, 11:38, insgesamt 1-mal geändert.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Hallo,

das klingt gut, aber wo genau muss ich das update machen lassen,
in der Funktion selbst hat es keinen Effekt, also:

Code: Alles auswählen

   
def change_second_optionmenu(self, *arg):
   
      self.option_03_list= ["c_01","c_02"]      
      self.option_03_var.set(self.option_03_list[0])
      root.update()
bringt leider nix. Gleicher unerwuenschter Effekt.

Auch:
self.option_03.update()
hilft nicht weiter.
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Mach es mal so:

Code: Alles auswählen

!/usr/bin/env python

import Tkinter as tk
import string, re, os, sys, subprocess

class MyApplication(tk.Frame):

   def __init__(self, master):
      tk.Frame.__init__(self, master)
      self.grid( row=0, column=0)      
     
      option_01_list     = ["a_01","a_02"]      
      self.option_01_var = tk.StringVar(self)
      self.option_01_var.set(option_01_list[0])
      self.option_01     = tk.OptionMenu(self, self.option_01_var, \
                           *option_01_list, command=self.change_second_optionmenu)

      self.option_03_list= ["b_01","b_02"]
      self.option_03_var = tk.StringVar(self)
      self.option_03_var.set(self.option_03_list[0])
      self.option_03     = tk.OptionMenu(self, self.option_03_var, \
                           *self.option_03_list)
                           
      self.option_01.grid(row=0, column=1)                          
      self.option_03.grid(row=2, column=1)
           
   
   def change_second_optionmenu(self, *arg):
      self.option_03.destroy()
      self.option_03_list= ["c_01","c_02"]
      self.option_03_var.set(self.option_03_list[0])
      self.option_03 = tk.OptionMenu(self, self.option_03_var, \
                           *self.option_03_list)
      self.option_03.grid(row=2, column=1)
                         
# ------------------------------------------------------------------------------

if __name__ == "__main__":
   root = tk.Tk()
   root.geometry("200x200")
   root.config(background="#123456")
   root.option_add('*font',("courier",8,"bold"))
   root.title("lsf_submitting")
   display = MyApplication(root)
   root.mainloop()
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Thats amazing, it works!

Ich hab mal beide codes mit tkdiff verglichen, da sind doch einige Aenderungen drin,
und so stellen sich Fragen, z.B.
1)
Wieso muss die Klasse auf einmal tk.Frame vererben?

2)
Wie setze ich das ganze dann auf, wenn ich mehrere Frames aufsetzen muss,
wegen der Positionierung einiger geplanter Buttons oder Textwidgets usw.

3)
Du hast aus dem LabelFrame einen Frame gemacht, bekomme ich das ganze auch
mit einem Labelframe hin?

4)
Kannst du mir das Kommando erklaeren: tk.Frame.__init__(self, master)
Ich initialisiere doch gerade schon, warum doppelt?

5)
Warum muss man hier:
self.option_03 = tk.OptionMenu(self, self.option_03_var, ...
kein parent mehr angeben, sondern schreibt einfach nur noch: self


Vielen Dank fuer deine Zeit!
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

ts7343 hat geschrieben:1)
Wieso muss die Klasse auf einmal tk.Frame vererben?
Musst du nicht, nur wenn du eine Application schreibst, die sogar noch ein Objekt names frame_entries hat also ein Frame hinterlegt wird, dann ist dies nur eine logischere Variante - also hat nichts mit deinem eigentlichen Problem zutun
ts7343 hat geschrieben:2)
Wie setze ich das ganze dann auf, wenn ich mehrere Frames aufsetzen muss,
wegen der Positionierung einiger geplanter Buttons oder Textwidgets usw.
Bau ein Frame auf das Frame, also tk.Frame(self, ...), da self jetzt deine Application auf Basis eines Frames ist.
deine App. könntest du jetzt zB mit einem anderen Geometry (pack, grid, place) drauf packen, da die so unabhängig voneinander ist.
ts7343 hat geschrieben:3)
Du hast aus dem LabelFrame einen Frame gemacht, bekomme ich das ganze auch
mit einem Labelframe hin?
nenn es einfach tk.LabelFrame statt tk.Label - sry mein Fehler
ts7343 hat geschrieben:4)
Kannst du mir das Kommando erklaeren: tk.Frame.__init__(self, master)
Ich initialisiere doch gerade schon, warum doppelt?
Wird bei der Vererbung von tk.Frame benötigt, also generell bei Vererbung benötigt.
Dient dazu um den Konstruktor der Basis Klasse aufzurufen um von dieser zu erben.
ts7343 hat geschrieben:5)
Warum muss man hier:
self.option_03 = tk.OptionMenu(self, self.option_03_var, ...
kein parent mehr angeben, sondern schreibt einfach nur noch: self
Habe ich oben schon mal erwähnt dein self ist jetzt dein Frame damit kannst du solange deiner member funktion mit def ...(self, ...) definiert wird, kannst du nun immer auf self und damit auf den Frame zugreifen.

Eine alternative zum Zerstören des OptionMenus würde noch das überschreiben der Option "menu", allerdings geht das nicht ohne weiteres, da mir leider aus einem unerklärlichen Grund eine abhängigkeiten verloren geht.
Aber, zudem müsste man auf eine interne Klasse von tkinkter zugreifen. Also ist die Option das Menü neu anzulegen die einfachste und die bessere.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Vielen Dank, das hat mir schon sehr sehr weiter geholfen.

Meinst du es waere dolle gefaehrlich, wenn ich bei meinen anderen Frames, wo ich nicht
so viel Flexibilitaet brauche einfach wieder "master" als parent angebe,
siehe z.b. der button_frame oder der viewing_frame.

Aber was ich nun nicht verstehe ist folgendes:
Meine Applikation ist nun auf Basis eines Frames sagst du. Nun ist es so, dass ich
in der viewing_frame definition wieder "master" als parent angebe:

Code: Alles auswählen

      frame_viewing    = tk.LabelFrame(master)
jedoch kann ich das widget "text_01" trotzdem mit self definieren und es bleibt trotzdem im "viewing_frame":

Code: Alles auswählen

      self.text_01     = tk.Text(frame_viewing,yscrollcommand = self.scr_text_01.set)
und weiterfuehrend kann ich von der separaten funktion drauf zugreifen:

Code: Alles auswählen

      self.text_01.insert('end', line)

Ist das dolle unsauber oder kann man das so machen?

Hier der komplette Testcode:

Code: Alles auswählen

#!/usr/bin/env python

import Tkinter as tk
import string, re, os, sys, subprocess

class MyApplication(tk.LabelFrame):

   def __init__(self, master):

#     option frame   
      tk.LabelFrame.__init__(self, master)     
      label_01           = tk.Label(self)
      option_01_list     = ["a_01","a_02"]      
      self.option_01_var = tk.StringVar(self)
      self.option_01_var.set(option_01_list[0])
      self.option_01     = tk.OptionMenu(self, self.option_01_var, \
                           *option_01_list, command=self.change_second_optionmenu)

      self.option_03_list= ["b_01","b_02"]
      self.option_03_var = tk.StringVar(self)
      self.option_03_var.set(self.option_03_list[0])
      self.option_03     = tk.OptionMenu(self, self.option_03_var, \
                           *self.option_03_list)

      self.grid(          row=0, column=0)                                 
      label_01.grid(      row=0, column=0)
      self.option_01.grid(row=1, column=0)                          
      self.option_03.grid(row=2, column=0)
      
      label_01.configure(text = "label_01" )
      self.configure(    text = "lsf options"  ) 
      
#     button frame      
      frame_buttons    = tk.LabelFrame(master)
      button_03        = tk.Button(frame_buttons, command = master.quit)
      
      frame_buttons.grid(row=1, column=0)
      button_03.grid(    row=0, column=0)
      
      frame_buttons.configure(text = "Exit Frame"  ) 
      button_03.configure(    text = "Exit")

#     viewing frame
      frame_viewing    = tk.LabelFrame(master)
      self.scr_text_01 = tk.Scrollbar(frame_viewing)
      self.text_01     = tk.Text(frame_viewing,yscrollcommand = self.scr_text_01.set)
      self.scr_text_01.config(command=self.text_01.yview)
      self.scr_text_01.grid(row=0, column=1, sticky="N"+"S")
      self.text_01.grid(row=0, column=0)
      frame_viewing.grid(row=2, column=0, columnspan=2)

      frame_viewing.configure(text          = "History"  )
      self.text_01.configure(width  = 30)
      self.text_01.configure(height = 10)   
   
   
   
   def change_second_optionmenu(self, *arg):
      self.option_03.destroy()
      self.option_03_list= ["c_01","c_02"]
      self.option_03_var.set(self.option_03_list[0])
      self.option_03 = tk.OptionMenu(self, self.option_03_var, \
                           *self.option_03_list)
      self.option_03.grid(row=2, column=0)
      line = self.option_03_var.get()
      self.text_01.insert('end', line)
      self.text_01.see('end')
                         
# ------------------------------------------------------------------------------

if __name__ == "__main__":
   root = tk.Tk()
   root.geometry("300x300")
   root.config(background="#123456")
   root.option_add('*font',("courier",8,"bold"))
   root.title("lsf_submitting")
   display = MyApplication(root)
   root.mainloop()
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ebend genau das solltest du lassen.

Eine Application musst du unterscheiden von einem Programm.
So hat die Application nur ein TopLevel (ein Fenster- gut in deinem Fall ein Frame ), dieses kann man nun natürlich unterteilen.
Ein Programm kann im Gegensatz zur Application mehrere TopLevel besitzen, also mehrer Applicationen.

Also musst du

Code: Alles auswählen

frame_viewing = tk.LabelFrame(master)
in

Code: Alles auswählen

frame_viewing = tk.LabelFrame(self)
umschreiben.

Das master darfst du hier nicht mehr nutzen, da es Application übergreifend werden könnte, außer du willst das natürlich dann ist das etwas anderes, aber da gibt es auch schönere Sachen.

Tipp: Mach mal bei deinen Frames ein "borderwidth=3" und eine "relief='groove'" rein, dann siehst du den aufbau deiner Frames.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Hallo Xynon1,

vielen Dank, ich hab es jetzt sauberer aufgesetzt und muss nur aufpassen, dass ich mit dem Grid Layout Manager
immer beachte, dass alle Folgeframes auch auf self gehen,

zur Zeit komm ich erst einmal klar, vielleicht eine letzte Frage, weil ich ca 15 OptionMenues aufsetzen werde,
welche aber nicht dynamisch sein muessen. Der Code blaeht sich langsam auf, wenn ich alle OptionMenues separat
definiere, dann noch die Grid-Definitionen und die Configure-Definitionen fuer Farbe usw. Da hab ich mir gedacht,
dass manche ja auch mit Listen solche Sachen erschlagen.

Kann man das auch mit OptionMenues als Liste aufsetzen und wenn ja wie? Meine groessten Probleme wuerden liegen:
1) Definition der OptionMenue Liste selbst
2) Wie bekommt dann jedes Element der OptionMenu-Liste ihre spezielle Liste an Optionen
3) Wie greife ich mit get dann darauf zu, um diverse Werte abzugreifen?


many thanx
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

zB. könntest du die Optionsmenüs automatisch erzeugen:

Code: Alles auswählen

import Tkinter as tkinter

root = tkinter.Tk()

# Einzelne Listen, die du warscheinlich schon hast
option_01_list= ["a_01","a_02"]
option_02_list= ["b_01","b_02"]
option_03_list= ["c_01","c_02"]
#...

# Zusammenfassen in einer Liste
values = []
values.append(option_01_list)
values.append(option_02_list)
values.append(option_03_list)
#...

# Liste fuer alle Kontrollvariablen anlegen
controllers = []

# Ueber alle Listen iterieren und mitzaehlen welche Liste es ist
for row, value in enumerate(values):

    # Neue Kontrollvariable anlegen
    stringvar = tkinter.StringVar(root)

    # Ersten Wert der Liste setzen, wie du das vorher schon gemacht hast
    stringvar.set(value[0])

    # Optionsmenu anlegen
    om = tkinter.OptionMenu(root, stringvar, *value)

    # Optionsmenu auf das Widget packen
    om.grid(row=row, column=0)

    # Kontrollvariale zu der dafuer angelegten Liste hinzufuegen
    controllers.append(stringvar)
Zugriff per get würde dann zB so erfolgen:

Code: Alles auswählen

for stringvar in controllers:
    print(stringvar.get())
Edit: Achja, dieser Zauber nennt sich Schleifen :mrgreen:
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Au Backe, das ist echt noch eine andere Liga, in der ich leider noch nicht spiele.
Hab ein bisschen Angst das komplett so umzustellen, wer weiss in was fuer pitfalls ich da reinlaufe.

Ich werd es mal Schritt fuer Schritt versuchen, ich hoffe du bist auf "notify" fuer diesen Thread,
falls ich voellig verzweifeln sollte.

Thanx, i will keep you informed
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Ja, :D

Denk daran das da oben war nur ein Beispiel.
Du kannst die auch so lassen wie es jetzt ist, wichtig ist nur das du in diesem Fall alles in einem Container hast, also in der Liste.
Denn darüber kann man dann iterieren und somit auf jedes Element zugreifen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Benutzeravatar
ts7343
User
Beiträge: 69
Registriert: Mittwoch 5. Mai 2010, 13:48

Hab mal ueber mein Gesamtkonzept geschaut, ich werd es ueber die Liste und so einen Loop
mal mit den Labels versuchen, aber mit Optionmenues trau ich es mir noch nicht zu,
allein wegen der Punkte:

- das anfangs angesprochene Thema mit dem dynamischen Update, ob das so funktionieren wird,
wenn ich das Optionmenue in einer Liste habe, dass man das optionmenue einfach so destroyed?
- verschiedene commands fuer verschiedene optionmenues, wo man halt im loop immer abfragen
muesste welches denn nun gerade in arbeit ist,
Xynon1
User
Beiträge: 1267
Registriert: Mittwoch 15. September 2010, 14:22

Es gibt immer noch eine Menge anderer Möglichkeiten, zB. in einer Dictionary.

Aber das ist bei einem solchen Aufwand eventuell nicht sehr praktisch, da du über all andere Angaben hast.
Das iterieren geht aber trotzdem über das master Frame, allerdings sollte man da am Anfang die Finger von lassen.
Der Einfachheit halber, kannst du dir eine Liste "option_menus" anlegen und deine gerade erstellten Optionsmenüs dort mit .append(...) hinzufügen und dann einfach über diese iterieren.

Achso nein, du kannst es nicht einfach so "destroyen" denn nur die Referenz wird darauf behalten, also index ermitteln und mit der neuen überschreiben oder alte löschen und neue hinten anhängen.
Traue keinem Computer, den du nicht aus dem Fenster werfen kannst.
Xynon auf GitHub
Antworten