Solved Creating Class for Buttons Returns None

Hi,

Usually, I create buttons by just adding the vanilla lines. I wanted to create a class for buttons but it returns None and gives and me an error of

line 34, in create
        bmp_btn.SetImage(bmp_color, True)
    AttributeError: 'NoneType' object has no attribute 'SetImage'

Interestingly. The same code works on a vanilla code. So I was thinking there is an additional procedure when creating a class?

You can check the working code here:

import c4d
from c4d import bitmaps, documents, gui, plugins, threading, utils

PLUGIN_ID   = 18113289

class ColorButton(c4d.gui.GeDialog):

    def __init__(self):
        self.width = None
        self.height = None
        self.color = None
        self.color = None
        self.btn_id = 3000


    def create(self, w, h, color,btn_id):

        self.width = w
        self.height = h
        self.color = color

        bmp_color = c4d.bitmaps.BaseBitmap()
        bmp_color.Init(w, h)

        for y in xrange(w):
            for x in xrange(h):
                bmp_color.SetPixel(x, y, color[0], color[1], color[2])

        bcBitmapButton = c4d.BaseContainer()
        bcBitmapButton[c4d.BITMAPBUTTON_BUTTON] = True

        bmp_btn = self.AddCustomGui(btn_id, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_CENTER|c4d.BFV_CENTER, w, h, bcBitmapButton)

        bmp_btn.SetImage(bmp_color, True)

class MyDialog(c4d.gui.GeDialog):

    buttonId = 2000

    def CreateLayout(self):

        self.SetTitle('Class Button')

        # Creating a red button through vanilla lines 
        w = 50
        h = 50
        color=(255,0,0)
        btn_id = 5000

        bmp_color = c4d.bitmaps.BaseBitmap()
        bmp_color.Init(w, h)

        for y in xrange(w):
            for x in xrange(h):
                bmp_color.SetPixel(x, y, color[0], color[1], color[2])

        bcBitmapButton = c4d.BaseContainer()
        bcBitmapButton[c4d.BITMAPBUTTON_BUTTON] = True
        bmp_btn = self.AddCustomGui(btn_id, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_CENTER|c4d.BFV_CENTER, w, h, bcBitmapButton)
        bmp_btn.SetImage(bmp_color, True)

        # Creating a red button through class
        # This part causes the error

        red_button = ColorButton()
        red_button.create(w=50,h=50,color=(255,0,0),btn_id=6000)
                
        return True
    
    def Command(self, id, msg):

        return True
    
    def CoreMessage(self, id, data):

        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, "Class Button",0, None, "Class Button", MyMenuPlugin())
    if (okyn):
        print "Class Button"

Hi @bentraje,

the main issue is that ColorButton is another GeDialog, so that means if you want to draw something you have to open it first.
Either you have to change it to a SubDialog, this way you can embed it within another GeDialog/SubDialog see gedialog_subdialog_r19.py .
Or either you have to use the instance of the dialog that is already open to do the draw operation.

import c4d

class ColorButton(object):

    def __init__(self):
        self.width = None
        self.height = None
        self.color = None
        self.color = None
        self.btn_id = 3000


    def create(self, dlg, w, h, color, btn_id):

        self.width = w
        self.height = h
        self.color = color

        bmp_color = c4d.bitmaps.BaseBitmap()
        bmp_color.Init(w, h)

        for y in xrange(w):
            for x in xrange(h):
                bmp_color.SetPixel(x, y, color[0], color[1], color[2])

        bcBitmapButton = c4d.BaseContainer()
        bcBitmapButton[c4d.BITMAPBUTTON_BUTTON] = True

        bmp_btn = dlg.AddCustomGui(btn_id, c4d.CUSTOMGUI_BITMAPBUTTON, "", c4d.BFH_CENTER | c4d.BFV_CENTER, w, h, bcBitmapButton)

        bmp_btn.SetImage(bmp_color, True)

class MyDialog(c4d.gui.GeDialog):

    def CreateLayout(self):
        # Creating a red button through class
        # This part causes the error

        red_button = ColorButton()
        red_button.create(self, w=50,h=50,color=(255,0,0), btn_id=6000)
                
        return True

Now regarding which solution is best, it is up to you, but I will argue that SubDialog is cleaner since Gadget ID become local to this SubDialog, while if you share an instance of the GeDialog, you have to be sure that all ID is unique.

Hope it makes sense,
Cheers,
Maxime.

Thanks for the response. The code works as expected.
For the option, I think I'll go with "use the instance of the dialog" like in your code above. Seems like an easier way to handle and maintain.

With the AutoID() class you shared, I never had any problem with the ID management now. Hehehe

BTW, just one thing, in your code you wrote
class ColorButton(object):. which makes it that you are inheriting a class, correct me if I'm wrong.

However, it seems like the class ColorButton: seems to work as well

Is there a particular reason that you included (object) ?

https://stackoverflow.com/questions/4015417/python-class-inherits-object
It's just a "good practice" for python 2.X to always derive from object your BaseClass.

Cheers,
Maxime.

@m_adam

Ah. Gotcha. Thanks for the clarification and link.