Gray Out Custom UI Bitmap Button?
bentraje last edited by bentraje
Is there a way I can gray out a button that doesn't need a specific condition?
You can see an illustration of the problem here:
I used the
BITMAPBUTTON_ICONID2but I am having a problem on implementing it.
Here is the code so far:
import c4d from c4d import bitmaps, documents, gui, plugins, threading, utils PLUGIN_ID = 1011328 class MyDialog(gui.GeDialog): def CreateLayout(self): self.SetTitle('Colorizer') self.doc = c4d.documents.GetActiveDocument() # Prepare a red bitmap for the button. w = 50 h = 50 # BitmapButton configuration bcBitmapButton = c4d.BaseContainer() bcBitmapButton[c4d.BITMAPBUTTON_BUTTON] = True bcBitmapButton[c4d.BITMAPBUTTON_ICONID1] = c4d.Ocube bcBitmapButton[c4d.BITMAPBUTTON_ICONID2] = c4d.Oplane # should be the greyed out Ocube buttonId = 2000 _bitmapButton = self.AddCustomGui(buttonId, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_CENTER|c4d.BFV_CENTER, w, h, bcBitmapButton) icon = c4d.bitmaps.InitResourceBitmap(c4d.Ocube) _bitmapButton.SetImage(icon, True) if len(doc.GetActiveObjects())>0: _bitmapButton.SetToggleState(0) else: _bitmapButton.SetToggleState(1) return True def Command(self, id, msg): if id==2000 : print "Create Cube" return True class MyMenuPlugin(plugins.CommandData): dialog = None def Execute(self, doc): # create the dialog if self.dialog is None: self.dialog = MyDialog() return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=200, defaulth=150, xpos=-1, ypos=-1) def RestoreLayout(self, sec_ref): # manage the dialog if self.dialog is None: self.dialog = MyDialog() return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref) if __name__ == "__main__": okyn = plugins.RegisterCommandPlugin(PLUGIN_ID, "Cubey",0, None, "Cubey initialized", MyMenuPlugin()) if (okyn): print "Cubey initialized"
Thank you for looking at my problem
m_adam last edited by
The correct way would be to have a timer or to forward the GetState method from the CommandData to the GeDialog. And in this method call GeDialog.Enable(buttonId, False).
Let me know if it fulfills your needs.
Thanks for the response.
RE: And in this method call GeDialog.Enable(buttonId, False).
This was clear to me. I was able to dim it down.
But how do I implement this one:
The correct way would be to have a timer or to forward the GetState method from the CommandData to the GeDialog.
m_adam last edited by m_adam
Hi, sorry I totally forget about EVMSG_CHANGE which is called when anything in the scene changed.
Side note doesn't store the current active document in a class variable since the active document could change while the dialog is opened, so the preferred way is to retrieve it each time (I've done it through a python property).
class MyDialog(c4d.gui.GeDialog): buttonId = 2000 @property def doc(self): return c4d.documents.GetActiveDocument() def CreateLayout(self): self.SetTitle('Colorizer') # Prepare a red bitmap for the button. w = 50 h = 50 # BitmapButton configuration bcBitmapButton = c4d.BaseContainer() bcBitmapButton[c4d.BITMAPBUTTON_BUTTON] = True bcBitmapButton[c4d.BITMAPBUTTON_ICONID1] = c4d.Ocube bcBitmapButton[c4d.BITMAPBUTTON_ICONID2] = c4d.Oplane # should be the greyed out Ocube _bitmapButton = self.AddCustomGui(self.buttonId, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_CENTER|c4d.BFV_CENTER, w, h, bcBitmapButton) icon = c4d.bitmaps.InitResourceBitmap(c4d.Ocube) _bitmapButton.SetImage(icon, True) return True def InitValues(self): self.SetEnableCheck() return True def SetEnableCheck(self): if len(self.doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE))>0: self.Enable(self.buttonId, True) else: self.Enable(self.buttonId, False) def Command(self, id, msg): if id==2000 : print "Create Cube" return True def CoreMessage(self, id, data): if id == c4d.EVMSG_CHANGE: self.SetEnableCheck() return True
Cairyn last edited by
@m_adam Side question: Is this really how it's done in general?
EVMSG_CHANGE(which happens often) the dialog checks the enabling status for this button by looking up
GetActiveObjects()which returns a list. So, the system had to assemble a list of active objects which may have hundreds of entries out of thousands of living objects in the scene.
Now there are hundreds of icons in my layout, and a good deal of them have a functionality like this. Being
CommandDataobjects, they cannot share the result of
GetActiveObjects()(like it would be possible in a dialog), so for each of them, this overhead is created just to determine whether something is selected?
I know that we measure processor speed in billions of executions per second, but doing this on
EVMSG_CHANGEstill seems incredibly wasteful. Maybe my understanding of the overhead is wrong and we could perform much more stuff during an
EVMSG_CHANGEthan I currently am comfortable with?
Thanks for the response. It works as expected. It took me a while to get the code.
I was thinking how is the
id == c4d.EVMSG_CHANGEwhere the id
buttonId = 2000. Then it hit me, the
CoreMessageis actually different ids .
Hahaha. Anyhow thanks!
Just out of curiosity. I'm assuming you are not using the workflow above, may I ask how would you have done it?
Cairyn last edited by
@bentraje I do not have the issue at the moment; my last plugins were all independent of selections and therefore always active. When I program for myself, I normally skip the niceties and test the requirements after clicking the button; I can always stop execution after that.
Currently I am working on writing a Python tutorial series - the Python script templates have a State() function but don't tell me when they are called. Maybe that actually IS during an EVMSG_CHANGE.
Given the internal messaging system of C4D, it seems logical. It just is a lot of stuff to do during that timeframe.
m_adam last edited by m_adam
@Cairyn sorry for the delay, I missed your reply.
React to EVMSG_CHANGE is used a lot (I agree with you, performance-wise it's not the best idea, but until everything is moved to the new core, this is like that).
So as long as you don't do something fancy there, this is not a big issue, and it's also what all render engine do to tracks change into the current document to refresh their IPR.
GetState is called at each UI redraw so it's called multiple time per frames each frame (so I would say it's even more critical than EVMSG_CHANGE, as you can really slow down Cinema 4D quickly)
Unfortunately, currently this is the only way.
Gotcha. Thanks for sharing!
". . . but until everything is moved to the new core"
That's a nice tease there :)