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:
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.