Gui-Anderung einer Eigenschaft in einem anderen Modul

Fragen zu Tkinter.
Antworten
Dajosef
User
Beiträge: 4
Registriert: Mittwoch 8. April 2015, 13:46

Hallo zusammen,
ich bin Python Beginner und neu hier im Forum.

Ich beschäftige mich als Hobby mit Mikrocontroller und habe jetzt vor, mich auch mit dem Raspberry Pi zu beschäftigen
(Bisher habe ich VBA und C programmiert.)
Mein Ziel ist es, meine Mikrocontroller, welche bisher bereits über I2C Daten austauschen, "bequem" über den Raspberry Pi - (I2C) anzusprechen.
(Der Datenaustausch bisher war: PC - serielle Schnittstelle - MC-Master - I2C - MC-Slaves)

(Tut nichts zur Frage, nur kurz zu Info über meine Motivation.)

Da ich meine Programme gerne über eine GUI steuern würde, habe ich mich über TKinter informiert, und grundsätzlich klappts.

Mein Problem ist jedoch folgendes:
Ich starte mein Hauptfile (gui.py)
Dieses initialisiert mein Gui und geht in mainloop()

Wenn ich einen Button klicke, wird in einem andern Modul (funktionen.py) eine Funktion ausgeführt

Diese Funktion möchte wiederum in einem Textfeld (in gui.py) einen Text einfügen. (Dieses hat nichts mit dem Button zu tun)

Und das funktioniert so leider nicht.

Wisst ihr einen Weg, wie ich mein Problem lösen kann?
Das gegenseitig abhängige Module nicht optimal sind ist, mir klar, jedoch möchte meine Programmteile klar getrennt haben. (Gui und Funktionen.)

Danke,
Josef,

Im Anhang findet ihr den Code

gui.py

Code: Alles auswählen

import tkinter as tk
import funktionen as fkt


root = tk.Tk()
root.geometry('400x300')
root.title("Test-Programm")
    
Button_start=tk.Button(root, text =  "Öffnen", command = fkt.datei_laden)
Button_start.pack()

Button_ende=tk.Button(root, text = "Ende", command=root.destroy)
Button_ende.pack()


T = tk.Text(root, height=4, width=50)
T.pack()


#################################################################
def text_insert(text):
    T.insert(END, text)
#################################################################

root.mainloop()
funktionen.py

Code: Alles auswählen

#import tkinter as tk
import gui

def datei_laden():
    gui.text_insert("+++")
BlackJack

@Dajosef: Gegenseitig abhängige Module sind nicht nur nicht ideal, wenn die sich gegenseitig importieren dann funktioniert das in der Regel auch nicht so wie man das möchte. Das sollte man auf jeden Fall sein lassen. Wenn Deine Funktionen unabhängig von der GUI sein sollen, dann dürfen die auch selbst die GUI nicht ändern wollen. Man würde da in der GUI eine Methode schreiben welche die Geschäftslogik aufruft und dann das Ergebnis in der GUI darstellt.

Mit abgekürzten Namen sollte man sparsam umgehen. `tk` ist eine verbreitete Konvention und es gibt noch ein paar andere Module die wirklich viele Namen definieren von denen man mehr als eine Handvoll benötigt, wo sich das Importieren über einen abgekürzten Namen etabliert hat, aber in der Regel sollte man entweder den Modulnamen ausschreiben oder explizit die Namen aus dem Modul importieren die man benötigt.

Bei GUI-Programmierung kommt man um objektorientierte Programmierung nicht wirklich herum. Und auf Modulebene sollte kein Code stehen der nicht Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst und über das folgende Idiom aufgerufen wird:

Code: Alles auswählen

if __name__ == '__main__':
    main()
Dann kann man das Modul importieren ohne dass das Programm los läuft, zum Beispiel um einzelne Teile interaktiv oder automatisiert zu testen.

Bezüglich der Namenschreibweisen und Quelltextformatierung lohnt sich ein Blick in den Style Guide for Python Code.
Dajosef
User
Beiträge: 4
Registriert: Mittwoch 8. April 2015, 13:46

Hallo,

danke für die Antwort.

Ich habe jetzt die Punkte beachtet, welche du erwähnt hast
(tkinter für "tk", Verwendung von Klassen und die Funktion main() im Hauptprogramm.

Zur Lösung des Hauptprogramms habe ich jetzt die Inputs und Outputs der GUI in Klassen bzw. Module aufgeteilt.
gui_input und gui_output. (Platzierung wird aufwendiger, aber ok.)

Jetzt habe ich das Problem, wenn ich auf einen Button klicke, soll im "main"- Modul die Funktion "start()" laufen.
jedoch sagt das Programm, das "ui_out" nicht definiert ist.

Wenn ich die Funktion "start()" jedoch vom "main"- Modul aus laufen lasse, findet er die Variable "ui_out"

Weiß jemand warum bzw. kann mir jemand sagen, wie ich das Problem lösen kann?

Danke,

Hier der Code:

main.py

Code: Alles auswählen

import tkinter as tkinter
import gui_input as gui_input
import gui_output as gui_output


def start():
    global ui_out
    ui_out.text_aendern("Ziel erreicht")

def pause():
    global ui_out
    ui_out.label_aendern("Pause")


def main():
    global ui_out
    root=tkinter.Tk()

    ui_out=gui_output.gui_out(root)    
    ui_in=gui_input.gui_in(root)

    
    pause()
    start()

    
    root.mainloop()
    
if __name__ == "__main__":
    main()    
gui_input.py

Code: Alles auswählen

import tkinter as tkinter
import main as main


class gui_in:

    def __init__(self, master):

     
        self.Button_start=tkinter.Button(master, text =  "Start", command =main.start)
        self.Button_start.pack()

        self.Button_pause=tkinter.Button(master, text =  "Pause", command =main.pause)
        self.Button_pause.pack()

        self.Button_ende=tkinter.Button(master, text = "Ende", command=master.destroy)
        self.Button_ende.pack()

        
    def start(self):
        main.start()

    def pause(self):
        main.pause()

if __name__ == "__main__":
    print ("kein Hauptprogramm")
gui_output.py

Code: Alles auswählen

import tkinter as tkinter
import main as main


class gui_out:
    
    def __init__(self, master):
     
        self.text=tkinter.Text(master, height= 4, width=50)
        self.text.pack()

        self.label=tkinter.Label(master, text ="Label")
        self.label.pack()
        
    def text_aendern(self, new_text):
        self.text.insert(tkinter.END, new_text )

    def label_aendern(self, new_text):
        self.label.config(text=new_text)



if __name__ == "__main__":
    print ("kein Hauptprogramm")

Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Dajosef: wenn Du main.py ausführst ist das ja auch ein anderes Modul, als wenn Du main importierst. Du darfst sowieso keine Ringabhängigkeiten haben. Warum steckst Du nicht alles in eine Datei? Wenn gui_in auf gui_out zugreifen soll, dann muß gui_in gui_out auch kennen und Du brauchst keinen Umweg über eine weitere Funktion zu machen. Bei der Gelegenheit vergißt Du am besten gleich, dass es global überhaupt gibt. Es löst keine Probleme, es schafft nur welche.
BlackJack

@Dajosef: Die Module importieren sich ja schon wieder/immer noch gegenseitig. Und ``global`` sollte man nicht verwenden. Insbesondere wenn man Klassen verwendet gibt es dafür gar keinen Grund mehr.

Was soll denn bei allen Importen das ``as`` gefolgt von dem Modulnamen? Das ist vollkommen sinnfrei.

Klassennamen werden anders geschrieben, damit man sie auch als Klassennamen erkennt.

Die Aufteilung der GUI auf Klassen ist unpraktisch gelöst. Normalerweise erstellt man eine Widget-Klasse, zum Beispiel einen von `Frame` abgeleiteten Typ, den man in der `__init__()` mit Inhalt füllt und wo der *Aufrufer* dann entscheidet wie dieses Widget im ”master” layoutet wird. Halt so wie das auch die ganzen Widgets aus dem `tkinter`-Modul machen. Und eine Klasse pro Modul sollte auch nicht das Ziel sein.

Der Quelltext ist für das was er ”löst” jedenfalls hoffnungslos überkompliziert. Und es gibt nicht mal ansatzweise Geschäftslogik, das ist alles GUI.
Dajosef
User
Beiträge: 4
Registriert: Mittwoch 8. April 2015, 13:46

Hallo,

Das der Code für diesen Anwendungsfall über kompliziert ist, ist mir klar. Es dient ja nur als einfaches Beispiel für meine Frage.

Zum Besseren Verständnis für meine Frage:
Ich möchte folgendes realisieren:

-Ich klicke auf einen Button.
-Ausgehend davon wird eine/mehrere Funktionen in einem andern Modul aufgerufen.
-Diese Funktion(en) berechnet etwas
-Diese Funktion(en) sollen das Ergebnis in einem Textfeld angeben.

Mir ist klar, dass ich das Ergebnis als Rückgabewert der Funktion erhalten kann und so das Ergebnis im Textfeld anzeigen kann.

Ich wollte/möchte jedoch herausfinden, ob die Funktion das Textfeld direkt ändern kann.
Mein bisheriger Stand ist: nein, da sich dadurch eine gegenseitige Abhängigkeit der Module (nicht von Funktinen) ergibt


(Der Import As dient dafür dass ich die Module leichter ersetzen/umbenennen kann (z.B gui_input, gui_input_1,..), ohne den gesamten Code ändern muss)

Grüße,
Josef
BlackJack

@Dajosef: Die Funktion könnte das Textfeld, also die GUI, direkt verändern wenn man ihr das Textfeld als Argument übergeben würde. Dann sollte das aber auch eine Funktion sein die zur GUI gehört und keine Geschäftslogik enthält.

Was ist denn komplizierter am ersetzen oder umbenennen von Modulen wenn man erst wenn man tatsächlich etwas umbenennt das beim Importieren wieder umbenennt. Wobei man sich beim umbenennen eines Moduls ja etwas denkt, also einen besseren Namen wählt — was ist dann der Sinn im importierenden Modul den alten, schlechteren zu behalten? Und wann gedenkst Du Module in der Standardbibliothek umzubennen, denn Du hast diesen Unsinn ja auch bei `tkinter` gemacht. *Das* wird übrigens häufig tatsächlich beim Importieren umbenannt zu `tk`.
Dajosef
User
Beiträge: 4
Registriert: Mittwoch 8. April 2015, 13:46

Hallo nochmals,

Ich habe, dass jetzt so umgesetzt, wie ich die Antwort von Black Jack verstanden habe. (Ich hoffe, du bzw Sie hast/haben das so gemeint.)

Ich übergebe jetzt das GUI-Objekt als Argument der Funktion und diese greift dann direkt auf das Objekt zu.

Ich habe jetzt das Gui in die "main" integriert und die Berechnung in das Modul "Funktionen" ausgelagert. Macht so, glaube ich jetzt, mehr Sinn.

Funktioniert gut, und mach ja auch Sinn, damit das Modul flexibel einsetzbar ist/wäre.

Danke für die Hilfe!
Grüße,
Josef

Anbei der Code

main.py

Code: Alles auswählen

import tkinter
import funktionen as fkt

#__________________________________________________________________________________
# Klasse GUI
class Gui:

    def __init__(self, master):

        master.geometry("400x300")
        master.title("Test: "  +__name__)
     
        self.Button_start=tkinter.Button(master, text =  "Start", command =start)
        self.Button_start.pack()

        self.Button_pause=tkinter.Button(master, text =  "Pause", command =pause)
        self.Button_pause.pack()

        self.Button_ende=tkinter.Button(master, text = "Ende", command=master.destroy)
        self.Button_ende.pack()

     
        self.text=tkinter.Text(master, height= 4, width=50)
        self.text.pack()

        self.label=tkinter.Label(master, text ="Label")
        self.label.pack()
#________________________        

    def text_aendern(self, new_text):
        self.text.insert(tkinter.END, new_text )
#________________________
    def label_aendern(self, new_text):
        self.label.config(text=new_text)
        
#__________________________________________________________________________________

def start():
    fkt.datei_pause(ui)

#________________________

def pause():
    fkt.datei_laden(ui)

#________________________

def main():
    global ui
    root=tkinter.Tk()

    ui=Gui(root)    
   
    root.mainloop()
#__________________________________________________________________________________    
if __name__ == "__main__":
    main()    

Funktionen.py

Code: Alles auswählen

#__________________________________________________________________________________  

def datei_laden(interface):

    interface.text_aendern("funktioniert")

#__________________________________________________________________________________  

def datei_pause(interface):

    interface.label_aendern("klappt")

#__________________________________________________________________________________    
if __name__ == "__main__":
    print("kein Hauptprogramm")
BlackJack

@Dajosef: Also für mich macht das keinen Sinn. Die Punkte hatte ich aber alle schon genannt. Vergiss das es ``global`` gibt. Ich sehe da keine Trennung zwischen GUI und Geschäftslogik — unter der Bedingung das `funktionen` (hoffentlich nur der Name für das Beispiel (und schon wieder abgekürzt)) die Geschäftslogik umsetzen und die nicht *nochmal* wo anders sitzt. In dem Fall würde ich den Zwischenschritt über das `funktionen`-Modul nicht verstehen.

Es würde auch mehr Sinn machen erst die Geschäftslogik zu implementieren und dort dann eine GUI drauf zu setzen.
Flamez
User
Beiträge: 11
Registriert: Sonntag 14. Juni 2015, 18:43

Hallo zusammen,

ich steht vor dem gleichen Problem wie der Threadersteller. Ich habe ein GUI, frei von Klassen, aufgesetzt. In einem Modul GUI.py sind alle Oberflächenelemente mit allem drum und dran, im Modul Funktionen.py habe ich (versucht) alle Berechnungen unterzubringen. Ein drittes Modul ist bei mir eine Art Bibliothek mit Funktionen die ich in diesem Projekt öfter gebrauchten könnte. Zu den Bibliotheksfunktionen gehört zum Beispiel eine, die eine Textdatei einliest, Stringoperationen durchführt und als String bereitstellt.

Hintergrund meiner übermotivierten Strukturierungsversuche ist, dass meine GUI mehrere komplette Frames besitzt mit unterschiedlichen Funktionalitäten. Ein GUI Modul, mehrere Funktionsmodule, leider scheiter ich schon bei einem.

Nun zu meinem konkreten Fall. Über die Betätigung eines Buttons wird in der Funktionen.py eine Funktion aufgerufen. Darin werden Daten eingelesen, Verarbeitet und die Ergebnisse im GUI angezeigt. Dazu müssen sich beide gegenseitig importieren, was grundverkehrt ist. Habe ich in den vorherigen Beiträgen gelernt. Um die Ergebnisse in eine Datei zu schreiben, habe ich einen weiteren Button, wieder verknüpft mit einer Funktion aus dem Funktionsmodul, der das erledigen soll. Nun wäre es am besten wenn ich die Ergebnisse direkt aus der berechnenden Funktion in die speichernde Funktion übergebe, da die speichernde die Ergebnisse erst mal nicht kennt. Dazu müsste ich das aber irgendwo zwischenspeichern. Also womöglich als Variable in irgendeinem Modul, bessere wäre wohl eine Klasse (GUI-Klasse) mit entsprechendem Attribut.

Noch dazu gibt es schon mehr als genug Variablen durch die ganzen TKinter Oberflächenelemente in der GUI.py. Im Endeffekt werden alle Frames zusammengesetzt und angezeigt oder eben nicht.

Man merkt schon, das was ich gemacht hab - mit einem Ansatz einer übersichtlichen Struktur - wird langsam kontraproduktiv und ist an sich eigentlich ziemlich sch****e. Besser wäre wohl eine GUI-Klasse in der GUI.py die so viel Logik wie möglich enthält und ggf. noch benötigte Attribute. Dadurch würde der Namensraum des Moduls auch nicht so überquellen.

Wie unterscheidet man nun eigentlich zwischen Geschäftslogik und GUI? Also wo trennt man wirklich sinnvoll und was gehört in die GUI-Klasse?

Bei mir scheitert das weniger an der Implementierung selbst, sondern meistens an konzeptionellen Problemen. Ich bekomme einfach keine Vernünftige Struktur in das ganze Programm bzw. weiß einfach nicht wie man es am besten macht. Vielleicht hilft es ja schon meine Denkweisen etwas anzupassen und nicht alles trennen zu wollen, was eigentlich nicht getrennt werden soll/muss.

Bitte helft mir.

P.S.: (mein erster Beitrag, woohoo) :mrgreen:
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Flamez: zuerst, es ist nicht sinnvoll, sich an irgendeinen Beitrag mit einer anderen Frage anzuhängen. Mach für jedes neue Problem eine neue Frage auf. Zum eigentlichen Problem: Klassen sind bei GUI-Programmen eigentlich immer nötig. Eine prinzipielle Trennung in Logik und Methoden für die Anzeige ist gut, ob das in verschiedene Dateien aufzuteilen ist, kommt auf den Einzelfall an.
Flamez
User
Beiträge: 11
Registriert: Sonntag 14. Juni 2015, 18:43

Mein Beitrag sollte eigentlich eine Ergänzung bzw. Frage zu diesem Thema sein (siehe das Unterstrichene in meinem Beitrag). Leider ist er dann doch etwas länger geworden, das nächste mal werde ich wohl doch einen eigenen Thread dazu eröffnen, sry.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Also ich kann in unterschiedlichsten Modulen Funktionalitäten ansprechen.

Ich mache das mit Broadcast Messages:

Mit dem Befehl send(msgid,Message) kann man Messages an alle schicken, die sich für die betreffende msgid registriert haben.

Messageempfänger sind Objekte, die eine Methode receive(Message) haben. Und die müssen sich als Messageempfänger registrieren mit: registerReceiver(msgid,Empfangsobjekt)

Die Registrierung kann mit unregisterReceiver(msgid,Empfangsobjekt) wieder aufgehoben werden.

Dabei habe ich dann zwei Sendemethoden:

- sendImmediate(msgid,Message)
- send(msgid,Message)

sendImmediate wird sofort ausgeführt (an alle Empfänger) , wie ein Funktionsaufruf - eigentlich verwende ich die gar nicht.
Mit send dagegen wird die Message zuerst in eine Messagequeue geschrieben, wenn im Augenblick bereits andere Messages ausgefürt werden. Ich bevorzuge die Methode send,
da dann keine Vermischungen von sich eventuell gegenseitig störenden Aktionen auftreten können

Auch Buttonklicks oder Mouseevents lasse ich über die Messagequeue ausführen.

Da gibt es bei mir nämlich einen Empfänger für die MessageID "execute_message" - ich verwende Strings als Message IDs.
Und dieser Empfänger führt dann Messageobjekte aus, die eine Methode execute() beitzen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ich habe eine Lösung für das Problem. Hierbei handelt es sich um einen Broadcast Message Callback.

Mit do_receive(parent,messageId,command) wird eine Callback Function eingerichtet. So wie bei einem Event. Nur statt event sollte man zur besseren Verdeutlichung message schreiben:

Code: Alles auswählen

do_receive(parent,'CREATE_WIDGET_REQUEST',lambda message: print("Class:",message[0],"Name:",message[1]))
Der Sinn der Parentangabe ist, ein Widget als Owner anzugeben. Wenn der Owner gelöscht wird, sollte er in seiner destroy Methode aufrufen: undo_receiveAll(self).
Wenn nämlich die Widgets gelöscht werden und die Message weiterhin empfangen wird, gäbe es wahrscheinlich einen Crash, wenn sich der Callback auf Widgets bezieht.

Für eine MessageId können sich mehrere Callbacks als Empfänger anmelden. Dann erhalten alle angemeldeten Empfänger die Message. Einzelne Callbacks kann man auch wieder mit undo_receive(owner,msgid,receive) beseitigen, sofern man den Callback receive noch kennt. UI, es hätte da wohl auch undo_receive(owner,receive) gereicht. Kann ich mal bei Gelegenheit nachimplementieren.

Hier habe ich ein Beispiel dazu, das besteht aus zwei Teilen, nämlich der Gui Teil message.py:

Code: Alles auswählen

from tkinter import *
from proxy import do_receive
from proxy import send


import threading
class MyThread(threading.Thread):
	def run(self):
		while True:
			a = input("> ")
			try: eval(a) 
			except: print("Error:",a)


mythread = MyThread()
mythread.daemon = True
mythread.start()

root = Tk()

def buildGui(parent):

	labelframe = LabelFrame(parent,text="""Create Widget""")
	labelframe.pack()

	def createWidgetsContent(parent):

		Label(parent,text="""Class""").grid(row='0')
		Label(parent,text="""Name""").grid(row='1')

		buttonCreate = Button(parent,text="""Create""",bg='green')
		buttonCreate.grid(column='3',sticky='e',row='0')
		labelWidgetType = Label(parent,text="""Button""",bg='yellow',fg='blue')
		labelWidgetType.grid(column='1',sticky='w',row='0')
		entryWidgetName=Entry(parent)
		entryWidgetName.grid(column='1',columnspan='3',row='1')

		### CODE ===================================================

		# normal callbacks
	
		buttonCreate.config(command=lambda hereClass = labelWidgetType, hereName = entryWidgetName: send('CREATE_WIDGET_REQUEST',(hereClass['text'],hereName.get())))
		entryWidgetName.bind("<Return>",lambda event, hereClass = labelWidgetType, hereName = entryWidgetName: send('CREATE_WIDGET_REQUEST',(hereClass['text'],hereName.get())))

		# ===============================================================
		# a broadcast message callback
		# ===============================================================

		def receiveSelectedClass(message,ltype,entry):
			ltype['text'] = message
			entry.delete(0,END)
			entry.insert(0,message)
		do_receive(parent,'CLASS',lambda message, ltype = labelWidgetType, entry = entryWidgetName: receiveSelectedClass(message,ltype,entry))

		# ---- Initialization with Class 'Button' ---------------------------

		send('CLASS','Button')

	   ### ========================================================

	createWidgetsContent(labelframe)
	del createWidgetsContent

	# ===============================================================
	# test broadcast message callback for CREATE_WIDGET_REQUEST
	# ===============================================================
	do_receive(parent,'CREATE_WIDGET_REQUEST',lambda message: print("Class:",message[0],"Name:",message[1]))

buildGui(root)
del buildGui

root.mainloop()
Und die dazugehörige Klasse im Modul proxy.py:

Code: Alles auswählen


class MessageProxy:

	def __init__(self): self.reset()

	def reset(self):
		self.Dictionary = {}
		self.Queue = []
		self.owners = {}
		self.counter = 0
		self._register("execute_function",lambda msg: msg()) 

	def send(self,msgid,msgdata=None):
		if self.counter == 0: self.sendImmediate(msgid,msgdata)
		else: self.Queue.append((msgid,msgdata))

	def sendImmediate(self,msgid,msgdata=None):
		while True:
			if msgid in self.Dictionary:
				receivers = self.Dictionary[msgid].items()
				self.counter += 1
				for receive,active in receivers:
					if active: receive(msgdata)
				self.counter -= 1

			if self.counter > 0: return
			if len(self.Queue) == 0: return
			msgid = self.Queue[0][0]
			msgdata = self.Queue.pop(0)[1]

	def _register(self,msgid,receive):
		if msgid not in self.Dictionary: self.Dictionary[msgid] = {}
		self.Dictionary[msgid][receive] = True

	def do_receive(self,owner,msgid,receive):
		if not owner in self.owners: self.owners[owner] = {}
		self.owners[owner][receive]=msgid
		self.send("execute_function",lambda: self._register(msgid,receive))

	def activate_receive(self,msgid,receive,flag):
		if msgid in self.Dictionary:
			receivers = self.Dictionary[msgid]
			if receive in receivers:
				receivers[receive] = flag

	def _unregister2(self,msgid,receive):
		if msgid in self.Dictionary:
			receivers = self.Dictionary[msgid]
			if receive in receivers:
				receivers.pop(receive,None)
				if len(receivers) == 0: self.Dictionary.pop(msgid,None)
				
	def _unregister1(self,msgid,receive):
		self.activate_receive(msgid,receive,False)
		self.send("execute_function", lambda: self._unregister2(msgid,receive)) 

	def undo_receive(self,owner,msgid,receive):
		if owner in self.owners:
			if receive in self.owners[owner]: self.owners[owner].pop(receive,None)
		self._unregister1(msgid,receive)
		
	def undo_receiveAll(self,owner):
		if owner in self.owners:
			messages = self.owners[owner]
			self.owners.pop(owner,None)
			for receive,msgid in messages.items(): self._unregister1(msgid,receive)
		
Proxy = MessageProxy()

def do_receive(owner,msgid,receive): Proxy.do_receive(owner,msgid,receive)
def undo_receive(owner,msgid,receive): Proxy.undo_receive(owner,msgid,receive)

# this you should call, if you destroy a widget, which is owner of broadcast receivers
def undo_receiveAll(owner): Proxy.undo_receiveAll(owner)

def send(msgid,message=None): Proxy.send(msgid,message)
def sendImmediate(msgid,message=None): Proxy.sendImmediate(msgid,message)
Mit diesem Callback können Module miteinander kommunizieren, ohne sich gegenseitig zu kennen. Bekannt muss nur die MessageID sein, und wie sich die Message zusammensetzt.

In diesem Beispiel habe ich außerdem eine Task zur direkten Kommandoeingabe. Da kann man dann testen, was bei Dieser Eingabe geschieht:

Code: Alles auswählen

send('CLASS','Label')
Ich hoffe, dass dieses ein Callback ist, den man wirklich gut gebrauchen kann.

Und normalerweise hätte ich bei dem Labelframe die destroy Methode ändern müssen, falls er gelöscht werden sollte.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Das ist ärgerlich, wenn man hier etwas richtig veröffentlicht, dabei einen Fehler korrigiert und dann vergissst, es bei seinem Programm nachzuziehen. Habe heute Stunden vergeudet, um den Fehler zu finden.
Habe meinen GUI Designer, gleich zweimal geladen. Das ist dann wie Stereo. Wenn man ein Widget anlegt, dann legt es der eine GUI Designer an und der andere auch. Hat man es eben zweimal. Nachdem ich einen GUI Designer geschlossen hatte, kam es bald zum Crash, weil ich die Korrektur nicht nachgezogen hatte. Das ist richtig:

Code: Alles auswählen

        def do_receive(self,owner,msgid,receive):
                if not owner in self.owners: self.owners[owner] = {}
                self.owners[owner][receive]=msgid
                self.send("execute_function",lambda: self._register(msgid,receive))
Aber ich hatte es noch falsch aufgebaut mit msgid und receive im Dictionary vertauscht. 'receive' ist dabei die Callback Funktion. Hier ein Auszug der fehlerhaften Version:

Code: Alles auswählen

        def do_receive(self,owner,msgid,receive):
                if not owner in self.owners: self.owners[owner] = {}
                self.owners[owner][msgid]=receive
                self.send("execute_function",lambda: self._register(msgid,receive))
Per Container Widget hatte ich auch durchaus zwei oder dreimal dieselbe Message ID. Und es waren dann zwei oder drei verschiedene Message Callback Funktionen. Beim key msgid wurde leider nur eine davon gelöscht. Und bei gelöschtem GUI Designer 2 existierten dann leider noch nicht gelöschte Message Callbacks. Das kommt davon, wenn man zwar etwas richtig veröffentlicht, aber dann vergisst, es in seinem Programm auch nachzuziehen.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Habe nochmal darüber nachgedacht, dass man das überarbeiten sollte. sendImmediate braucht man nicht und send sollte man besser threadsicher über Queue und Eventqueue machen. Dann ist es auch von anderen Threads aus nutzbar.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Flamez hat geschrieben:Über die Betätigung eines Buttons wird in der Funktionen.py eine Funktion aufgerufen. Darin werden Daten eingelesen, Verarbeitet und die Ergebnisse im GUI angezeigt. Dazu müssen sich beide gegenseitig importieren, was grundverkehrt ist.
Richtig, und das ist normalerweise auch nicht notwendig. Die Hilfsmittel aus deiner `Funktionen.py` sollten nur über Rückgabewerte arbeiten und kein Wissen von deinen GUI-Elementen haben. Schreibe dein Programm so, dass der GUI-Anteil die nötigen Daten aus den Widgets extrahiert und sie in dieser "neutralen" Form an die Hilfsfunktionen übergibt. Dort werden sie verarbeitet und das Resultat wird mittels `return` ausgeliefert (falls ein Rückgabewert sinnvoll ist). Dieser Rückgabewert wird dann von deinem GUI-Modul, welches ja der Aufrufer war, entgegen genommen und wieder ins GUI-Element übermittelt. So wäre das grobe Schema, wie man üblicherweise vorgeht.

Bezüglich der Imports heißt dies: Das GUI-Modul importiert `Funktionen.py`, ABER: `Funktionen.py` ist nun nicht mehr fest mit der GUI verbunden und benötigt somit keinen Import des GUI-Moduls mehr. Damit bist du den zirkulären Import und somit auch die Fehlerquelle los. Und nebenbei könntest du dann auch alternativ z.B. eine Ausgabe im Terminal oder im Web-Browser ermöglichen, basierend auf der selben `Funktionen.py`, auf der auch deine GUI basiert.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@snafu, richtig genau darum geht es hier. Diese Funktion sollte man mit queue.Queue machen. Bis zu Start von mainloop() muss man sie allerdings dann im Prinzip so lassen. Nach Start von mainloop() kann man dann auch über die Eventqueue mit event_generate gehen. Und dann hat man nach mainloop() Start sogar Threadsicherheit und kann von anderen Threads an die GUI senden. Wenn man auch Callbacks von einem andern Threads aus braucht, müßte man sich auch noch hierzu etwas überlegen.

Aber im Normalfall arbeitet man nicht von einem andern Thread aus. Mithilfe dieser Funktion kann man GUI Teile und Geschäftslogik unabhängig voneinander machen. Man schickt einfach nur eine Message ID und Daten an alle, die sich für diese Message ID registriert haben.

Rückgabewert könnte man bei sendImmediate auch machen. Aber bei mehreren Empfängern könnte man nur den Rückgabewert vom letzten Empfänger zurück geben. Es geht auch ohne Rückgabewert. Wenn man etwas braucht, dann anfordern und mit einem Callback abholen. Normalerweise sendet die GUI die Daten, wenn der Anwender auf einen Button drückt oder sonstwo hindrückt. Braucht man normalerweise auch nichts nachfragen.
Zuletzt geändert von Alfons Mittelmeyer am Montag 31. August 2015, 12:34, insgesamt 1-mal geändert.
BlackJack

@Alfons Mittelmeyer: Das verkompliziert nur alles unnötig. `Queue` braucht man erst wenn man Threads verwendet und die braucht man erst wenn die Geschäftslogik Sachen machen muss die länger dauern und die GUI nicht blockieren. Und selbst dann würde ich die Geschäftslogik davon möglichst nichts wissen lassen von den Threads sondern das noch zum notwendigen GUI-Code zählen. Denn ohne GUI und deren Anforderungen bräuchte man die Threads ja nicht.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@Alfons Mittelmeyer: Wärst du Straßenbahn- oder Busfahrer, dann würdest du dem Fahrgast wahrscheinlich einen Vortrag über das gesamte Ticketangebot in allen Variationen halten, obwohl er nur wissen will, was er für eine Fahrt zum Bahnhof zahlen muss... :roll:
Antworten