Add "Clickable" Text in a GUI?
-
Hi,
Is there a way to have a clickable text in a GUI?
I was thinking of like the Object Manager where if you click an item, you can then set several commends likeSetActiveObject
, etc.I understand that can be done with the
TreeViewFunctions
but I'm not after a hierarchy. I just really want a simple
self.AddClickableText
line or something like that for my UI.I checked the documentation. The closest is the
AddStaticText
but it does not have a parameter to be clicked.Is there a way around this?
-
Hello @bentraje,
Thank you for reaching out to us. I do not fully understand your question, especially the part 'the closest is the AddStaticText but it does not have a parameter to be clicked'. GUI gadgets do not really have parameters in the sense Cinema 4D is using the term, so I assume when you say 'parameter', you mean an interaction event/message, i.e., you want to be informed when the user has interacted with the gadget.
Just to get the basics out of the way: Gadget interactions are routed through
GeDialog.Command
(direct interactions as clicks) andGeDialog.Message
(lower-level events). So, when you have some gadget with the IDID_MY_CLICK_TEXT
, you must listen in.Command()
for this event ID. There are then three options I see here:- Simply use a button, i.e.,
GeDialog.AddButton
. This might not be what you want visually, but this is how Cinema 4D usually realizes a 'clickable text'. If you want a 'flatter' look, you could use ac4d.gui.BitmapButtonCustomGui
. There you would have to either render the text inside the button yourself by rendering it into aBaseBitmap
, usingGeClipMap
, or just use an icon instead. - There is also
c4d.gui.HyperLinkCustomGui
which does more or less the same. Important is here its flagHYPERLINK_IS_LINK
with which you can turn on and off the gadget attempting to open the link in a browser on click events. - Finally, there is
GeDialog.AddStaticText
. If I remember correctly, static texts do not fire off on click events inGeDialog.Command
, probably the reason why you are asking this question here. You could however check if an interaction message is sent viaGeDialog.Message
when a static text gadget is being clicked. The event will likely be sent asBFM_ACTION
where then the message data container holds the ID of the triggering element asBFM_ACTION_ID
. See GUI Messages for an overview of the messages sent to dialogs.
I personally would just go with a button or bitmap button because that is usually how Cinema 4D solves the task of 'clickable text', but you can likely make all three options work.
Cheers,
Ferdinand - Simply use a button, i.e.,
-
Hi @ferdinand
Thanks for the response. I'm trying the #2 route you presented but I'm having a problem on implementing it.
There is theAddCustomGui
which I believe is just an equivalent of theAddStaticText
but for some custom stuff (like the c4d.gui.HyperLinkCustomGui).My only concern is that in the documentation under SymbolID. The HyperlinkCustomGui is not listed.
How do I pass that to the
AddCustomGui
?P.S. As you can see, the HyperlinkCustomGui is not listed.
P.P.S. I already search the forum forc4d.gui.HyperLinkCustomGui
but its only this thread that appears in the search result huehue
.S -
Hey @bentraje,
The id is
CUSTOMGUI_HYPER_LINK_STATIC
, you can find it on the page of the custom GUI. However, I just tried it myself, andHYPERLINK_IS_LINK
unfortunately turns the link effectively into a static text. Even more so, static texts do not send any interaction messages at all (at least I did not find any). So, the only route which remains isBitmapButton
, find an example below.Bitmaps containing text to be shown in GUIs are always tricky because one must combat interpolation issues. My example renders at twice the required resolution but is far from perfect.
Cheers,
FerdinandThe result:
The code:"""Demonstrates how to render "clickable text" with a bitmap button. Can be run as a Script Manger script. """ import c4d class MyDialog(c4d.gui.GeDialog): """Implements a dialog using a bitmap button, a hyper link, and a static text gadget. """ ID_GRP_MAIN: int = 1000 ID_BTN_BITMAP: int = 2000 ID_BTN_HYPERLINK: int = 2001 ID_BTN_STATICTEXT: int = 2002 def AddClickableText(self, gid: int, label: str): """Adds a bitmap button containing the text #label. Content is rendered at twice the output resolution to combat interpolation issues with the text. """ font: c4d.BaseContainer = c4d.bitmaps.GeClipMap.GetDefaultFont(c4d.GE_FONT_DEFAULT_SYSTEM) canvas: c4d.bitmaps.GeClipMap = c4d.bitmaps.GeClipMap() # Fake init the canvas with a generous size so that we can measure the size of #label. canvas.Init(1000, 100, 32) canvas.BeginDraw() canvas.SetFont(font, 0) # We render with a margin of 3, 3, 3, 3 and at twice the size to combat blurry text. w, h = (canvas.TextWidth(label) + 6) * 2, (canvas.TextHeight() + 6) * 2 fSize: int = canvas.GetFontSize(font, c4d.GE_FONT_SIZE_INTERNAL) canvas.EndDraw() # Get the drawing colors for the gadget (bg) and text (fg) bg: c4d.Vector = c4d.gui.GetGuiWorldColor(c4d.COLOR_BG) * 255 fg: c4d.Vector = c4d.gui.GetGuiWorldColor(c4d.COLOR_TEXT) * 255 bg: tuple[int] = int(bg.x), int(bg.y), int(bg.z) fg: tuple[int] = int(fg.x), int(fg.y), int(fg.z) # Re-init the canvas at twice the final display size, fill the background and render the # text (also at twice the size). canvas.Init(w, h, 32) canvas.BeginDraw() canvas.SetFont(font, fSize * 2) canvas.SetColor(*bg) canvas.FillRect(0, 0, w, h) canvas.SetColor(*fg) canvas.TextAt(6, 6, label) canvas.EndDraw() # Prepare the bitmap button container, we force the button to be drawn at half its size. bc: c4d.BaseContainer = c4d.BaseContainer() bc.SetInt32(c4d.BITMAPBUTTON_BACKCOLOR, c4d.COLOR_BG) bc.SetInt32(c4d.BITMAPBUTTON_FORCE_SIZE, int(canvas.GetBw() / 2)) bc.SetInt32(c4d.BITMAPBUTTON_FORCE_SIZE_Y, int(canvas.GetBh() / 2)) bc.SetBool(c4d.BITMAPBUTTON_DISABLE_FADING, True) # Add the button and set the image. button: c4d.gui.BitmapButtonCustomGui = self.AddCustomGui( gid, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_SCALE, 0, 0, bc) if not button: raise MemoryError("Could not allocate bitmap button.") button.SetImage(canvas.GetBitmap()) def CreateLayout(self) -> bool: """Adds the three gadgets to the dialog. """ self.SetTitle("Clickable Text Examples") self.GroupBegin(id=self.ID_GRP_MAIN, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=1) self.GroupBorderSpace(5, 5, 5, 5) # Add the bitmap button. self.AddClickableText(self.ID_BTN_BITMAP, "Bitmap Button") # Add the static text and the hyper-link, both won't work regarding raising click events. self.AddStaticText(self.ID_BTN_STATICTEXT, c4d.BFH_SCALE, name="Static Text") bc: c4d.BaseContainer = c4d.BaseContainer() bc.SetString(c4d.HYPERLINK_LINK_TEXT, "HyperLink") bc.SetString(c4d.HYPERLINK_LINK_DEST, "") bc.SetBool(c4d.HYPERLINK_IS_LINK, False) bc.SetBool(c4d.HYPERLINK_NO_UNDERLINE, True) self.AddCustomGui(self.ID_BTN_HYPERLINK, c4d.CUSTOMGUI_HYPER_LINK_STATIC, "", c4d.BFH_SCALE, 0, 0, bc) self.GroupEnd() return super().CreateLayout() def Command(self, cid: int, msg: c4d.BaseContainer) -> bool: """Called by Cinema 4D to signal gadget interactions. """ if cid == self.ID_BTN_BITMAP: print ("Command: Bitmap Button") # Never emitted once HYPERLINK_IS_LINK is set to false. elif cid == self.ID_BTN_HYPERLINK: print ("Command: Hyper Link") # Never emitted elif cid == self.ID_BTN_STATICTEXT: print ("Command: Static Text") return super().Command(cid, msg) def Message(self, msg: c4d.BaseContainer, result: c4d.BaseContainer) -> int: """Called by Cinema 4D to convey the raw message stream of the dialog. This also includes gadget interactions with BFM_ACTION. """ # Only emitted for ID_BTN_BITMAP, i.e., the bitmap button which shows up in Command() # anyways. if msg.GetId() == c4d.BFM_ACTION: print (f"Action from: {msg.GetInt32(c4d.BFM_ACTION_ID)}") # In fact, ID_BTN_HYPERLINK and ID_BTN_STATICTEXT never appear in any message data. elif MyDialog.ContainsIds(msg, (self.ID_BTN_HYPERLINK, self.ID_BTN_STATICTEXT)): print (f"{msg.GetId() = }") return super().Message(msg, result) @staticmethod def ContainsIds(bc: c4d.BaseContainer, idCollection: tuple[int]) -> bool: """Returns if an element of #idCollection is contained in #bc or one of its descendant containers. """ for key, value in bc: if value in idCollection: return True if bc.GetType(key) == c4d.DA_CONTAINER: if MyDialog.ContainsIds(value, idCollection): return True return False DIALOG: MyDialog = MyDialog() if __name__ == "__main__": DIALOG.Open(c4d.DLG_TYPE_ASYNC, defaultw=300, default=200)
-
RE: The id is CUSTOMGUI_HYPER_LINK_STATIC, you can find it on the page of the custom GUI
Gotcha. Thanks for the confirmation.
Just wondering, why is that ID not also on the list of the Custom GUIs in the documentation. Along with other CUSTOMGUIs.
Is it because the list will be too long already?RE: code
Can confirm it works as expected. Although had to revised thisdefault=200
part todefaultw=200
.