Gray Out Custom UI Bitmap Button?
-
Hi,
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:
https://www.dropbox.com/s/rfw2nw8o298r0ej/c4d162_python_gray_out_custom_gui_button.jpg?dl=0I used the
SetToggleState(set)
,BITMAPBUTTON_ICONID1
andBITMAPBUTTON_ICONID2
but 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
-
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.
Cheers,
Maxime. -
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. -
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
Cheers,
Maxime. -
@m_adam Side question: Is this really how it's done in general?
On every
EVMSG_CHANGE
(which happens often) the dialog checks the enabling status for this button by looking upGetActiveObjects()
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
CommandData
objects, they cannot share the result ofGetActiveObjects()
(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_CHANGE
still seems incredibly wasteful. Maybe my understanding of the overhead is wrong and we could perform much more stuff during anEVMSG_CHANGE
than 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 theid == c4d.EVMSG_CHANGE
where the idbuttonId = 2000
. Then it hit me, theid
in theCommand()
andCoreMessage
is 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?
-
@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.
-
@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.
Cheers,
Maxime. -