some problem with CUSTOMGUI_BITMAPBUTTON
-
hello,guys
there is some problem with CUSTOMGUI_BITMAPBUTTON, I want to use it to create a file browser.
my envirment: c4d R26, Python
1.width self-adaptive,when I change the dialog width,the number of BitmapButton in row could be changed by the dialog width。
just like this.
here is my demo picture
2.can I capture the double click event Message with bitmapbutton? I want entry the directory when I double click the directory bitmapbutton。
3.the bottom of the directory bitmapbutton Text
when the text is too long, it will be hide in the end
exp. Text is “electric wire”
I want Is Shows "delctric..."
but is Shows "electric w"
how could I measure the width,or Is there have some other solution?thanks very much for reading! have a good day!
here is my demo code
import c4d class Test1Dialog(c4d.gui.GeDialog): ID_BITMAP_BUTTON = 10000 ID_BITMAP_TEXT = 20000 BITMAP_WIDTH = 50 def CreateLayout(self): """Creates the layout for the dialog. """ if self.ScrollGroupBegin(0, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, c4d.SCROLLGROUP_VERT | c4d.SCROLLGROUP_AUTOVERT, 80, 80): if self.GroupBegin(1, c4d.BFH_LEFT | c4d.BFV_TOP, 0, 0, "", c4d.BFV_GRIDGROUP_EQUALCOLS | c4d.BFV_GRIDGROUP_EQUALROWS | c4d.BFV_DIALOG_BAR_VERT | c4d.BFV_LAYOUTGROUP_PALETTEOUTLINES): for i in range(30): bc = c4d.BaseContainer() bc[c4d.BITMAPBUTTON_BUTTON] = True bc[c4d.BITMAPBUTTON_ICONID1] = 1052837 bc[c4d.BITMAPBUTTON_BACKCOLOR] = c4d.COLOR_BG bc[c4d.BITMAPBUTTON_DISABLE_FADING] = False bc[c4d.BITMAPBUTTON_FORCE_SIZE] = Test1Dialog.BITMAP_WIDTH if self.GroupBegin(0, c4d.BFH_LEFT, 1, 2): self.AddCustomGui(Test1Dialog.ID_BITMAP_BUTTON + i, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_CENTER | c4d.BFV_CENTER, 0, 0, bc) self.AddStaticText(id= Test1Dialog.ID_BITMAP_TEXT + i,flags=c4d.BFH_SCALEFIT,initw=Test1Dialog.BITMAP_WIDTH,inith=0,name="test string number" + str(i) ,borderstyle=0) self.GroupEnd() self.GroupEnd() self.GroupEnd() self.AddDlgGroup(c4d.DLG_OK | c4d.DLG_CANCEL) return True def main(): global dialog dialog = Test1Dialog() dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, defaultw=-2, defaulth=-2) if __name__ == '__main__': main()
-
Hello @mogli,
Thank you for reaching out to us. Please refer to our Forum Guidelines for the scope of support. Multiple questions constitute multiple topics.
So, to sort out the easy things first:
- (2.) You can detect also bitmap button clicks just as all gadget interactions with
GeDialog::Command
. When you want to detect a double click, it would be up to you to associate two clicks in quick succession on one gadget as a single action. - (3.) That your text was cut off was caused by you using the incorrect layout flags in the
if self.GroupBegin(0, c4d.BFH_LEFT, 1, 2):
calls. Since you set hereBFH_LEFT
, the group could not scale to the required size of its static text child. You were also reusing here a gadget ID (0
) since this line of code is called thirty times.
But your major problem, question (1.), has nothing to do with
CUSTOMGUI_BITMAPBUTTON
and more with your general approach to the problem and how you treat dialog groups. Groups cannot dynamically resize their column count in Cinema 4D and you never define the column count in your code. In general, I would also say that the approach you want to use here, place multiple items in a group, and then dynamically rearrange them there, is very labor intensive, because you have then to interact with and respect all the dynamic GUI handling Cinema 4D does. I would strongly recommend implementing a folder view as a singleGeUserArea
gadget, where you draw all items manually and have full control over spacing and other things.When you are hell-bent on doing it in this manner, you can find below a draft. You must flush parts of the dialog on resize events and then rebuild the relevant group(s) with a column count which fits the new dialog width. But there are multiple hoops to jump through, especially when this all must happen inside a scroll view.
Cheers,
FerdinandThe result:
The code:"""Example for a dialog which dynamically manages its gadgets based on its width. The pattern shown here works, but is not very advisable. It would be better to implement such things with GeUserArea, where the area renders all buttons and is wrapped by a scroll group. The solution shown here is harder to handle and less clean, and has therefore still some glitches here and there, especially when scaling back the width of dialog (you would have to communicate the scaling direction to RebuildFolderView() and handle the item row count differently based on that). The major problem with your code was that you assumed dialog groups in Cinema 4D to work like a StackPanel, i.e., that things are dynamically arranged in that box. And while they have that quality to some degree, they are in essence more like a grid gadget. When you set a group to have 10 columns, Cinema 4D will always just place 10 items in each row, no matter if each item is just 1px wide, and the group having a width of 1000px. The row and column count of groups is also static. So, no matter how much you resize a group, its row and column count will always stay the same, even when items could be placed more densely with more columns and less rows. In your code you used the row and column count (0, 0) which will cause Cinema 4D to place the items in a fit fashion towards the default size of a dialog. But as lined out above, this will not dynamically change based on the current dialog size. """ import c4d import math class MyDialog(c4d.gui.GeDialog): """Implements a dialog which dynamically manages its gadgets based on its width. """ # The gadget IDs, more or less the same as yours. ID_GRP_FOLDER_SCROLL is the scroll group, # ID_GRP_FOLDER_CONTAINER the container in the scroll group, and ID_GDT_BASE is then the # base ID for all gadgets in that container. ID_GRP_SCROLL: int = 1000 ID_GRP_CONTAINER: int = 1001 ID_GDT_BASE: int = 2000 # The icon width, number of items and item width. As explained below, 'guessing' the item width # is just a lazy approach, GeDialog.GetItemDim would be more precise. ICON_WIDTH: int = 50 ITEM_COUNT: int = 42 ITEM_WIDTH: int = 55 # The bitmap button data container, always the same, can therefore be attached to the class. DATA_BITMAPBUTTON: c4d.BaseContainer = c4d.BaseContainer() DATA_BITMAPBUTTON[c4d.BITMAPBUTTON_BUTTON] = True DATA_BITMAPBUTTON[c4d.BITMAPBUTTON_ICONID1] = 1052837 DATA_BITMAPBUTTON[c4d.BITMAPBUTTON_BACKCOLOR] = c4d.COLOR_BG DATA_BITMAPBUTTON[c4d.BITMAPBUTTON_DISABLE_FADING] = False DATA_BITMAPBUTTON[c4d.BITMAPBUTTON_FORCE_SIZE] = ICON_WIDTH def __init__(self) -> None: """ """ # The dialog width for which a dialog state has been built. self._widthCache: int = c4d.NOTOK super().__init__() def CreateLayout(self): """Creates the layout for the dialog. """ # The scroll group, this gadget is never being removed. It is important to allow here both # horizontal and vertical scrolling because otherwise it will be impossible the scale the # dialog horizontally down. self.ScrollGroupBegin(MyDialog.ID_GRP_SCROLL, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, c4d.SCROLLGROUP_HORIZ | c4d.SCROLLGROUP_VERT, 80, 80) self.GroupBorderSpace(5, 5, 5, 5) # The container which is being flushed when the dialog is resized, this gadget is never # being removed. self.GroupBegin(MyDialog.ID_GRP_CONTAINER, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT) # Build the dynamically crated content. self.RebuildFolderView() self.GroupEnd() self.GroupEnd() return True def RebuildFolderView(self, width: int = 400) -> None: """Builds the dynamic folder list. """ # Calculate the number of items which fit into the width of the dialog. MyDialog.ITEM_WIDTH # is sort of a cheap hack here. MyDialog.ICON_WIDTH is not the correct value, because there # is space being added around items which must be accounted for. A more precise approach # would be GeDialog.GetItemDim called on one of the button label container created below. itemsPerRow: int = int(math.floor(width / MyDialog.ITEM_WIDTH)) print(f"\t{width = }, {itemsPerRow = }") # Flush all gadgets in ID_GRP_CONTAINER and place the gadget cursor after ID_GRP_CONTAINER. # When this method is being called from CreateLayout, this will not do anything. self.LayoutFlushGroup(MyDialog.ID_GRP_CONTAINER) # Create a group inside ID_GRP_CONTAINER where we can set the number of items per row, i.e., # it looks like this: # # ID_GRP_SCROLL # ID_GRP_CONTAINER # [ID_GDT_BASE] <--- The group here created. # ID_BTN_1 # ID_BTN_2 # ... # # This is necessary because (A) scroll groups itself cannot be flushed and (B) we must # dynamically change the number of rows in the group where we place the buttons. Since # we flush ID_GRP_CONTAINER (i.e., that gadget itself is static), we need this group here. self.GroupBegin(MyDialog.ID_GDT_BASE, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, itemsPerRow) # More or less your code. for i in range(MyDialog.ITEM_COUNT): idBase: int = MyDialog.ID_GDT_BASE + 1 + (i * 3) label: str = f"Label {i + 1}" self.GroupBegin(idBase, c4d.BFH_LEFT, 1, 2) self.AddCustomGui(idBase + 1, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 0, 0, MyDialog.DATA_BITMAPBUTTON) self.AddStaticText(idBase + 2, c4d.BFH_SCALEFIT, MyDialog.ICON_WIDTH, name=label) self.GroupEnd() def IsUpdateEvent(self, width: int) -> bool: """Determines if the dynamically created gadgets of the dialog should be rebuilt based on the passed dialog width. Doing this is necessary, especially the `(abs(self._widthCache - width) >= self.ITEM_WIDTH)` part, because otherwise `RebuildFolderView()` will introduce a feedback loop where the dialog changes its size, then RebuildFolderView() is called, which will cause gadgets to resize, which in turn will cause the dialog resize (to the same or marginally larger size), which then will cause RebuildFolderView() to be called, ... """ # Something did pass garbage. if not isinstance(width, int): return False # either the width cache has not been set yet, i.e., the dialog has never been resized, or # the current dialog width requires the gadgets to be rebuild. elif (self._widthCache == c4d.NOTOK) or (abs(self._widthCache - width) >= self.ITEM_WIDTH): self._widthCache = width return True # The current gadget layout is still the optimal one for this dialog width. return False def Message(self, msg: c4d.BaseContainer, result: c4d.BaseContainer) -> int: """Called by Cinema 4D to convey events to the dialog. Here used to react to size changes of the dialog. """ # When the dialog has been resized, ... if msg.GetId() == c4d.BFM_ADJUSTSIZE: # and the new width requires the gadgets to be rebuilt, ... width: int = msg[c4d.BFM_ADJUSTSIZE_WIDTH] if self.IsUpdateEvent(width): print(f"Update: {width}") # Then rebuild the dynamic gadgets and invoke an update event on #ID_GRP_CONTAINER. self.RebuildFolderView(width) self.LayoutChanged(MyDialog.ID_GRP_CONTAINER) else: print(f"No update: {width}") return super().Message(msg, result) def main(): global dialog dialog = MyDialog() dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, defaultw=500, defaulth=500) if __name__ == '__main__': main()
- (2.) You can detect also bitmap button clicks just as all gadget interactions with
-
thank you very much! It's help me a lot! sorry about the multiple topics, I'll take them apart next time