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, and HYPERLINK_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 is BitmapButton
, 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,
Ferdinand
The 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)