Checkbutton innerhalb zweitem widget wird nicht richtig dargestellt

Fragen zu Tkinter.
Antworten
LtTuvok
User
Beiträge: 18
Registriert: Freitag 11. August 2017, 09:53

Hallo

Ich habe im Forum keinen passenden Beitrag zu meinem Problem gefunden.
Ich habe ein widget (Main) und innerhalb diesem starte ich ein zweites (NextFrame). In diesem NextFrame möchte ich Checkbuttons erstellen und true oder false setzen. Die Checkbutton-Variablen werden auch gesetzt, aber die Häckchen werden nicht dargestellt.
Ich habe mal das einfachste Beispiel zu meinem Problem kodiert.
Kann mir bitte jemand helfen?

Viele Grüße,
Tuvok

Code: Alles auswählen


from tkinter import *

def SetCheckBoxTrue(CheckBoxVar):
	print(CheckBoxVar.get())
	CheckBoxVar.set(True)
	print(CheckBoxVar.get())
	
def SetCheckBoxFalse(CheckBoxVar):
	print(CheckBoxVar.get())
	CheckBoxVar.set(False)
	print(CheckBoxVar.get())
	
def Next():
	NextFrame=Tk()
	
	Check1=IntVar()
	Checkbutton(NextFrame,variable=Check1).grid()
	
	Button(NextFrame,text="Set True",command=lambda x=Check1: SetCheckBoxTrue(x)).grid()
	Button(NextFrame,text="Set False",command=lambda x=Check1: SetCheckBoxFalse(x)).grid()
	
	NextFrame.mainloop()

Main=Tk()

Button(Main,text="Start Next",command=Next).grid()

Main.mainloop()

Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

In Zeile 25 legst Du ein tkinter Gui an mit Main = Tk()
In Zeile legst Du nochmals eine an innerhalb der mainloop der ersten tkinter GUI. Damit ist alles kaputt.

Brauchst Dich nicht wundern, wenn dann einiges nicht funktioniert.

So schreibt man den Code auch nicht, aber das würde gehen:

Code: Alles auswählen

from tkinter import *
 
Main=Tk()

def SetCheckBoxTrue(CheckBoxVar):
    print(CheckBoxVar.get())
    CheckBoxVar.set(True)
    print(CheckBoxVar.get())
   
def SetCheckBoxFalse(CheckBoxVar):
    print(CheckBoxVar.get())
    CheckBoxVar.set(False)
    print(CheckBoxVar.get())
   
def Next():
    NextFrame=Frame(Main)
    NextFrame.grid()
   
    Check1=IntVar()
    Checkbutton(NextFrame,variable=Check1).grid()
   
    Button(NextFrame,text="Set True",command=lambda x=Check1: SetCheckBoxTrue(x)).grid()
    Button(NextFrame,text="Set False",command=lambda x=Check1: SetCheckBoxFalse(x)).grid()
   
 
Button(Main,text="Start Next",command=Next).grid()
Main.mainloop()
Oder statt Frame nimm ein Toplevel denn das hattest Du vielleicht gemeint
Zuletzt geändert von Alfons Mittelmeyer am Freitag 11. August 2017, 10:40, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@LtTuvok: es darf in jedem Programm nur ein Tk-Objekt geben. Weitere Fenster kannst Du mit Toplevel erzeugen.

Anmerkungen zum Code: *-Importe sind böse, weil sie unkontrolliert Namen in den Namensraum laden. Bei Tk wird üblicherweise "import tkinter as tk" benutzt und alle Tk-Objekte per tk.xy angesprochen. Namenskonvention für Variablen und Funktionen ist klein_mit_unterstrich. Mit AnfangsGrossBuchstaben bzw. CamelCase werden nur Klassennamen geschrieben.

Code: Alles auswählen

import tkinter as tk
 
def set_check_box_true(check_box_var):
    print(check_box_var.get())
    check_box_var.set(True)
    print(check_box_var.get())
   
def set_check_box_false(check_box_var):
    print(check_box_var.get())
    check_box_var.set(False)
    print(check_box_var.get())
   
def next():
    nextframe = tk.Toplevel()
   
    check_var = tk.IntVar()
    tk.Checkbutton(nextframe, variable=check_var).grid()
   
    tk.Button(nextframe,text="Set True", command=lambda: set_check_box_true(check_var)).grid()
    tk.Button(nextframe,text="Set False", command=lambda: set_check_box_false(check_var)).grid()
 
def main():
    root = tk.Tk()
    tk.Button(root, text="Start Next", command=next).grid()
    root.mainloop()

if __name__ == '__main__':
    main()
LtTuvok
User
Beiträge: 18
Registriert: Freitag 11. August 2017, 09:53

Hallo

Ja genau. Toplevel war das was ich gesucht habe. Vielen Dank Alfons!
Ich versuche gerade mir Python mit Tutorials, Google und einem eigenen Projekt selbst beizubringen. Da mache ich noch diese blöden Fehler. Das mache ich schon nicht mehr!

Danke auch Sirius3. Ich versuche mir die neue Schreibweise anzugewöhnen. Schön finde ich es nicht, aber ich sehe es ein, dass es besser ist, wenn ein allgemeiner Konsens eingehalten wird!

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

LtTuvok hat geschrieben: Ich versuche gerade mir Python mit Tutorials, Google und einem eigenen Projekt selbst beizubringen. Da mache ich noch diese blöden Fehler. Das mache ich schon nicht mehr!
Wenn Du einen schnellen Überblick haben willst und schnell eine GUI erstellen willst, dann schau doch mal meinen GuiDesigner an:
viewtopic.php?f=18&t=41011

Da kannst Du auch alles gleich ausprobieren, ohne Code zu schreiben. Die GUI kannst Du dann speichern und auch exportieren, wobei dann eine gute Code Struktur erzeugt wird.

Im Feld call Code(self) kann man dann eine Funktion oder Klasse angeben, am besten in einem anderen Modul, etwa

mein_code.meine_funktion

daraus wrde dann:

mein_code.meine_funktion(self)

oder man kann auch eintragen:

mein_code.meine_funktion(self,self.checkbutton1,self.checkbutton2)

Dann wird bei Gui Neugenerierung der Code nicht überschrieben.
BlackJack

@LtTuvok: Die Anmerkungen beziehen sich auf den überarbeiteten Code von Sirius3:

`next()` ist der Name einer eingebauten Funktion, den sollte man besser nicht überschreiben.

Die beiden `set_check_box_*()`-Funktionen unterscheiden sich nur durch den Wahrheitswert — den könnte man auch als Argument übergeben.

Im Zusammenhang mit den ``lambda``-Funktionen lohnt auch ein Blick auf `functools.partial()`. Das würde hier keinen Unterschied machen, aber die Funktion sollte man für die Fälle kennen, bei denen es einen Unterschied macht zu welchem Zeitpunkt die Werte gebunden beziehungsweise aufgelöst werden.

Code: Alles auswählen

import tkinter as tk

 
def set_variable_value(variable, value):
    print(variable.get())
    variable.set(value)
    print(variable.get())

   
def start_next():
    next_frame = tk.Toplevel()
   
    check_var = tk.IntVar()
    tk.Checkbutton(next_frame, variable=check_var).grid()
   
    tk.Button(
        next_frame,
        text='Set True',
        command=lambda: set_variable_value(check_var, True),
    ).grid()
    tk.Button(
        next_frame,
        text='Set False',
        command=lambda: set_variable_value(check_var, False),
    ).grid()


def main():
    root = tk.Tk()
    tk.Button(root, text='Start Next', command=start_next).grid()
    root.mainloop()


if __name__ == '__main__':
    main()
LtTuvok
User
Beiträge: 18
Registriert: Freitag 11. August 2017, 09:53

Hallo

Danke an alle für die Tipps. Ich werde mir den GuiDesigner ansehen!

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

@LtTuvok: bringen wir doch in Dein Programm ein wenig Struktur rein. Das ist berichtigt von tK() und kein zweites mainloop mehr. Und dazu starten wir den GuiDesigner:

Code: Alles auswählen

from DynTkInter import *
 
Main=Tk()

def SetCheckBoxTrue(CheckBoxVar):
    print(CheckBoxVar.get())
    CheckBoxVar.set(True)
    print(CheckBoxVar.get())
   
def SetCheckBoxFalse(CheckBoxVar):
    print(CheckBoxVar.get())
    CheckBoxVar.set(False)
    print(CheckBoxVar.get())
   
def Next():
    NextFrame=Toplevel(Main)
   
    Check1=IntVar()
    Checkbutton(NextFrame,variable=Check1).grid()
   
    Button(NextFrame,text="Set True",command=lambda x=Check1: SetCheckBoxTrue(x)).grid()
    Button(NextFrame,text="Set False",command=lambda x=Check1: SetCheckBoxFalse(x)).grid()
   
 
Button(Main,text="Start Next",command=Next).grid()
Main.mainloop('guidesigner/Guidesigner.py')
Bei der root in der config unter "call Code(self)" tragen wir ein "appcode.app".

Dann ergibt das exportiert:

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

#============= imports call Code ===================

try:
    import appcode
except ImportError:
    print("imports call Code: module 'appcode' doesn't exist")


class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        # widget definitions ===================================
        self.button = tk.Button(self,text='Start Next')
        self.button.grid(row=0)
        # call Code ===================================
        try:
            appcode.app(self)
        except NameError:
            print("call Code: function or methode 'appcode.app' doesn't exist")

if __name__ == '__main__':
    Application().mainloop()
Dann haben wir schon einmal einwandfreien Code für einen Button, der nichts tut, weil wir den command noch nicht programmiert haben.

Im GuiDesigner drücken wir auf diesen Button und dann geht das Toplevel auf. Da tut der Button schon etwas. Über den Menüpunkt "Special -> Toproot" und dann bei 'toplevel' auf Pfeil rechts können wir in das Toplevel überwechseln.

Dort erfassen wir dann in der config unter "call Code(self)" 'appcode.nexr"

Exportiert ergibt das dann:

top.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

#============= imports call Code ===================

try:
    import appcode
except ImportError:
    print("imports call Code: module 'appcode' doesn't exist")


class toplevel(tk.Toplevel):

    def __init__(self,master,**kwargs):
        tk.Toplevel.__init__(self,master,**kwargs)
        # widget definitions ===================================
        self.checkbutton = tk.Checkbutton(self)
        self.checkbutton.grid(row=0)
        self.button = tk.Button(self,text='Set True')
        self.button.grid(row=1)
        self.button_2 = tk.Button(self,text='Set False')
        self.button_2.grid(row=2)
        # call Code ===================================
        try:
            appcode.next(self)
        except NameError:
            print("call Code: function or methode 'appcode.next' doesn't exist")


if __name__ == '__main__':
    toplevel(tk.Tk()).mainloop()
Und schon haben wir wieder einwandfreien Code für einen Checkbutton und zwei Buttons, die nichts tun, weil der Code für die commands noch fehlt.

Aber wenn man noch den Code schreibt, dann geht es:

appcode.py

Code: Alles auswählen

from functools import partial
from tkinter import IntVar
import top

def app(self):
    self.button['command'] = partial(top.toplevel,self)

def next(self):
    self.check_value = IntVar()
    self.checkbutton['variable'] = self.check_value
    self.button['command'] = partial(self.check_value.set,True)
    self.button_2['command'] = partial(self.check_value.set,False)
Das ist einfacher wie zuerst, denn man schreibt nur appcode.py. Das andere wird generiert.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: und was bringt das jetzt, ein relativ simples Programm für einen Anfänger auf vier unterschiedliche Dateien aufzuteilen, wobei die Aufteilung in GUI und appcode (wie ich schon oft geschrieben habe) falsch herum ist. Wenn man einen GUI-Builder verwenden will, dann soll der dort erzeugte Code im Hintergrund bleiben, weil man sich auf die Logik konzentrieren will. Du schiebst aber die Logik in irgendein Hintergrundmodul und packst davor den automatisch erzeugten GUI-Code.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: und was bringt das jetzt, ein relativ simples Programm für einen Anfänger auf vier unterschiedliche Dateien aufzuteilen
Du hast dich verzählt, es ist aufgeteilt auf drei Module. Das ist aber nur so wegen dem Toplevel, denn Toplevel zählen im GUI Designer als eine Art eigenständige App
Sirius3 hat geschrieben:wobei die Aufteilung in GUI und appcode (wie ich schon oft geschrieben habe) falsch herum ist. Wenn man einen GUI-Builder verwenden will, dann soll der dort erzeugte Code im Hintergrund bleiben, weil man sich auf die Logik konzentrieren will. Du schiebst aber die Logik in irgendein Hintergrundmodul und packst davor den automatisch erzeugten GUI-Code.
Es ist genau richtig herum aufgeteilt. Im Modul appcode - es können auch beliebig viele Module sein, das man programmiert, kann man sich völlig auf die Logik konzentrieren. Die GUI selber - die man auch auf beliebig viele Module aufteilen kann, ist da völlig im Hintergrund. Oder meinst Du, weil man ja beim Aufruf die GUI zuerst startet, dass deswegen die GUI im Vordergrund ist?

Wenn man dagegen Gui Teile vom Code her hereinlädt, bildet man in seinem Code die Struktur der GUI. Und das ist genau nicht nur Konzentration auf die Logik, sondern auch sehr auf diie GUI.

Wenn man dann die GUI umstellen will, muss man dann seinen Code umstellen.

So geht es aber ganz einfach. Im GuiDesigner Widget (normalerweise Container Widget oder alle Widgets eines Container Widgets - Container von innen) auswählen dann über den Menü Button 'more' ein cut, dann anderen Platz wählen und 'paste'. So einfach stellt man die Gui Struktur um ohne eine Zeile seines Codes für die Logik ändern zu müssen.

Oder man macht ein cut und löscht dann alles - oder man bendet nach em cut den Gui Designer und startet ihn neu - und macht dann das paste und speichert es und exportiert es dann in ein eignes Modul. Das Zusammenfügen wenn das Programm gestartet wird zum Laufen kann man im GuiDesigner über den Eintrag baseclass bewerkstelligen.

Gui in Teile zerlegen, oder Teile wieder vereinen kann man alles im GuiDesigner erledigen ohne den Code der Logik ändern zu müssen.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Dann fangen wir halt mal an. appcode, also der Teil, den man selber schreibt wird in einem automatisch erzeugten Skript mit einem __main__-Teil importiert:

Code: Alles auswählen

try:
    import appcode
except ImportError:
    print("imports call Code: module 'appcode' doesn't exist")
Dieses try-except macht absolut keinen Sinn, da das Programm, wenn appcode nicht existiert, keinen Sinn macht. Das führt nämlich später dazu, dass appcode nicht existiert und Du einen NameError abfangen mußt. Du hast also einen automatisch generierten Code, der ein selbst geschriebenes Modul importiert. Das ist genau das, was ich geschrieben habe und das jeden (außer Dir) verwirrt.

Dann gibt es da das Modul appcode, das Funktionen enthält, die auf tiefste Interna von irgendetwas zugreifen, deren Herkunft aber am Modul appcode überhaupt nicht sehen kann. Wieder etwas sehr verwirrendes. Und in der Funktion »next« fügst Du auch noch diesem Objekt »self« weitere Attribute hinzu, ohne absehen zu können, was das für Auswirkungen hat.

Dein Konstrukt enthält einfach so viele fachliche Fehler, die das Programm undurchschaubar, nicht wartbar und fehleranfällig macht, dass das niemand mit etwas Programmiererfahrung anfassen möchte.

Um das etwas sinnvoller zu gestalten und das Programm vom Kopf auf die Füße zu stellen, könnte man es so lösen:

Code: Alles auswählen

# -*- coding: utf-8 -*-
# Datei gui_application.py, automatisch generiert, nicht ändern!
import tkinter as tk
 
class Application(tk.Tk):
    def __init__(self, **kwargs):
        tk.Tk.__init__(self, **kwargs)
        self.button = tk.Button(self, text='Start Next')
        self.button.grid(row=0)
 
if __name__ == '__main__':
    Application().mainloop()

Code: Alles auswählen

# -*- coding: utf-8 -*-
# Datei gui_toplevel.py, automatisch generiert, nicht ändern!
import tkinter as tk

class SecondWindow(tk.Toplevel):
    def __init__(self, master, **kwargs):
        tk.Toplevel.__init__(self, master, **kwargs)
        self.checkbutton = tk.Checkbutton(self)
        self.checkbutton.grid(row=0)
        self.button = tk.Button(self, text='Set True')
        self.button.grid(row=1)
        self.button_2 = tk.Button(self, text='Set False')
        self.button_2.grid(row=2)
 
if __name__ == '__main__':
    SecondWindow(tk.Tk()).mainloop()
und dann noch das Hauptprogramm, das man selber schreiben muß:

Code: Alles auswählen

from functools import partial
from tkinter import IntVar
from gui_application import Application
from gui_toplevel import SecondWindow
 
def create_second(app):
    check_value = IntVar()
    win = SecondWindow(app)
    win.checkbutton['variable'] = check_value
    win.button['command'] = partial(check_value.set, True)
    win.button_2['command'] = partial(check_value.set, False)

def main():
    app = Application()
    app.button['command'] = partial(create_second, app)
    app.mainloop()
 
if __name__ == '__main__':
    main()
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:Dann fangen wir halt mal an. appcode, also der Teil, den man selber schreibt wird in einem automatisch erzeugten Skript mit einem __main__-Teil importiert:

Code: Alles auswählen

try:
    import appcode
except ImportError:
    print("imports call Code: module 'appcode' doesn't exist")
Dieses try-except macht absolut keinen Sinn, da das Programm, wenn appcode nicht existiert, keinen Sinn macht. Das führt nämlich später dazu, dass appcode nicht existiert und Du einen NameError abfangen mußt. Du hast also einen automatisch generierten Code, der ein selbst geschriebenes Modul importiert. Das ist genau das, was ich geschrieben habe und das jeden (außer Dir) verwirrt.
Es macht total viel Sinn. Man kann das Programm starten und die GUI sehen. Für Teile der GUI ist der Code eventuell schon implementiert und sie funktionieren. Für andere Teile aber noch nicht. Was da ist geht, was noch nicht da ist, eben nicht.

Es kommt vor, dass Du mit jemand korrespondierst und ein Teil seiner Anwendung funktioniert noch nicht so, wie er will. Er will Dir aber nicht sein ganzes Programm schicken mit allen imports sondern nur den Teil, worauf es ankommt. Was hast Du jetzt davon, wenn Du dieses Try nicht hast? Es kommt ein Crash und man kann mit diesem Teilprogramm nichts anfangen.

So geht aber, was Da ist, und wenn etwas nicht da ist, dann crasht das Programm nicht. Dann ist eben nur eine Teilfunktionalität nicht da.

Dieses Try wegen des Moduls muss ja nicht das Einzige gewesen sein. Es kann ja noch weitere Module geben, die da sind und auch funktionieren!

Und das NameError abfangen ist nicht nur wegen dem, dass das Modul nicht da ist. Es kann auch das Modul da sein, aber die Funktion oder auch Klasse oder Methode ist eben noch nicht implementiert.

Man kann also sein Programm Stück für Stück aufbauen ohne dass es crasht, bevor nicht alles fertig ist. Gut, man hätte die Funktion ja auch nicht zuvor bereits eintragen müssen bevor sie fertig ist. Das kann man hier halt schon. Aber jedenfalls sehr sinnvoll, wenn Jemand Dir nur ein Teil des Programmes schickt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben: Dann gibt es da das Modul appcode, das Funktionen enthält, die auf tiefste Interna von irgendetwas zugreifen, deren Herkunft aber am Modul appcode überhaupt nicht sehen kann. Wieder etwas sehr verwirrendes.
Man würde natürlich die Funktion nicht einfach nur 'next' nennen, sondern ihr einen Namen geben, der Aufschluß darüber gibt, mit welchem Teil der GUI er korrespondiert. Man kann auch noch einen Kommentar dazu schreiben. Allerdings welches GUI Modul das ist, könnte etwas problematisch sein. Denn man könnte den dazu gehörigen GUI Teil in ein anderes Modul verlegen und dann vergessen den Kommentar nachzuziehen. Am Besten ist da ein Betriebssystem mit einem Dateimanager wie dem vom Raspberry. Einfach Dateien suchen mit *.py und als Inhalt Modulnamen + .Funktionsname eingeben und schon hat man es. Wie das unter Windows geht, weiß ich leider nicht.

Nö, das war es nicht. Ich empfehle dass man zu jedem Gui Modul auch ein dazugehöriges code Modul hat das gleich heißen soll mit dem Zusatz '_code'. Und die Funktionen oder Klassen sollen gleich heißen wie die Klassen für die Container Widgets mit dem Zusatz '_init'. Dann weiß man exakt, was zu was gehört. Ich habe nur festgestellt, dass man am liebsten den Code in ein Modul packt, während man viele GUI Module hat.
[/quote]
Sirius3 hat geschrieben: Und in der Funktion »next« fügst Du auch noch diesem Objekt »self« weitere Attribute hinzu, ohne absehen zu können, was das für Auswirkungen hat.
Du tust ja jetzt so, als wenn Du nicht wüßtest, welcher GuiTeil das ist und welche Widgets definiert sind. Oder siehst Du vielleicht dort ein Widget mit Attributnamen self.check_value?

Code: Alles auswählen

        self.checkbutton = tk.Checkbutton(self)
        self.checkbutton.grid(row=0)
        self.button = tk.Button(self,text='Set True')
        self.button.grid(row=1)
        self.button_2 = tk.Button(self,text='Set False')
        self.button_2.grid(row=2)
Sirius3 hat geschrieben: Dein Konstrukt enthält einfach so viele fachliche Fehler, die das Programm undurchschaubar, nicht wartbar und fehleranfällig macht, dass das niemand mit etwas Programmiererfahrung anfassen möchte.
Das ist eine völlig aus der Luft gegriffene Behauptung. Diese Programme enthalten keine fachlichen Fehler, sind völlig durchschaubar, extrem gut wartbar überhaupt nicht fehleranfällig.

Allerdings habe ich das Beispiel des Users durch die Exportierung gejagt ohne daran etwas zu machen, also sinnvolle Namen für die Widgets und die Klassen und auch die Funktionen zu vergeben.

Gegen Dein Beispiel ist in diesem Falle nichts zu sagen. Da brauchen wir ein anderes Beispiel um die Vorzüge meines Ansatzes aufzuzeigen. Das Beispiel des Users ist dazu völlig ungeeignet mit nur einem Button, bei dem ein Toplevel aufgeht. Es geht schließlich um die Entwicklung komplexer GUIs.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: Dieser Ansatz ist vom Aufbau der GUI unabhängig. Der GUI Aufbau ist jederzeit änderbar ohne Code Änderung und übersichtlich sollte es bei richtiger Benennung auch sein.

Das ist jetzt die GUI. Ich habe mal die Namen mit exportiert, damit ich das Script wiederbekomme, sollte ich es verlieren.

message_gui.py

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk

#import DynTkInter as tk # for GuiDesigner


#============= imports call Code ===================

try:
    import message_gui_code
except ImportError:
    print("imports call Code: module 'message_gui_code' doesn't exist")

# Application definition ============================

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        # widget definitions ===================================
        self.button_frame = ButtonFrame(self,name='#0_button_frame')
        self.button_frame.pack(side='left')

class ButtonFrame(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.call_code = 'message_gui_code.button_frame_init'
        # widget definitions ===================================
        self.frame_show_hide = FrameShowHide(self,name='#1_frame_show_hide')
        self.frame_show_hide.grid(row=0, rowspan=3, sticky='nesw', column=1)
        self.button_show_checkframe = tk.Button(self,name='#2_button_show_checkframe',text='Show Frame')
        self.button_show_checkframe.grid(row=0, sticky='nesw')
        self.button_hide_checkframe = tk.Button(self,name='#3_button_hide_checkframe',text='Hide Frame')
        self.button_hide_checkframe.grid(row=1, sticky='nesw')
        # call Code ===================================
        try:
            message_gui_code.button_frame_init(self)
        except NameError:
            print("call Code: function or methode 'message_gui_code.button_frame_init' doesn't exist")


class FrameShowHide(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.call_code = 'message_gui_code.frame_show_hide_init'
        self.config(bd=1, relief='solid')
        # widget definitions ===================================
        self.checkbutton = tk.Checkbutton(self,name='#4_checkbutton')
        self.checkbutton.grid(row=0, sticky='nesw')
        self.button_setcheck = tk.Button(self,name='#5_button_setcheck',text='Set True')
        self.button_setcheck.grid(row=1, sticky='nesw')
        self.button_clear_check = tk.Button(self,name='#6_button_clear_check',text='Set False')
        self.button_clear_check.grid(row=2, sticky='nesw')
        # call Code ===================================
        try:
            message_gui_code.frame_show_hide_init(self)
        except NameError:
            print("call Code: function or methode 'message_gui_code.frame_show_hide_init' doesn't exist")


if __name__ == '__main__':
    #Application().mainloop('guidesigner/Guidesigner.py') # for GuiDesigner
    Application().mainloop()
Hatte vergessen die Klassennamen einzutrasgen, wurden aber automatisch aus den Widgetnamen (Attributnamen) mit CamelCase generiert. Das Code Modul heißt wie das GUI Modul mit _code angefügt und die aufgerufene Funktion wurde nach dem Container Widget benannt. Also ist das leicht aufzufinden und zuzuordnen.

Hier ist ein Frame, der durch den Button mit der Aufschrift 'Hide Frame' verborgen werden soll und mit dem anderen Button wieder gezeigt werden soll. Der GUI Aufbau soll sich später noch ändern. Es ist nicht davon auszugehen, dass dieser Frame später noch an dieser Stelle sein wird. Daher wurde ein Eventbroker eingesetzt. Hier zuerst der Code

message_gui_code.py

Code: Alles auswählen

from functools import partial
from tkinter import IntVar
from eventbroker_simple import publish, subscribe

def button_frame_init(self):
    self.button_show_checkframe['command'] = lambda: publish('SHOW_CHECK_FRAME',True)
    self.button_hide_checkframe['command'] = lambda: publish('SHOW_CHECK_FRAME',False)

def frame_show_hide_init(self):

    def show_hide(shall_show):
        self.grid() if shall_show else self.grid_remove()

    subscribe('SHOW_CHECK_FRAME',show_hide)
    
    self.checkbutton.variable = IntVar()
    self.checkbutton['variable'] = self.checkbutton.variable
    self.button_setcheck['command'] = partial(self.checkbutton.variable.set,True)
    self.button_clear_check['command'] = partial(self.checkbutton.variable.set,False)


Hier ein ganz einfacher Eventbroker

eventbroker_simple.py

Code: Alles auswählen

class EventBroker():
    def __init__(self):
        self._dictionary_ = {}

    def subscribe(self,message_id,callback):
        self._dictionary_[message_id] = callback

    def publish(self,message_id,*args,**kwargs):
        self._dictionary_[message_id](*args,**kwargs)

eventbroker = EventBroker()
publish = eventbroker.publish
subscribe = eventbroker.subscribe
Dann wurde der GUI Aufbau geändert.

Code: Alles auswählen

# -*- coding: utf-8 -*-

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk
try:
    from tkinter import ttk
except ImportError:
    import ttk

#import DynTkInter as tk # for GuiDesigner
#import DynTtk as ttk    # for GuiDesigner


#============= imports call Code ===================

try:
    import message_gui_code
except ImportError:
    print("imports call Code: module 'message_gui_code' doesn't exist")

# Application definition ============================

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        # widget definitions ===================================
        self.button_frame = ButtonFrame(self,name='#0_button_frame')
        self.button_frame.pack(side='left')

class ButtonFrame(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.call_code = 'message_gui_code.button_frame_init'
        # widget definitions ===================================
        self.button_show_checkframe = tk.Button(self,name='#1_button_show_checkframe',text='Show Frame')
        self.button_show_checkframe.grid(sticky='nesw', row=0)
        self.button_hide_checkframe = tk.Button(self,name='#2_button_hide_checkframe',text='Hide Frame')
        self.button_hide_checkframe.grid(sticky='nesw', row=1)
        self.notebook = Notebook(self,name='#3_notebook')
        self.notebook.grid(column=1, sticky='nesw', row=0, rowspan=3)
        # call Code ===================================
        try:
            message_gui_code.button_frame_init(self)
        except NameError:
            print("call Code: function or methode 'message_gui_code.button_frame_init' doesn't exist")


class Notebook(ttk.Notebook):

    def __init__(self,master,**kwargs):
        ttk.Notebook.__init__(self,master,**kwargs)
        self.config(width=300)
        # widget definitions ===================================
        self.panedWindow = Panedwindow(self,name='#4_panedWindow')
        self.text = tk.Text(self,name='#5_text',height=1, width=1)
        self.text.delete(1.0, tk.END)
        self.text.insert(tk.END,'')
        self.add(self.panedWindow,text='page 1')
        self.add(self.text,text='text')

class Panedwindow(tk.PanedWindow):

    def __init__(self,master,**kwargs):
        tk.PanedWindow.__init__(self,master,**kwargs)
        self.config(sashwidth='8', handlepad=14, bd=0, handlesize='11', showhandle=1)
        # widget definitions ===================================
        self.labelFrame = tk.LabelFrame(self,name='#6_labelFrame',text='pane 1')
        self.labelFrame2 = Labelframe2(self,name='#7_labelFrame2')
        self.add(self.labelFrame,stretch='first')
        self.add(self.labelFrame2,stretch='never')

class Labelframe2(tk.LabelFrame):

    def __init__(self,master,**kwargs):
        tk.LabelFrame.__init__(self,master,**kwargs)
        self.config(text='pane 2')
        # widget definitions ===================================
        self.frame_show_hide = FrameShowHide(self,name='#8_frame_show_hide')
        self.frame_show_hide.grid(row=0)

class FrameShowHide(tk.Frame):

    def __init__(self,master,**kwargs):
        tk.Frame.__init__(self,master,**kwargs)
        self.call_code = 'message_gui_code.frame_show_hide_init'
        self.config(bd=1, relief='solid')
        # widget definitions ===================================
        self.checkbutton = tk.Checkbutton(self,name='#9_checkbutton')
        self.checkbutton.grid(sticky='nesw', row=0)
        self.button_setcheck = tk.Button(self,name='#10_button_setcheck',text='Set True')
        self.button_setcheck.grid(sticky='nesw', row=1)
        self.button_clear_check = tk.Button(self,name='#11_button_clear_check',text='Set False')
        self.button_clear_check.grid(sticky='nesw', row=2)
        # call Code ===================================
        try:
            message_gui_code.frame_show_hide_init(self)
        except NameError:
            print("call Code: function or methode 'message_gui_code.frame_show_hide_init' doesn't exist")


if __name__ == '__main__':
    #Application().mainloop('guidesigner/Guidesigner.py') # for GuiDesigner
    Application().mainloop()
Am Code für die Logik in message_gui_code.py war Null zu ändern. Wie willst Du das mit Deinem Ansatz machen?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: Empfehlenswert wäre die Beützung des Eventbrokers auch in der GUI

Da braucht nur immer ein Modul in der GUI importiert werden. Und dann ist der Code dazu auch modulunabhängig, etwa:

Code: Alles auswählen

from functools import partial
from tkinter import IntVar
from eventbroker_simple import publish, subscribe

def button_frame_init(self):
    self.button_show_checkframe['command'] = lambda: publish('SHOW_CHECK_FRAME',True)
    self.button_hide_checkframe['command'] = lambda: publish('SHOW_CHECK_FRAME',False)

subscribe('MESSAGE_GUI.BUTTON_FRAME_INIT',button_frame_init)

def frame_show_hide_init(self):

    def show_hide(shall_show):
        self.grid() if shall_show else self.grid_remove()

    subscribe('SHOW_CHECK_FRAME',show_hide)
    
    self.checkbutton.variable = IntVar()
    self.checkbutton['variable'] = self.checkbutton.variable
    self.button_setcheck['command'] = partial(self.checkbutton.variable.set,True)
    self.button_clear_check['command'] = partial(self.checkbutton.variable.set,False)
    
subscribe('MESSAGE_GUI.FRAME_SHOW_HIDE_INIT',frame_show_hide_init)
Wenn man es so macht, kann man dann nämlich Codeteile ohne Änderung der Funktionalität und ohne Änderung von GUI Einträgen einfach in andere Module nach Belieben umverteilen.

Wenn man beginnt, schreibt man den Code in ein Modul. Wenn das Programm umfangreicher wird, teilt man den Code auf.

Das ist die Art und Weise, wie man komplexe GUI Programme schreibt. Wenn der Code rein die Funktionalität nur jeweils eiines Containerwidgets behandelt und GUI und Code ansonsten unabhängig sind, der Code und die GUI beliebig umverteilt werden können, dann ist nichts mehr komplex und ansonsten komplexe Programme sind mit Leichtigkeit zu entwickeln.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Naja stimmt auch nicht ganz, dass dann nichts mehr komplex wäre. Ich habe eine Maske mit etwa 25 Widgets. Der Code dazu sind über 2000 Zeilen. Das ist ist das Grid Layout.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Brauchbarkeit von GUI Buildern für komplexe Programme.

Dazu wieder dieses Beispiel:

Code: Alles auswählen

from functools import partial
from tkinter import IntVar
from eventbroker_simple import publish, subscribe
 
def button_frame_init(self):
    self.button_show_checkframe['command'] = lambda: publish('SHOW_CHECK_FRAME',True)
    self.button_hide_checkframe['command'] = lambda: publish('SHOW_CHECK_FRAME',False)
 
#subscribe('MESSAGE_GUI.BUTTON_FRAME_INIT',button_frame_init)
 
def frame_show_hide_init(self):
 
    def show_hide(shall_show):
        self.grid() if shall_show else self.grid_remove()
 
    subscribe('SHOW_CHECK_FRAME',show_hide)
   
    self.checkbutton.variable = IntVar()
    self.checkbutton['variable'] = self.checkbutton.variable
    self.button_setcheck['command'] = partial(self.checkbutton.variable.set,True)
    self.button_clear_check['command'] = partial(self.checkbutton.variable.set,False)
   
subscribe('MESSAGE_GUI.FRAME_SHOW_HIDE_INIT',frame_show_hide_init)
Das was hier steht, hat man normalerweise in der __init__ nach den Widget Definitionen. Es steht nur wegen der GUI Generierung in einem extra File, damit es nicht überschrieben wird,

Wenn man die GUI umbaut ob generiert oder programmiert, geht natürlich die __init__ mit und man kann die GUI daher beliebig umbauen.

Andere GUI Builder bieten auch an, dass man command und event Binding bereits im GUI Builder vornehmen kann. Dadurch erübrigt sich die Funktion button_frame_init.

Aber mit command und event Binding ist es nicht getan, denn frame_show_hide_init ist mehr als bloßes command und event binding. Wenn so ein GUI Builder auch für die Fortsetzung der __init__ einen Aufruf oder ein Signal vorsieht und das Interface für den Zugriff auf die Widgets in Ordnung ist, ist er für komplexe Programme brauchbar.

Wenn aber so ein Aufruf oder Signal fehlt und man deswegen die GUI nachbilden muß, nur um dann dorthin zu gelangen, dann sollte man so einen GUI Builder vergessen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Es ist aber auch richtig, dass diese Programmteile in extra Files stehen. Denn es geht normalerweise auch um mehr als nur GUI Logik. Normalerweise hat man auch eine Geschäftslogik. Und hier sind dann auch die Interfaces zwischen GUI und Geschäftslogik zu impementieren.
Antworten