Transparent Bitmap Button

I am pretty new to wxpython and trying to create a basic UI.

I want to create a bitmap button with a transparent background. I have read numerous posts on this topic but have not been able to figure out how to make the solutions work for my situation.

My sample code below is for a simplistic layout to illustrate. I have 2 child panels on a parent panel. The topmost panel is supposed to be cyan with a transparent “X” button on it. I have tried to implement the approach of binding EVT_ERASE_BACKGROUND to a null event handler, and this does cause the button to be transparent, but now my panel is not drawn correctly.

import wx

class Panel1(wx.Panel):
    def __init__(self, parentPanel, *args, **kwargs):
        super().__init__(parentPanel, *args, **kwargs)       
        self.SetBackgroundColour("#009DD6")
            
        btn = wx.BitmapButton(self, -1, wx.Bitmap('img\\close.png'), (120,120), style=wx.NO_BORDER)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add((0,-1), proportion=1)
        hbox.Add(btn)
        self.SetSizer(hbox)
        
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnPaint)      
     
    def OnPaint(self, evt):
        pass        
        
class Panel2(wx.Panel):
    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.SetBackgroundColour(wx.GREEN)
 
class MainFrame(wx.Frame):

    def __init__(self, *args, **kw):
        super().__init__(None, title="test", size=(600,400), *args, **kw)
        
        # Create main panels & titlebar
        rootPanel = wx.Panel(self)
        panel1 = Panel1(rootPanel, size=(-1,50))
        panel2 = Panel2(rootPanel)
        
        # Add main panels to sizer
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(panel1, 0, wx.EXPAND)
        sizer.Add(panel2, 1, wx.EXPAND)
        rootPanel.SetSizer(sizer)        
        
        self.Centre()
        self.Show()

def main():

    app = wx.App()
    mainFrame = MainFrame()
    app.MainLoop()

if __name__ == '__main__':
    main()

If I comment out the line that binds the EVT_ERASE_BACKGROUND event, I get:

If I include that line, the button is transparent, but the panel is never properly drawn (see image in post below).

How can I put a transparent bitmap button on a panel, but still draw the parent panel correctly?

Here is the other image:

        btn = wx.BitmapButton(
            self, -1, wx.ArtProvider.GetBitmap(wx.ART_DELETE,
            size=(20,20)),size=(120,120), style=wx.BORDER_NONE)

:thinking:

This does not produce a transparent button. I want to see the blue background of the panel behind the button.

For a BitmapButton to show a transparent background it simply needs to use an image that contains a transparent background, either in the form of an alpha channel, or as a Mask.

It should not be necessary to add any additional code to make it work. I cannot see how binding a no-op event handler for the panel’s EVT_ERASE_BACKGROUND events could possibly help!

I am surprised that you say that @da-dada’s suggestion of using an image from the ArtProvider doesn’t work as I often use that technique without any problems with transparency.

Well, try the code I provided along with @da-dada’s suggested button and see what it does. For me, I get the gray background below - the button’s bitmap does in fact have an alpha value of 0 (as did my original button), but this makes the button background color revert to the gray shown below rather than the desired blue of the parent panel. Perhaps there is something I am missing - if you know a way to get truly transparent behavior with your bitmap buttons, please enlighten me! See links below for where I originally got the idea of making a null EVT_ERASE_BACKGROUND event handler:

https://wx-users.wxwidgets.narkive.com/nVswszLz/wxbitmapbutton-transparency

https://wx-dev.narkive.com/wkXzPHv6/transparent-images-image-buttons-revisited

(oddly enough, I have found that when I use a bitmap with transparency in a toolbar, it actually works)

On linux I get a different image for wx.ART_DELETE. With the Bind call still in the code, then Panel1 and the BitmapButton both have grey backgrounds.

If I comment out the Bind call I get:

Screenshot at 2024-02-03 08-33-21

However, if I then move the mouse pointer over the BitmapButton, its background colour suddenly changes to light grey.

On linux I get a different image for wx.ART_DELETE. With the Bind call still in the code, then Panel1 and the BitmapButton both have grey backgrounds.

I think it is a Windows issue. The links I provided indicated the EVT_ERASE_BACKGROUND workaround was for Windows. Yes, with the Bind call included, panel 1 is never drawn, and so the panel and button background default to gray.

However, if I then move the mouse pointer over the BitmapButton, its background colour suddenly changes to light grey.

I think since you used a default button, it defaults to recoloring the background on mouse-over events.

Thanks for trying it out! Now I know it is a Windows-specific issue.

well, you may want a plate button :wink:

import wx
import wx.lib.platebtn as pb

class Panel1(wx.Panel):
    def __init__(self, parentPanel, *args, **kwargs):
        super().__init__(parentPanel, *args, **kwargs)
        self.SetBackgroundColour("#009DD6")

        btn = pb.PlateButton(self, size=(30,30))
        btn.SetBitmap(
                wx.ArtProvider.GetBitmap(wx.ART_DELETE, size=(20,20)))
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add((0,-1), proportion=1)
        hbox.Add(btn)
        self.SetSizer(hbox)

class Panel2(wx.Panel):
    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.SetBackgroundColour(wx.GREEN)

class MainFrame(wx.Frame):

    def __init__(self, *args, **kw):
        super().__init__(None, title="test", size=(600,400), *args, **kw)

        # Create main panels & titlebar
        rootPanel = wx.Panel(self)
        panel1 = Panel1(rootPanel, size=(-1,50))
        panel2 = Panel2(rootPanel)

        # Add main panels to sizer
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(panel1, 0, wx.EXPAND)
        sizer.Add(panel2, 1, wx.EXPAND)
        rootPanel.SetSizer(sizer)

        self.Centre()
        self.Show()

def main():

    app = wx.App()
    mainFrame = MainFrame()
    app.MainLoop()

if __name__ == '__main__':
    main()

well, you may want a plate button :wink:

Thanks, that is definitely an interesting option I have not tried before. It does achieve the goal of having a transparent background, although they (plate buttons) appear to be a little funky in the way they respond to mouse-over, and because by default the bitmap used is cropped from the upper left corner rather than centered. This can be worked around of course, just requires some extra steps. I think at this point I would opt for a regular bitmap button where I manually set the background color over the plate button, but I would definitely look into using the plate button for dropdown buttons in a ribbon.

import wx
from wx.lib.buttons import GenBitmapButton


class Panel1(wx.Panel):
    def __init__(self, parentPanel, *args, **kwargs):
        super().__init__(parentPanel, *args, **kwargs)
        self.SetBackgroundColour("#009DD6")

        bmp = wx.ArtProvider.GetBitmap(wx.ART_DELETE, size=(20, 20))
        btn = GenBitmapButton(self, -1, bmp, size=(30, 30), style=wx.BORDER_NONE)

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add((0, -1), proportion=1)
        hbox.Add(btn)
        self.SetSizer(hbox)


class Panel2(wx.Panel):
    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.SetBackgroundColour(wx.GREEN)


class MainFrame(wx.Frame):

    def __init__(self, *args, **kw):
        super().__init__(None, title="test", size=(600, 400), *args, **kw)

        # Create main panels & titlebar
        rootPanel = wx.Panel(self)
        panel1 = Panel1(rootPanel, size=(-1, 50))
        panel2 = Panel2(rootPanel)

        # Add main panels to sizer
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(panel1, 0, wx.EXPAND)
        sizer.Add(panel2, 1, wx.EXPAND)
        rootPanel.SetSizer(sizer)

        self.Centre()
        self.Show()


def main():
    app = wx.App()
    mainFrame = MainFrame()
    app.MainLoop()


if __name__ == '__main__':
    main()

Thanks Richard, I did not know about that whole library of buttons! I like the way this one behaves a lot better than the plate button (it centers the bitmap by default). Only downside is it looks like it has no built in way to set a “mouse-over” bitmap - you can only set a “focused / unfocused” bitmap which is nice but only gives you a change for focus on the parent window. Of course I can do this manually. Is there any other downside to using these buttons in wx.lib rather than the native bitmap buttons?

I find the wxPython Demo program invaluable for exploring what widgets are available, plus providing example code for how to use them. If you’ve not downloaded it yet, look under the appropriate folder at Index of /wxPython4/extras

I can’t think of any off the top of my head. Looking in the wxPython GitHub repo there doesn’t appear to have been many issues raised about them. I have used several of them in my projects, occasionally sub-classing them to add some custom behaviour.

See also: wx.Window — wxPython Phoenix 4.2.1 documentation.
Setting empty EVT_ERASE_BACKGROUND handler was a workaround for the flickering problem.
Now, it should be replaced with:

>>> self.SetBackgroundStyle(wx.BG_STYLE_PAINT) # to avoid flickering

I think no, wx.lib families are quite nice and stable!

https://github.com/Metallicow/MCOW has what you are looking for… Tho it isn’t in the standard lib yet, … Or translated to c/c++ wxWidgets.

https://github.com/Metallicow/MCOW/blob/cbb185d96f8a208eb8fab6e8768ecc0f092c839c/wxPython_Phoenix/mcow/shapedbitmapbutton.py 

… Is probably what you are looking for, but if dealing with fonts or vector(svg) you may need to do more testing before ‘official’.

At the moment, you have to install it manually…
Drag and drop it into the wxPython structure or redirect patching in py files.

This started way back in classic…, Hope this helps.

Phone is a PITA to reply on a forum with BBCode…