Search code examples
wxpython

Cycling through images in wx.StaticBitmap


I'm working on a database of images and want to display each image as it is processed in a wx.StaticBitmap object. As far as I can understand, the line in the function onView, "self.imageCtrl.SetBitmap(wx.Bitmap(self.img))" should change the image. But nothing I do will get it to display. The code is from my explorations into an answer and is part of an image display app that changes the image on each click of the button. This works perfectly but as soon as the code is embedded in the loop it fails to refresh until the loop is complete where the last of the files finally displays.

    import time
    import os
    import wx

    #============ App and panel class ========#
    class PhotoCtrl(wx.App):
        def __init__(self, redirect=False, filename=None):
            wx.App.__init__(self, redirect, filename)
            self.frame = wx.Frame(None, title='Photo Control')

            self.panel = wx.Panel(self.frame)

            self.PhotoMaxSize = 256

            self.createWidgets()
            self.frame.Show()

    #=========== Set up button, wx.StaticBitmap etc ===============#

        def createWidgets(self):
            #instructions = 'Browse for an image'
            img = wx.Image(256,256)
            self.imageCtrl = wx.StaticBitmap(self.panel, wx.ID_ANY, wx.Bitmap(img))
            browseBtn = wx.Button(self.panel, label='Go')
            browseBtn.Bind(wx.EVT_BUTTON, self.onView)

            self.mainSizer = wx.BoxSizer(wx.VERTICAL)
            self.sizer = wx.BoxSizer(wx.HORIZONTAL)

            self.mainSizer.Add(wx.StaticLine(self.panel, wx.ID_ANY),
                               0, wx.ALL|wx.EXPAND, 5)
            #self.mainSizer.Add(instructLbl, 0, wx.ALL, 5)
            self.mainSizer.Add(self.imageCtrl, 0, wx.ALL, 5)
            #self.sizer.Add(self.photoTxt, 0, wx.ALL, 5)
            self.sizer.Add(browseBtn, 0, wx.ALL, 5)        
            self.mainSizer.Add(self.sizer, 0, wx.ALL, 5)

            self.panel.SetSizer(self.mainSizer)
            self.mainSizer.Fit(self.frame)

            self.panel.Layout()

    #=== Toy code to simulate automatic change of image to be displayed ===#

        def onView(self, event):
            for i in range(1,10):
                im_pth = os.getcwd() + f'/Cats/cats/Cats_{i}.png'
                self.img = wx.Image(im_pth, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
                self.image = self.img.ConvertToImage()
                self.img = wx.Bitmap(self.image.Scale(256, 256))
                self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
                self.panel.Refresh()
                print(f"should be showing cat_{i}") #This prints but the image doesn't show
                time.sleep(1)

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


Solution

  • It is because you are in a loop, which does not release control back to the mainloop.
    You need to Yield before time.sleep(1)

    i.e.

    def onView(self, event):
        for i in range(1,10):
            im_pth = os.getcwd() + f'/frame{i}.png'
            self.img = wx.Image(im_pth, wx.BITMAP_TYPE_ANY).ConvertToBitmap()
            self.image = self.img.ConvertToImage()
            self.img = wx.Bitmap(self.image.Scale(256, 256))
            self.imageCtrl.SetBitmap(wx.Bitmap(self.img))
            self.panel.Refresh()
            wx.Yield()
            print(f"should be showing cat_{i}") #This prints but the image doesn't show
            time.sleep(1)
    

    Strictly speaking, under wxPython Phoenix that should be wx.GetApp().Yield() but wx.Yield() still works.