Ändern des Textes eines Labels durch eine anderes Objekt

Fragen zu Tkinter.
Antworten
Umsteiger73
User
Beiträge: 12
Registriert: Mittwoch 29. April 2015, 18:12

Hallo
Ich mache meine ersten Versuche mit Tkinter. Die Idee ist ein Objekt, dass den Text eines Labels während der Laufzeit ändert. Das versuche ich mit folgendem Code:

Code: Alles auswählen

import tkinter as tk

class TextSetter():

    def __init__(self):
        self.single_label = SingleLabel()

        self.change_label()

    def change_label(self):
        self.single_label.label_variable.set("Come on!") # does not work
        self.single_label.set_label("What the ...!") # does not work either

class SingleLabel():

    def __init__(self):
        self.root = tk.Tk()
        self.label_variable = tk.StringVar()
        
        self.start()

    def set_label(self,text):
        self.label_variable.set(text)

    def start(self):
        self.label = tk.Label(self.root,textvariable=self.label_variable)
        self.label.pack()
        self.label_variable.set("So far so good")
        
        self.root.mainloop()

def main():
    TextSetter()
    

if __name__ == '__main__':
    main()
Ich erwarte eigentlich, dass der Text im Label "What the ...!" oder zumindest "Come on!" sein sollte. Statdessen bleibt der Text "So far so Good".
Es kommt keine Fehlermeldung. Ich mache irgend ein Denkfehler. Kann mir allenfall jemand auf die Sprünge helfen?

Danke im Voraus

Gruss

Thomas
BlackJack

@Umsteiger73: Der Aufruf von `mainloop()` kehrt erst zurück wenn das Fenster geschlossen wird, deshalb wird nichts ausgeführt was nach dem Aufruf von `SingleLabel()` kommt bis das Fenster geschlossen wird.
Umsteiger73
User
Beiträge: 12
Registriert: Mittwoch 29. April 2015, 18:12

@BlackJack

Danke für den Hinweis. Genau das habe ich gebraucht. Jetzt kann ich in der richtigen Richtung nach einer Lösung suchen.

Gruss

Thomas
Umsteiger73
User
Beiträge: 12
Registriert: Mittwoch 29. April 2015, 18:12

Nachtrag, für alle die es interessiert.

Ein rekursiver Aufruf der after Methode und das umstellen der Mainloop hat mir weiter geholfen. Spannenderweise ist auch das erwartete Problem mit der Rekursionstiefe nicht aufgetreten.

Code: Alles auswählen

import tkinter as tk

class SingleLabel(object):
    
    def __init__(self,root):
        self.root = root
        self.data = Countdown()
    
    def draw_label(self):
        frame=tk.Frame(self.root)
        frame.pack()
        self.text=tk.Label(frame)
        self.text.pack()

    def refresh_label(self):
        self.number= self.data.number()
        self.text.configure(text=self.number)
        self.root.after(1000, self.refresh_label) # des Pudels Kern

class Countdown(object):
    
    def __init__(self):
        self.countdown = [1,2,3,4,5,6,7,8,9,10]
    
    def number(self):
        if self.countdown:
            return self.countdown.pop()
        else:
            self.countdown = [1,2,3,4,5,6,7,8,9,10]
            return [0]

root=tk.Tk()
sl = SingleLabel(root)
sl.draw_label()
sl.refresh_label()
root.mainloop()
@BlackJack: Nochmals danke für den Fingerzeig.

Gruss Thomas
Sirius3
User
Beiträge: 18328
Registriert: Sonntag 21. Oktober 2012, 17:20

@Umsteiger73: Außerhalb von __init__ sollten keine neuen Attribute mehr hinzugefügt werden. Warum gibt es die draw_label-Methode überhaupt separat? number an self zu binden ist schlicht weg falsch. number in Countdown sollte besser next_number heißen, um den zustandsändernden Charakter der Methode deutlich zu machen. Auf Modulebene sollten kein Code direkt ausgeführt werden, das heißt, ab Zeile 32 sollte alles in eine Funktion wandern, die üblicherweise main genannt wird und per

Code: Alles auswählen

if __name__ == '__main__':
    main()
aufgerufen wird.
BlackJack

@Umsteiger73: Man könnte/sollte aus dem `Countdown` vielleicht auch ein iterierbares Objekt machen statt eine Methode zur Verfügung zu stellen die nicht nicht den dafür üblichen Namen trägt. Und das mit der Liste und `pop()` ist eine *sehr* eigenartige Implementierung. Das die literale Liste zweimal im Quelltext steht ist zudem auch fehleranfällig. Eigentlich bräuchte man gar keine eigene Klasse dafür sondern höchstens eine Funktion die ein passendes iterierbares Objekt mit eingebauten Funktionen (`range()`, `reversed()`) und Mitteln aus der Standardbibliothek (`itertools.cycle()`) erstellt.

Es ist eher ungewöhnlich das die Initialisierung eines Objekts GUI-Elemente auf einem Widget erstellt das als Argument übergeben wurde. Da hat der Aufrufer ja keinerlei Kontrolle was diese Methode in einem eigentlich *übergeordneten* Widget anstellt. Normalerweise würde man von einem Container-Widget erben, dort *drin* anzeigen was man lustig ist, und dem *Aufrufer* die Entscheidung überlassen wie und wo er das dann in einem übergeordneten Widget anordnet. Kann ja sein das am Ende mehrere solcher `SingleLabel` (was soll das eigentlich bedeuten?) in einem Frame in einer `grid()`-Anordnung angezeigt werden sollen. Dazu sollte man die `SingleLabel`-Klasse nicht ändern müssen.

Ungetestet:

Code: Alles auswählen

import tkinter as tk
from itertools import cycle


class CountdownUI(tk.Frame):

    def __init__(self, root):
        tk.Frame.__init__(self, root)
        self.countdown_label = tk.Label(self)
        self.countdown_label.pack()
        self.countdown_numbers = cycle(reversed(range(11)))
        self.event_id = None

    def _update_countdown(self):
        self.countdown_label['text'] = next(self.countdown_numbers)
        self.event_id = self.after(1000, self._update_countdown)

    def start_countdown(self):
        if self.event_id is None:
            self._update_countdown()

    def stop_countdown(self):
        if self.event_id is not None:
            self.after_cancel(self.event_id)


def main():
    root = tk.Tk()
    countdown_ui = CountdownUI(root)
    countdown_ui.pack()
    countdown_ui.start_countdown()
    root.mainloop()


if __name__ == '__main__':
    main()
Umsteiger73
User
Beiträge: 12
Registriert: Mittwoch 29. April 2015, 18:12

Hallo

Wow, danke für die Denkanstösse. Werde sie berücksichtigen.

Gruss

Thomas
Antworten