Hello @HerzogVonWiesel,
Thank you for reaching out to us. Using the message system could be possible here but seems a bit overkill. Since you own both implementations, it would be best if you simply tie together the dialog instances you want to exchange information between. E.g.:
import c4d
class ConnectedDialog(c4d.gui.GeDialog):
"""Realizes a dialog type which has a binding to another dialog instance.
Note that all methods in this example are not part of the GeDialog interface scheme, i.e.,
"custom" methods.
"""
def __init__(self) -> None:
"""
"""
# The list of dialogs this dialog instance does exchange information with.
self._connectedDialogs: list[ConnectedDialog] = []
super().__init__()
def Bind(self, other: "ConnectedDialog", twoWay: bool = True) -> None:
"""Binds the dialog #other to #self and makes the connection optionally two-way.
Also ensures that bindings are unique, i.e, two dialogs cannot be bound more than once in
one direction.
"""
if not isinstance(other, ConnectedDialog):
raise TypeError(f"{other = }")
if other not in self._connectedDialogs:
self._connectedDialogs.append(other)
if twoWay and self not in other._connectedDialogs:
other._connectedDialogs.append(self)
def SpecialMessage(self, sender: "ConnectedDialog", *args) -> None:
"""Receives message stream from all connected dialogs.
"""
print (args)
def Action(self, value: any, condition: any) -> None:
"""Exemplifies a method which informs all other dialog instances about an event.
"""
if condition:
for dlg in self._connectedDialogs:
dlg.SpecialMessage(self, value, condition)
if __name__ == "__main__":
# Instantiate five dialogs and creating bindings between all of them.
dialogCollection: tuple[ConnectedDialog] = (ConnectedDialog() for _ in range(5))
for a in dialogCollection:
for b in dialogCollection:
a.Bind(b)
You could do three million and one thing differently here; this is just an example to illustrate a pattern. The crucial information might be here for you (since we just talked about threading and dialogs before) that:
ℹ The methods both of modal and async dialogs run on the main thread. Async in an async dialog are only the drawing routines which you do not have access to, even when you implement a dialog with a custom GeUserArea. That area only enqueues drawing instructions into a buffer and does not do the actual drawing. So, there is no danger of access violations, which Python of course does not know in the first place due to its GIL.
When tying dialogs together is not possible then you can use the message system of Cinema 4D. But tying objects together is always possible in Python even when the objects live in two modules which do not have access to each other. You can either use sockets (a bit overkill) or be lazy and just setup shop in a commonly accessible object, e.g., the sys module.
When you go for messages, I would recommend having a look at the Message Manual first as I gave there a rough overview. In short:
GeDialog.Message is for UI messages and just like its NodeData.Message counter part instance specific. It is not meant to establish a binding between two dialog instances but to let Cinema 4D or elements in a UI tree communicate with the dialog ("button to dialog: I have been pressed").
What you can do, is set off a core event with c4d.SpecialEventAddd to then catch that core message in all other dialogs using GeDialog.CoreMessage.
Note that the Python API does filter message streams, and unlike in C++, you cannot just "invent" a new message type, except for using c4d.SpecialEventAdd. But there you are limited to sending three integers in Python (in C++ the p1 and p2 arguments are meant to be pointers to arbitrary data).
Cheers,
Ferdinand
PS: Yeah using the CPython API you can cast/wrap things (in order to use p1 and p2 actually in the manner they are intended to) but be aware that any C magic is not officially supported by us.