wxWidgets / Phoenix

wxPython's Project Phoenix. A new implementation of wxPython, better, stronger, faster than he was before.
http://wxpython.org/
2.34k stars 517 forks source link

wx.lib.buttons.GenBitmapButton do not handle wx.NullBitmap properly #2093

Open adixmasz opened 2 years ago

adixmasz commented 2 years ago

Operating system: Windows 10, 64-bit wxPython version: wxPython==4.1.1 from pypi Python version: Python 3.9.10, downloaded from python.org as 64-bit executable for windows

wx.lib.buttons.GenBitmapButton widget do not handle (bitmap=wx.NullBitmap) properly. You need to modify SetBitmapLabel() and DrawLabel() functions to handle bmp==wx.EmptyBitmap and do not draw it.

Error message: Traceback (most recent call last): File "E:\004_Elwas\Projekty\oxy\Software\gui\problem.py", line 17, in <module> frame = MainFrame(None) File "E:\004_Elwas\Projekty\oxy\Software\gui\problem.py", line 10, in __init__ self.refresh_button = buttons.GenBitmapButton( self, wx.ID_ANY, wx.NullBitmap, wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW|wx.BORDER_NONE ) File "E:\004_Elwas\Projekty\oxy\Software\gui\.venv\lib\site-packages\wx\lib\buttons.py", line 704, in __init__ self.SetBitmapLabel(bitmap) File "E:\004_Elwas\Projekty\oxy\Software\gui\.venv\lib\site-packages\wx\lib\buttons.py", line 822, in SetBitmapLabel image = bitmap.ConvertToImage() wx._core.wxAssertionError: C++ assertion ""hbmp"" failed at ..\..\src\msw\dib.cpp(155) in wxDIB::Create(): wxDIB::Create(): invalid bitmap

Code Example (click to expand) ```python import wx import wx.xrc import wx.lib.buttons as buttons class MainFrame ( wx.Frame ): def __init__( self, parent ): wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"App", pos = wx.DefaultPosition, size = wx.Size( 600,400 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL ) main_sizer = wx.BoxSizer( wx.VERTICAL ) self.refresh_button = buttons.GenBitmapButton( self, wx.ID_ANY, wx.NullBitmap, wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW|wx.BORDER_NONE ) main_sizer.Add( self.refresh_button, 1, wx.ALL | wx.EXPAND, 5 ) def __del__( self ): pass app = wx.App() frame = MainFrame(None) frame.Show(True) app.MainLoop() ```
adixmasz commented 2 years ago

I have changed that two functions:

    def SetBitmapLabel(self, bitmap, createOthers=True):
        """
        Set the bitmap to display normally.
        This is the only one that is required.

        If `createOthers` is ``True``, then the other bitmaps will be generated
        on the fly.  Currently, only the disabled bitmap is generated.

        :param wx.Bitmap `bitmap`: the bitmap for the normal button appearance.

        .. note:: This is the bitmap used for the unselected state, and for all other
           states if no other bitmaps are provided.
        """

        self.bmpLabel = bitmap
        if bitmap is not None and bitmap != wx.NullBitmap and createOthers:
            image = bitmap.ConvertToImage()
            imageutils.grayOut(image)
            self.SetBitmapDisabled(wx.Bitmap(image))

and

    def DrawLabel(self, dc, width, height, dx=0, dy=0):
        bmp = self.bmpLabel
        if self.bmpDisabled and not self.IsEnabled():
            bmp = self.bmpDisabled
        if self.bmpFocus and self.hasFocus:
            bmp = self.bmpFocus
        if self.bmpSelected and not self.up:
            bmp = self.bmpSelected

        if bmp != wx.NullBitmap:    
            bw,bh = bmp.GetWidth(), bmp.GetHeight()
            if not self.up:
                dx = dy = self.labelDelta
            hasMask = bmp.GetMask() is not None
            dc.DrawBitmap(bmp, (width-bw)/2+dx, (height-bh)/2+dy, hasMask)
komoto48g commented 2 years ago

You can't assign a null bitmap to a button since 4.1. 😕 For example, see: https://discuss.wxpython.org/t/the-null-bitmap-is-forbidden-from-4-1/35386/3 I hope this issue is fixed. Can you consider both NullBitmap and (0, 0)-sized bitmaps?

adixmasz commented 2 years ago

@komoto48g I can use (0, 0)-sized bitmaps as follows:

import wx
import wx.xrc
import wx.lib.buttons as buttons

class MainFrame ( wx.Frame ):
    def __init__( self, parent ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"App", pos = wx.DefaultPosition, size = wx.Size( 600,400 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )

        main_sizer = wx.BoxSizer( wx.VERTICAL )
        data = bytearray((0,0,0))
        alpha = bytearray((0,))
        image = wx.Image(1, 1, data, alpha)
        bmp = image.ConvertToBitmap()
        self.refresh_button = buttons.GenBitmapButton( self, wx.ID_ANY, bmp, wx.DefaultPosition, wx.DefaultSize, wx.BU_AUTODRAW|wx.BORDER_NONE )
        main_sizer.Add( self.refresh_button, 1, wx.ALL | wx.EXPAND, 5 )

    def __del__( self ):
        pass

app = wx.App()
frame = MainFrame(None)
frame.Show(True)
app.MainLoop()

It works ok.

Why are default argumnts (in GenBitmapButton constructors) invalid?

class GenBitmapButton(GenButton):
    """ A generic bitmap button. """

    def __init__(self, parent, id=-1, bitmap=wx.NullBitmap,
                 pos = wx.DefaultPosition, size = wx.DefaultSize,
                 style = 0, validator = wx.DefaultValidator,
                 name = "genbutton"):

I just can't use a default constructor for that object. I get an error in that specific case and I think it should be improve.

import wx
import wx.xrc
import wx.lib.buttons as buttons

class MainFrame ( wx.Frame ):
    def __init__( self, parent ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = u"App", pos = wx.DefaultPosition, size = wx.Size( 600,400 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )

        main_sizer = wx.BoxSizer( wx.VERTICAL )
        self.refresh_button = buttons.GenBitmapButton( self, wx.ID_ANY)
        main_sizer.Add( self.refresh_button, 1, wx.ALL | wx.EXPAND, 5 )

    def __del__( self ):
        pass

app = wx.App()
frame = MainFrame(None)
frame.Show(True)
app.MainLoop()
komoto48g commented 2 years ago

I agree with you. They shouldn't raise an error with the default arguments. I have not tested all cases with your modifications, but it seems valid. PS. I meant wx.Bitmap(0,0), but I'm sorry, I forgot that since wx 4.1, the creation of (0,0)-sized bitmaps is prohibited. I don't think we need to consider (0,0)-sized bitmaps anymore.

>>> wx.Bitmap(0,0)
Traceback (most recent call last):
  File "<input>", line 1, in <module>
wx._core.wxAssertionError: C++ assertion ""w > 0 && h > 0"" failed at... in wxBitmap::DoCreate(): invalid bitmap size