Steuerung zwischen Toplevel und Tk?

Fragen zu Tkinter.
Antworten
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

bisher habe ich von meinem Hauptprogramm (Tkinter) GUIś mit Tk aufgerufen.
Nun möchte ich aber die gleichen GUIś auch direkt mit Toplevel aufrufen.

In meinen GUIś, steht folgendes am Anfang:

Code: Alles auswählen

    def __init__(self, controller, i2name, mytext):

        #self.my_win = tk.Toplevel()
        self.my_win = tk.Tk()
Gibt es da eine Möglichkeit, dass die GUI selbst erkennt ob diese über eine andere GUI oder direkt aufgerufen wird?

Grüße Nobuddy
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also Toplevel sind Fenster der GUI. So wie Frames welche sind. Der Unterschied ist nur, dass die Frames in einem bereits bestehenden Fenster dargestellt werden und Toplevel als extra Fenster. Aber von der Handhabung her ist kein besonderer Unterschied. Und der Code würde heissen:

Code: Alles auswählen

def __init__(self, controller, i2name, mytext):
 
        self.my_win = tk.Tk()
        self.my_top = tk.Toplevel(self.my_win)
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo Alfons Mittelmeyer,

wenn ich die GUI direkt ausführe und 'tk.Toplevel()' aktiv ist, öffnet sich ein zweites Tk-Fenster.
Dies ist nicht der Fall, wenn die Einstellung 'tk.Tk()' ist.
Das Gleiche ist auch der Fall, wenn ich aus meinem Hauptprogramm (Einstellung 'tk.Tk()'), die zweite GUI mit der Einstellung 'tk.Tk()' aufrufe.
Daher ist hier die Einstellung 'tk.Toplevel()' richtig, während beim direkten Ausführen der zweiten GUI wieder die Einstellung 'tk.Tk()' richtig ist.
Ist hoffentlich nicht verwirrend, meine Ausführung.

Dein Vorschlag, hilft mir hier nicht, außer ich hätte ihn falsch verstanden.

Nach diesem hier, würde es funktionieren:

Code: Alles auswählen

    def __init__(self, controller, tk_status, i2name, mytext):

        tk_check = {'tk' : tk.Tk, 'top' : tk.Toplevel}
        self.my_win = tk_check[tk_status]()

    ......
    ...


def main():
    try:
        tk_status = 'top'
        Controller(tk_status, i2name, mytext).run()
    except NameError:
        tk_status = 'tk'
        i2name = {'24' : 'also und so', '01' : 'xyc', '13' : 'vw',
            '222' : 'aha', '02' : 'soso', '10' : 'juhu'}
        mytext = 'Ausgabe Kundennummer'
        Controller(tk_status, i2name, mytext).run()


if __name__ == '__main__':
    main()
Hoffe aber, dass es noch einen besseren Weg gibt.

Grüße Nobuddy

PS: Habe in main den 'tk_status' verbessert, der war zuerst falsch.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also, was ich nicht verstehe, ist das mit einer zweiten GUI. Du benutzt tkinter, das ist doch nur eine GUI.
Und normalerweise bekommt man die GUI mit:
root = tk.Tk()

Wenn man ein Toplevel haben möchte, aber nicht will, dass man das Hauptfenster sieht, dann kann man das Hauptfenster verbergen mit:
root.withdraw()

Wenn man allerdings dieses tut, muss man darüber nachdenken, was beim Schließen des Toplevels geschehen soll. Da das Hauptfenster dann nicht zu sehen ist, kann man es dann nicht mit der Maus schließen und so die Anwendung beeenden.

Deshalb sollte man eine eigene Toplevel Klasse ableiten und dort die destroy-Methode überschreiben,etwa:

Code: Alles auswählen

def destroy(self):
    tk.Toplevel.destroy(self)
    root.destroy()
Oder man macht dann das Hauptfenster wieder sichtbar - mit root.deiconify(), etwa um eine 'andere GUI' - wie Du das nennst - auszuwählen.

Am Anfang schriebst Du, ob man erkennen kann, um welchen Fenstertyp es sich handelt. Ja, etwa mit type(my_window) oder besser mit issinstance(my_window,tk.Toplevel)

Außerdem, ein Applikationsfenster bekommst Du immer, sogar hier:

Code: Alles auswählen

import tkinter as tk
root = tk.Button(text="Button")
root.place(x=10,y=10)
root.mainloop()
Günstig wäre jedoch, mit tk.Tk() zu starten, damit man sich eine Referenz dazu merken kann. Naja über root.master, wäre die auch zu bekommen.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Jetzt kommen wir der Sache schon näher.
Mein Hauptprogramm besteht aus einer GUI mit 'root = tk.Tk()'.
Öffne ich nun aus dem Hauptprogramm externe GUIś, so sind diese 'root = tk.Toplevel()', da es sonst mit 'root = tk.Tk()' bei diesen zu Fehlern und doppelten Fenstern führt.

Ich werde mir Deine Vorschläge testen, vielleicht ist dies dann schon die Lösung.
Das Hauptprogramm sollte aber immer sichtbar sein.

Grüße Nobuddy
Benutzeravatar
Kebap
User
Beiträge: 686
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Nobuddy hat geschrieben: Mein Hauptprogramm besteht aus einer GUI mit 'root = tk.Tk()'.
Öffne ich nun aus dem Hauptprogramm externe GUIś, (...)
Könntest du mir kurz ein Anwendungsbeispiel beschreiben? Geht es um ein zweites "Popup" Fenster, das kurzzeitig neben dem Hauptprogramm erscheinen soll?
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo Kebap,

aus meinem Hauptprogramm (HP) heraus, öffne ich diverse externe GUIś, je nach Anwendung.
Diese geben Informationen/Datensätze aus dem HP aus, die dann geändert oder ausgewählt werden und wieder in das HP zurückfließen, zur weiteren Verarbeitung.
Wenn Du das so nennen möchtest, sind dann die externen GUIś wie Popup-Fenster, werden nach Gebrauch wieder beendet durch ein 'Close-Button'.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Kann eine Tkinter-GUI wissen, wie sie angesteuert wird, ob über eine andere Tkinter-GUI oder direkt.
Das war eigentlich, auf das was ich heraus wollte, evtl. habe ich mich da zu undeutlich ausgedrückt.
Nach meinem jetzigen Stand, ist das wohl so nicht möglich.
Mit meinem letzten geposteten Code, lässt sich das umsetzen, auch wenn es etwas umständlich ist.

Vielleicht gibt es da jetzt doch noch von Euch, die richtige Idee?

Grüße Nobuddy
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Du solltest Deine Terminologie etwas ändern.
Denn erstens hast Du keine andere GUI sondern nur eine mit verschiedenen Popup Fenstern.
Und zweitens hast Du keine Ansteuerung, sondern Du greifst direkt auf Widgets zu.

Das ist so, wie wenn Du etwas in eine Variable schreibst und dann die Frage stellst, ob die Variable wissen kann, wer auf sie zugreift oder sie ansteuert.

Wenn Du wirklich so etwas wie eine andere GUI haben willst, die Du ansteuerst, dann musst Du eine Ansteuerung implementieren.
Mit der anderen GUI kommunizierst Du dann nur über Messages und bei den Messages implementierst Du einen Absender.

Ja dann gibt es eine andere GUI die dann auch wissen kann, wer auf sie zugreift. Das Message System läßt sich auch so erweitern, dass die andere GUI in einer anderen Applikation oder gar auf einem anderen Rechner läuft, etwa mit Intranet oder Internet verbunden.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Meine Erklärungen und die richtigen Fachausdrücke dazu, sind mangelhaft ... sorry!
Alfons Mittelmeyer hat geschrieben:Wenn Du wirklich so etwas wie eine andere GUI haben willst, die Du ansteuerst, dann musst Du eine Ansteuerung implementieren.
Mit der anderen GUI kommunizierst Du dann nur über Messages und bei den Messages implementierst Du einen Absender.

Ja dann gibt es eine andere GUI die dann auch wissen kann, wer auf sie zugreift. Das Message System läßt sich auch so erweitern, dass die andere GUI in einer anderen Applikation oder gar auf einem anderen Rechner läuft, etwa mit Intranet oder Internet verbunden.
Das hört sich gut an, kann es mir aber momentan nicht vorstellen, wie Du das meinst. Hast Du eine kleines Beispiel dafür?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ein kleines komplettes Code Beispiel habe ich nicht, aber ich kann Dir erklären worum es geht.

Ich habe eine komplexe GUI die aus mehreren Komponenten besteht. Und die einzelnen Komponenten kennen einander nicht - könnten auch räumlich getrennt auf verschiedenen Rechnern laufen, was sie aber bei meinem GUI Designer nicht tun. Und die Komponenten brauchen voneinander gewisse Informationen.

Wenn der User ein Widget erzeugt hat und das mittels Maus über die Applikation bewegt, dann sollen ein bis zwei Komponenten des GUI Designers dessen Koordinaten anzeigen, während das Widget bewegt wird. Beim Widget war ich so frei, dieses mit mit <Button-1>, <ButtonRelease-1> und <B1-Motion> (dafür nehme ich inzwischen einen timer mit after) Events zu versehen, sodass es bewegt werden kann und dabei auch seine Position meldet.

Wie geht das nun, wenn das Widget keinerlei Ahnung von GuiDesigner Komponenten hat? Und die Komponenten auch nichts darüber wissen, ob der User das Widget bewegt?

Das geht ganz einfach, das Widget braucht nur eine Positionsmeldung durchgeben, während es bewegt wird und zwar eine Positionsmeldung für ihm unbekannte Empfänger.
Dafür nimmt man einen Event Broker. Dieser arbeitet nach dem publisher subscriber Prinzip.
Subscriber registrieren sich für den Empfang von bestimmen Messages oder Events. Und wenn ein Publisher solche Messages sendet, empfangen sie diese.

Der Code zum Ausgeben der Widget Koordinaten in Spinboxen meines Place Layouts sieht so aus:

Code: Alles auswählen

    def set_yx_entries(SpinboxX = widget("X"), SpinboxY = widget("Y")):
        if this().Layout==PLACELAYOUT:
            SpinboxX.delete(0,END)
            SpinboxX.insert(0,this().getlayout("x"))
            SpinboxY.delete(0,END)
            SpinboxY.insert(0,this().getlayout("y"))

    # set X,Y spinboxes to X,Y layout values of the widget
    do_receive('POSITION_CHANGED',set_yx_entries)
Hier registriert sich das Place Layout Modul als Empfänger für die Message mit der ID 'POSITION_CHANGED'. Da bei meinem System immer ein Widget das ausgewählte Widget ist, erübrigt sich die Übermittlung, um welches Widget es sich handelt, oder welche Koordinaten das Widget hat. Die Funktion kann für das selektierte Widget this() mittels getlayout('x') und getlayout('y') seber nachsehen. Aber genausogut hätte man die Koordinaten auch als Message Parameter mit übermitteln können.

Und was macht das Widget, während es bewegt wird? Es sendet einfach nur:

Code: Alles auswählen

send('POSITION_CHANGED')
Durch Komma getrennt hätte man auch Message Parameter (bei mehreren als Tuple oder List) mitsenden können.

Es ist also so etwas wie ein Event. Aber ausgelöst wird dieser, indem irgend ewas aufruft: send(message_id,message_parameters)
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo Alfons Mittelmeyer,

danke für Deine ausführliche Erklärung! :wink:

Das muss ich mir mal durch den Kopf gehen lassen, wie ich das für mich umsetzen kann.
Wenn das Widget von mehreren Modulen gleichzeitig genutzt wird, bleibt dann die Zuordnung jeweils bestehen?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

An einer Zuordnung kann man doch eh nichts ändern. Die Widgets sind bei tkinter doch bereits beim Anlegen einem Parent zugeordnet. Das ist nicht so, wie bei .Net, wo diese Zuordnung erst beim Layout geschiehrt, und man das Widget von einem Frame in einen anderen Frame verschieben kann.

Eine Auswahl ist keine Änderung einer Zuordnung. Ich habe nur eine eigene tkinter Erweiterung und grundlegende Eigenschaften dieser Erweiterung sind, dass es genau ein jeweils ausgewähltes Widget gibt und eine von tkinter weitgehend unabhängige Parent Children Verzeichnis Struktur. Tkinter benutzt ein Children Liste. Ich benutze ein Children Dictionary, in dem die Widgets mit Namen verzeichnet sind. Und diese Namen lassen sich jederzeit ändern, Namen von tkinter Widgets hingegen nicht, wenn man von 'name' Gebrauch macht. Namen soll man eigentlich bei einer normalen Anwendung nicht mehr ändern. Beim Erstellen einer GUI allerdings schon.

Ach so, ich kann jederzeit mit Hilfe der Children Verzeichnisse durch die ganze GUI gehen und alle Widgets beliebig bearbeiten und modifizieren. Mit tkinter ohne Erweiterung ginge das natürlich auch. Nur da kann man den Namen nicht ändern und für Namen gibt es dabei auch Regeln. Meine Regel für Namen ist nur, dass es ein String sein muss, aber etwas anderes läßt sich im GuiDesigner eh nicht eingeben. Und wenn jemand den selben Namen mehrmals im selben Container Widget verwendet, ist auch OK.
Benutzeravatar
Kebap
User
Beiträge: 686
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Um ein Popup Fenster zu öffnen, gibt es sehr einfache Möglichkeiten.

Ich vermute, deine falsche Terminologie lockt dich auf falsche Spuren.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Antworten