wx.Panel with background image

Here’s the problem. I created a custom object Tile(wx.Panel). It consists of a wx.GridSizer containing a 3x3 grid of square buttons labeled “1” through “9”. As the user clicks on a button, the button (and that digit) are removed from the display (button.Hide()). When there is only one button remaining the tile is considered “solved” and I hide the remaining button and

        image = r'd:\script\python\sudoku\images\D' + digit + '.jpg'
        bmp = wx.Image(image, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
        self.bitmap = wx.StaticBitmap(self, -1, bmp, pos=(0, 0), size=(72,72))

display the solved digit in the Tile. So far so good. But when I want to reset the app so a new puzzle can be entered I want to clear the background image and unhide all of the buttons. I have tried two ways. One is to set the background image to a bitmap which is all white and the other is as follows

        self.bitmap = wx.StaticBitmap(self, -1, pos=(0, 0), size=(72,72))

With the first method I end up with blank tiles. If I move the mouse cursor over the buttons they appear one by one. With the second method, any tile that had the background image set to a digit leaves that digit displayed (with the buttons appearing on a mouse_enter). Other tiles show the buttons as required.

I can either try to figure out why the buttons don’t automatically render on a button.Show() or try to find some way to remove the background image from the tile (method two obviously doesn’t do that). I’ve been running around in circles for two days trying to figure this out.

FYI - My tile is the most basic component of a Sudoku tool that I wrote (successfully) in vb.NET. I am porting it to Python/wxPython as a project to learn both.

wxPython version 4.0.6
Python version 3.7.3
Windows 10 Home

Here is a stripped down sample that illustrates the problem. Change the value of IMAGEFILE to a jpg file on your computer. When you run the app you will see a frame with two buttons on the top, and a grid of nine buttons below. The 3x3 grid is in a wx.Panel. The simplest illustration is to click Show Image. This will cause your image to be displayed on top of the buttons. Moving the mouse over the buttons will cause the masked buttons to be redrawn. In my project I want to display the image once the buttons are all hidden. For reasons I do not understand, while my full Sudoku all also fails to display properly once a background bitmap has been set, it fails in a slightly different way. I suspect that once I understand why the sample behaves the way it does (and how to fix it) I will be able to fix the actual app. It may come down to how and when (and on what) to do a Refresh().

import wx

IMAGEFILE = 'd:/temp/resize/DSCF5157.JPG'

class App(wx.App):

    def OnInit(self):
        self.frame = Frame()
        return True

class Frame(wx.Frame):

    def __init__(self):

        super(Frame, self).__init__(None, title="Test", style=wx.CLOSE_BOX | wx.CAPTION)

        self.SetSize(380,440)
        self.Centre()

        vsizer = wx.BoxSizer(wx.VERTICAL)

        button = wx.Button(self, label='Reset', size=(120,30))
        button.Bind(wx.EVT_BUTTON, self.Reset_OnClick)
        vsizer.Add(button,0,wx.EXPAND,0)

        button = wx.Button(self, label='Show Image', size=(120,30))
        button.Bind(wx.EVT_BUTTON, self.Show_OnClick)
        vsizer.Add(button,0,wx.EXPAND,0)

        self.tile = Tile(self, name='Tile')
        vsizer.Add(self.tile,1,wx.EXPAND,0)

        self.SetSizer(vsizer)
        self.Show()

    def Reset_OnClick(self, event):
        self.tile.Reset()

    def Show_OnClick(self, event):
        self.tile.ShowImage(IMAGEFILE)

class Tile(wx.Panel):

    def __init__(self, parent, name):

        super(Tile, self).__init__(parent, -1, name=name, style=wx.BORDER_DOUBLE)

        self.buttons = []        

        gsizer = wx.GridSizer(cols=3, vgap=0, hgap=0) 

        for i in range(1,10): 
            button = wx.Button(self,label = str(i), size=(40,40))
            button.name = str(i)
            button.Bind(wx.EVT_BUTTON, self.OnClick)
            self.buttons.append(button)
            gsizer.Add(button,1,wx.EXPAND,0)

        self.SetSizerAndFit(gsizer)

    def Reset(self):

        self.ShowImage('')

        for button in self.buttons: 
            button.Show()

    def OnClick(self, event):

        event.GetEventObject().Hide()

    def ShowImage(self, imageFile):

        if imageFile == "":
            self.bitmap = wx.StaticBitmap(self, -1, size=(0,0))
        else:
            bmp = wx.Image(imageFile, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
            self.bitmap = wx.StaticBitmap(self, -1, bmp, pos=(0, 0), size=(200,200))

# Main ###############################################################

if __name__ == '__main__':
    app = App() 
    app.MainLoop()

In wxWidgets the behavior of overlapping sibling widgets is undefined. They will likely stack up on top of each other, but which one is visible and which one gets the mouse events and so forth is undefined.

If I understand correctly you are wanting to (re)show the buttons where the static bitmaps are now located. wx.StaticBitmaps are widgets too, so you end up in an overlapping situation. Also each time you call wx.StaticBitmap(...) you are creating a new one, and so things can get stacked pretty deep.

The approach I would take is to keep track of your static bitmaps just like you are keeping track of the buttons. When you Hide the buttons then Show the static bitmaps, and vice versa.

I see the problem now. My tile control now has

self.bitmap  = wx.StaticBitmap(self, -1)

as a property. My solved logic becomes

def ShowDigit(self, digit):
    """ Set the background image for this tile to the given digit """

    if digit == '':
        self.bitmap.Hide()
    else:
        self.bitmap.Bitmap = BITMAPS[digit]
        self.bitmap.Show()

Where BITMAPS is loaded from jpg image files. I am over the current hump (thanks to you) and can now get on with the program logic. It’s definitely more complicated than just dragging and dropping controls in vb dot net but all-in-all, I’d rather be programming in Python.

1 Like