Frage zur GUI-Erstellung und Steuerung

Fragen zu Tkinter.
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Hallo,

ich habe meine GUI erstellt, welche folgendes Muster besitzt.

Code: Alles auswählen


#Laden einzubindender Module
import GUI_support

def vp_start_gui():
	GUI().mainloop() 

class GUI(tk.Tk):
	def __init__(self, **kwargs):
		tk.Tk.__init__(self, **kwargs)
		
		#Setzen des Hauptmenüs für das Programm
		self.MainMenu = MenuGUI(self)
		self['menu'] = self.MainMenu

		#Setzen der Toolbar
		self.Toolbar = ToolbarGUI(self)
		self.Toolbar.place(relx=0.0, y=0.0, height=contentframeheigth+4, relwidth=1.0)
		self.Toolbar.configure(relief=GROOVE)
		self.Toolbar.configure(borderwidth="2")
		
		#Setzen der DatenEingabe
		self.DatenEingabe = DatenEingabeGUI(self)
		#self.DatenEingabe.place(relx=0.0, y=contentframeheigth, relheight=1.0, relwidth=1.0)
		
class MenuGUI(tk.Menu):
	def __init__(self, parent, **kwargs):
		tk.Menu.__init__(self, parent, **kwargs)

		global Lang
		Lang = IntVar()
		
		global CheckA, CheckB, CheckC, CheckD
		CheckA = IntVar()
		CheckB = IntVar()
		CheckC = IntVar()
		CheckD = IntVar()		
		
		
		datei = tk.Menu(self, tearoff=0, activebackground = _bgcolor)
		self.add_cascade(menu=datei, label=languagefile[0])
		datei.add_command(command=GUI_support.NewFile, label=languagefile[1])
		datei.add_command(command=self.action, label=languagefile[2])
		datei.add_command(command=self.action, label=languagefile[3])
		datei.add_command(command=self.action, label=languagefile[4])
		datei.add_command(command=self.action, label=languagefile[5])
		datei.add_separator()
		datei.add_command(command=self.action, label=languagefile[6])
		datei.add_separator()
		datei.add_command(command=self.action, label=languagefile[7])			

class ToolbarGUI(tk.Frame):
	def __init__(self, parent, **kwargs):
		tk.Frame.__init__(self, parent, **kwargs)
		
		#hier nicht dargestellt, um Code kurz zu halten

class DatenEingabeGUI(tk.Frame):
	def __init__(self, parent, **kwargs):
		tk.Frame.__init__(self, parent, **kwargs)

		#hier nicht dargestellt, um Code kurz zu halten

if __name__ == '__main__':
    vp_start_gui()	

Code: Alles auswählen

def NewFile():
	#Loeschen alter Daten und Laden neuer Form
	for widget in GUI().DatenEingabe.winfo_children(): #löschen aller Einträge
		widget.destroy()
	GUI().DatenEingabe = DatenEingabeGUI(self)
	GUI().DatenEingabe.place(relx=0.0, y=contentframeheigth, relheight=1.0, relwidth=1.0)
Ich möchte nun die Funktionalitäten implementieren, wie z.B. "Neue Datei", "Datei öffnen", "Datei schließen". Wie ich das innerhalb der class mache ist klar. Ich denke aber daran die Menüsterung durchaus in eine andere Datei auszulagern, aber da kennt Python dort nicht die Nomenklatur meiner GUI-KOnstruktion.
Kann mir da jemand weiterhelfen?

Grüße von Stefan
Zuletzt geändert von Anonymous am Mittwoch 17. Mai 2017, 07:59, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Zuerst kommt es darauf an, um was es bei der neuen Datei geht. Ist das auch Gui Definierung, sodaß Du nur die Gui in zwei Teile teilen willst, weil sonst das Script zu lang und dadurch unübersichtlich wird?

Oder soll es bei der neuen Datei darum gehen, dass Du GUI und eine sonstige Funktionalität voneinander trennen willst, sodass dort nur Kommunikation mit der Gui erfolgt, wie Daten- und Steuersignalübermittlung.

Also habe gesehen, dass es um die Menüsteuerung geht. Die Frage hat sich dadurch erledigt.

Meinst Du das?

Code: Alles auswählen

import menu_gui

class GUI(tk.Tk):
    def __init__(self, **kwargs):
        tk.Tk.__init__(self, **kwargs)
       
        #Setzen des Hauptmenüs für das Programm
        self.MainMenu = menu_gui.MenuGUI(self)
        self['menu'] = self.MainMenu
Zuletzt geändert von Alfons Mittelmeyer am Mittwoch 17. Mai 2017, 08:49, insgesamt 1-mal geändert.
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Hallo Alfons,

im Prinzip denke ich an beides. Ich habe zunächst die Dateneingabe erstellt. Nun möchte die Menüfunktionalitäten erstellen. Aber die Datenausgabe würde wahrscheinlich auslagern.
Ich denke, dass ich die Menfunktionalitäten auch auslagere, soweit möglich bzw. sinnvoll.

Gruß Sefan
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

stefanxfg hat geschrieben:

Code: Alles auswählen

def NewFile():
	#Loeschen alter Daten und Laden neuer Form
	for widget in GUI().DatenEingabe.winfo_children(): #löschen aller Einträge
		widget.destroy()
	GUI().DatenEingabe = DatenEingabeGUI(self)
	GUI().DatenEingabe.place(relx=0.0, y=contentframeheigth, relheight=1.0, relwidth=1.0)
Ja Alfons, die Ansteuerung funktioniert schon. Aber dann wird eine Fehlermeldung ausgegeben, dass die GUI()-Class nicht definiert sei.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

stefanxfg hat geschrieben: Ja Alfons, die Ansteuerung funktioniert schon. Aber dann wird eine Fehlermeldung ausgegeben, dass die GUI()-Class nicht definiert sei.
stefanxfg hat geschrieben:Ja Alfons, die Ansteuerung funktioniert schon. Aber dann wird eine Fehlermeldung ausgegeben, dass die GUI()-Class nicht definiert sei.
Wäre sowieso falsch. Mit GUI() würdest Du ja nicht auf Deine GUI zugreifen, sondern eine neue Gui Anwendung initialisieren.

Hier ein Beispiel wie man so etwas schön trennen kann.

Beispiel Gui:

Code: Alles auswählen

# -*- coding: utf-8 -*-
import tkinter as tk
import mainmenu
import eventbroker

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.eventbroker = eventbroker.EventBroker()
        self.mainmenu = mainmenu.Mainmenu(self,self.eventbroker)
        self['menu'] = self.mainmenu
        self.status = tk.Label(self,bg='white', height=3, width=40)
        self.status.pack()

        self.eventbroker.subscribe('OPEN_FILE',self.open_file)
        self.eventbroker.subscribe('CLOSE_FILE',self.close_file)

    def open_file(self):
        self.status['text'] = 'Datei geöffnet'

    def close_file(self):
        self.status['text'] = 'Datei geschlossen'

Application().mainloop()
Beispiel Menü im File mainmenu.py

Code: Alles auswählen

import tkinter as tk

class Mainmenu(tk.Menu):

    def __init__(self,master,eventbroker,**kwargs):
        tk.Menu.__init__(self,master,**kwargs)
        self.eventbroker = eventbroker
        self.add_command(label='open',command=self.call_open)
        self.add_command(label='close',command=self.call_close)

    def call_open(self):
        self.eventbroker.publish('OPEN_FILE')

    def call_close(self):
        self.eventbroker.publish('CLOSE_FILE')
Und das funktioniert mit einem Event Broker im file: eventbroker.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)

BlackJack

@stefanxfg: Schmeiss am besten diesen ganzen Code weg, vergiss den GUI-Designer der diesen Unsinn produziert hat und lerne erst einmal die Grundlagen von Python, also zum Beispiel wie Module und Objektorientierung in Python funktionieren. Sonst wirst Du keine Freude mit diesem Programm haben, und nicht weit kommen.

Zu der Fehlermeldung: Wo hast Du in dem Modul denn definiert was `GUI` bedeutet? Wenn das nicht im gleichen Modul definiert ist, dann steht das nicht einfach so auf magische Weise zur Verfügung. Das ist ja gerade der Sinn von Modulen, das man verschiedene Namensräume hat und nicht alles überall bekannt ist.

Wenn Du dazu das Modul importieren muss das `GUI` definiert, und das Modul aber das Modul importiert in dem die `NewFile()`-Funktion definiert wird, dann hast Du auch gleich das nächste Problem: zirkuläre Importe. Funktionieren in der Regel nicht so wie man sich das wünscht, darum sollte man das gar nicht erst machen. Es macht auch keinen Sinn etwas auf zwei Module aufzuteilen die sich dann gegenseitig importieren müssen. Wenn die sich gegenseitig brauchen, sind es auch keine zwei getrennte Module. Das ist immer ein Zeichen das etwas mit der Aufteilung nicht stimmt.

Das Du immer wieder `GUI()` aufrufst ist falsch. Das erzeugt jedes mal ein neues Hauptfenster. Du willst aber ziemlich wahrscheinlich das bestehende Hauptfenster verwenden. Das müsste dann direkt oder indirekt als Argument übergeben werden.

Das zerstören aller Kindelemente ist nicht wirklich das was Du willst. An der Stelle würde man einfach das Elternelement zerstören, was Du gar nicht machst, womit alle Kinder automatisch auch zerstört werden.

`contentframeheight` und `self` sind ebenfalls undefiniert.

Die Namenschreibweise hält sich nicht an den Style Guide for Python Code.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@stefanxfg: Schmeiss am besten diesen ganzen Code weg, vergiss den GUI-Designer der diesen Unsinn produziert
Was soll an meinem Beispiel mit der Splittung unsinnig sein und was hat das mit dem GuiDesigner zu tun?

Wenn Du allerdings den Code von stefanxfg meinst, den hat bestimmt kein GuiDesigner produziert, denn der macht derartiges - was man durchaus als Unsinnig bezeichnen kann - ganz bestimmt nicht.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Und beim Menü bin ich der Auffassung, dass man das durchaus als getrennt betrachten sollte, wenn die Anwendung komplexer ist, und dass dabei das Menü nicht in der Gui herumfuhrwerken sollte, sondern ohne Wissen über die GUI funktionieren sollte,
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Hallo Blackjack, Hallo Alfons,

ich verstehe nun. Ich werde es so machen, dass ich die GUI als Hauptdatei nehme. Damit lagere ich dann die folgenden BEstandteile als Module, welche ich mir zur Laufzeit reinhole:
- Mainmenü
- Toolbarmenü
- Eingabe
- Ausgabe

Die Menüsteuerung wird dann im Modul vereinbart. In analoger Weise wird mit der Ein- und Ausgabe verfahren.

Stimmt ihr mir zu?

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

stefanxfg hat geschrieben:Hallo Blackjack, Hallo Alfons,
ich verstehe nun. Ich werde es so machen, dass ich die GUI als Hauptdatei nehme. Damit lagere ich dann die folgenden BEstandteile als Module, welche ich mir zur Laufzeit reinhole:
- Mainmenü
- Toolbarmenü
- Eingabe
- Ausgabe
Vielleicht kannst Du einmal den Unterschied zwischen Mainmenü und Toolbarmenü erklären. An Deinem Code hatte ich bisher vom Sinn her gesehen, dass das Mainmenü mit dem Toolbarmenü identisch ist.

Ist das Toolbarmenü nun das Menü für eine Cascade oder ein zweites Mainmenü, das Du mit dem anderen zu gegebener Zeit auswechseln möchtest?
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Code: Alles auswählen

class ToolbarGUI(tk.Frame):
	def __init__(self, parent, **kwargs):
		tk.Frame.__init__(self, parent, **kwargs)
		#Modellierung Menü-Toolbar
		self.Frame10 = tk.Frame(self)
		self.Frame10.place(x=0.0, y=0.0, height=contentframeheigth, width=contentframewidth)
		self.Frame10.configure(relief=GROOVE)
		self.Frame10.configure(borderwidth="2")

		file_toolbar = Frame(self.Frame10, bd=1, relief=RAISED)	

		newimg = ImageTk.PhotoImage(Image.open("new.png"))
		openimg = ImageTk.PhotoImage(Image.open("open.png"))		
		saveimg = ImageTk.PhotoImage(Image.open("save.png"))
		saveasimg = ImageTk.PhotoImage(Image.open("saveas.png"))
		filecloseimg = ImageTk.PhotoImage(Image.open("fileclose.png"))		
		
		self.newButton = Button(file_toolbar, image=newimg, relief=FLAT, command=self.action)
		self.openButton = Button(file_toolbar, image=openimg, relief=FLAT, command=self.action)
		self.saveButton = Button(file_toolbar, image=saveimg, relief=FLAT, command=self.action)
		self.saveasButton = Button(file_toolbar, image=saveasimg, relief=FLAT, command=self.action)
		self.filecloseButton = Button(file_toolbar, image=filecloseimg, relief=FLAT, command=self.action)

		self.newButton.image = newimg
		self.newButton.pack(side = LEFT, padx=2, pady=2)
		self.openButton.image = openimg
		self.openButton.pack(side = LEFT, padx=2, pady=2)
		self.saveButton.image = saveimg
		self.saveButton.pack(side = LEFT, padx=2, pady=2)
		self.saveButton.pack(side = LEFT, padx=2, pady=2)
		self.saveasButton.image = saveasimg
		self.saveasButton.pack(side = LEFT, padx=2, pady=2)
		self.filecloseButton.image = filecloseimg
		self.filecloseButton.pack(side = LEFT, padx=2, pady=2)		

		file_toolbar.pack(side = TOP, fill=BOTH, expand=TRUE)

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

@stefanxfg: dann ist es also ein Frame und kein Menü. Dann ist es ja OK. Ich hatte nur gesehen, dass Du vorher einmal einen Pack auf das Mainwindow und auf das Mainmenu gemacht hattest und dieser Toolbar Frame war dann im Mainmenu, wo er bestimmt nicht hingehört.

OK, dann sieht Dein Ansatz gut aus.
BlackJack

@Alfons Mittelmeyer: Doch der Code den stefanxfg gezeigt hat ist von einem GUI-Designer erstellt. PAGE oder so ähnlich heisst der, und der erstellt ganz fürchterlichen Code.
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Hallo Blackjack, das stimmt schon. Ich hatte anfangs damit gearbeitet. Jedoch habe ich es schnell verworfen und die Codefragmente weiterverwendet. Mittlerweile habe ich mich auf deine Ratschläge besonnen und die überflüssigen Codezeilen gelöscht. Das bezog sich auf die vielen configure-Einstellungen (background, foregroung usw.). Aber dies ist im obigen Code nicht mehr enthalten.
Es geht mir hier um die GUI und das Handling sowie mit Objekten als auch mit Methoden. Ich möchte den Code zum Teil auslagern.

Ich werde mal was bauen und dann posten. Dann könnt ihr mal drüber schauen.

Danke an euch beide. Ihr seid super. :D
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

In Python bin ich noch nicht soweit, als dass sich nicht noch Denkfehler einstellen würden und lese deshalb eigentlich nur mit. Doch auf wie viele Codezeilen kommst Du denn in Deinem Script, so dass Du zwingend etwas auslagern müsstest?

Wenn nicht mehr als 500 bis 800 Zeilen, dann würde ich für den Anfang lieber gruppieren und die Gruppen mit ausführlichen Kommentaren teilen, als in einzelne Dateien aufzusplitten. Auslagern und Importieren würde ich mir aufheben, bis ich wüsste, was ich da mache. Und Auslagern kannst Du doch auch dann noch, wenn eine Anwendung bereits die ersten Tests durchlaufen hat.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Melewo: ob man etwas auslagert oder nicht, ist doch egal. Auslagern zu Beginn ist allerdings schon von Vorteil, weil man dann sicher die Funktionalität nicht abhängig vom Gui Aufbau anderer Teile schreibt. Wenn man später doch auslagern möchte, gäbe es dann eventuell Probleme. Ganz sicher aber ergeben sich Probleme, wenn man den Gui Aufbau noch ändert, etwa noch einen Frame dazwischenzieht.
Zuletzt geändert von Alfons Mittelmeyer am Mittwoch 17. Mai 2017, 12:35, insgesamt 1-mal geändert.
stefanxfg
User
Beiträge: 85
Registriert: Sonntag 2. April 2017, 14:11

Naja, ich habe nur die GUI an sich und es sind mehr als du geschrieben hast. Darüber hinaus kommen noch die Funktionalitäten des Programms und andere Fenster mit dazu, so dass sich das auslagern durchaus lohnt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ja mache es mit diesem EventBroker. Ich habe auch ein Programm, das besteht aus einem Mainscript und dann noch 65 Teilen. Nur wegen dieses Event Brokers (es ist ein anderer, der dieselbe Message auch an mehrere Empfänger senden kann, dafür aber komplexer) geht alles einfach und reibungslos.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

@Alfons Mittelmeyer: Das ist Deine Meinung, meine ist es nicht. Mit Auslagern und Importieren sollte man sich meiner Meinung nach erst beschäftigen, wenn man bereits weiß, was man da macht.

@stefanxfg: Und wie lange bist Du bereits mit Python und Tkinter befreundet?
Mit Python und Tkinter beschäftige ich mich erst seit 4 Wochen und verstehe von den meisten Themen hier nicht einmal die Hälfte. Was aber nicht heißt, dass ich mich nicht seit circa 10 Jahren teilweise recht intensiv mit anderen Scriptsprachen beschäftigt hätte.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@Melewo: was Du da beschreibst ist gerade für Anfänger eine gute Vorgehensweise; aber nicht nur für diese. Ein Refactoring in mehrere Module ist erst sinnvoll, wenn der Code umfangreich wird und sich die Abhängigkeiten von Codeteilen herauszubilden beginnen. Je nach Erfahrung erfolgt dies früher oder später. Funktionaler Programmierstil hilft dabei, dieses spätere Refactoring möglichst schmerzfrei zu halten. Dabei zeigt sich dann oft auch, ob sich im Programmentwurf ein Designfehler befindet.
Antworten