Zimm3r
Zimm3r

Reputation: 3425

Resize a wxPython Panel without EVT_SIZE being fired

Edit As I was updating this with the bellow edit I realized during my tests that it is something seemingly wrong with the traceback function of python. I will post a new question

Edit: Updated to make it much more simple of a question

The issue

The problem is when calling a function in the EVT_SIZE event handler that has to resize the panel for whatever reason, it will become an infinite loop causing a recursion error.

This use to be solved (in 2.8) with a boolean true/false variable on whether the function inside the EVT_SIZE handler. However this doesn't work in 2.9 because it seems the events have become asynchronous (or at least in some way it means the SetSize call is allowed to return, the flag reset, THEN the event handler is called).

Examples of situations

The Default (no handling of it any way

import wx


class TestPanel(wx.Panel):

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, -1)
        self.Bind(wx.EVT_SIZE, self.OnSize, self)

        wx.CallLater(10000, self.ResizeThing)

    def ResizeThing(self):
        print "Doing our resize thing...."
        # calculate whatever we need to calculate
        # seems we got to change it in some way
        self.SetSize(self.GetSize() + (30, 30))
        return "whatever we'd return"

    def OnSize(self, event):
        print "Size event got to resize"
        self.ResizeThing()


if __name__ == '__main__':
    app = wx.App(False)
    f = wx.Frame(None, -1)
    TestPanel(f)
    f.Show()
    app.MainLoop()

2.8 Solution using a boolean flag

Is there a way to resize a panel without the evt size event firing because when it does it causes an infinite loop (on 2.9 but not 2.8 for some reason).

Notable code

    def __init__(...):
        ..... other stuff....
        self.Bind(wx.EVT_SIZE, self.OnSize, self)

    def OnSize(self, event):
        # if we didn't have it we would have a infinite recursion going evt_size -> update -> evt_size -> update -> evt_size -> update... forever
        if not self.setting_size:
            # update current size and set artwork
            print self.GetSize()
            print self.current_size
            print "OnSize"
            self.current_size = self.GetSize()
            print self.current_size
            self.UpdateArtwork(self.current_img)


        else:
            event.Skip()



    def UpdateArtwork(self, img):
        ... get scaled image and set the bitmap
        img = self.scale(img)
        self.SetBitmap(img.ConvertToBitmap())

    def scale(self, img):
        print "Calling Scale"
        # size the window to the correct dimensions
        # get the size the sizer thinks is best

        wW = self.GetBestVirtualSize()[0] # the width that the sizer reccomends
        wH = self.GetBestVirtualSize()[1] # the height the sizer recommends
        W = img.GetWidth()
        H = img.GetHeight()

        # modifiy the sizer recommendations to fit the image aspect ratio
        # use bigger side as the base
        if wW > wH:
            # round to nearest integer value sense we are using future division
            # get the new wH base on the image aspect ratio
            wH = round(H/W * wW) # H/W how many H pixels per one W
        else:
            wW = round(W/H * wH) # W/H how many W pixels per one H

        ... this is SUPPOSE to prevent a loop by flagging it
        self.setting_size = True
        print time.time()
        print "Setting size in scale..."
        self.current_size = (wW, wH)
        print self.current_size
        self.SetSize((wW, wH))
        print time.time()
        self.setting_size = False
        print time.time()
        # then scale the image based on the panel size
        return img.Scale(wW, wH)

However this doesn't work sense the EVTs are non synchronous so it fires after resetting it to false and then causes an infinite loop!

Full code

# for proper scaling calculations (such things as .6451 going becoming 0)
from __future__ import division
import time

import wx

from twisted.web import client
from twisted.python import log

try:
    import cStringIO as StringIO
except ImportError:
    import StringIO

class URLImage(wx.StaticBitmap):
    '''Allows a easy mechanism for setting url images'''
    def __init__(self, parent, default_image):
        wx.StaticBitmap.__init__(self, parent, -1)

        self.current_img = None
        self.current_url = None
        self.current_size = self.GetSize()

        self.di_d = default_image
        self.default_image = wx.Image(default_image, wx.BITMAP_TYPE_ANY)
        self.current_img = self.default_image
        self.SetArtwork(self.default_image)

        self.Bind(wx.EVT_SIZE, self.OnSize, self)

    def OnSize(self, event):
        # if we didn't have it we would have a infinite recursion going evt_size -> update -> evt_size -> update -> evt_size -> update... forever
        if not self.current_size == self.GetSize():
            # update current size and set artwork
            print self.GetSize()
            print self.current_size
            print "OnSize"
            self.current_size = self.GetSize()
            print self.current_size
            self.UpdateArtwork(self.current_img)


        else:
            event.Skip()

    def SetArtwork(self, img):
        # for bitmaps (use corresponding method for urls)
        self.current_url = None # the current artwork isn't a url, it is a bitmap
        print "SetArtwork Updating Artwork"
        self.UpdateArtwork(img)


    def SetDefaultImage(self):
        # this is to change the image to the default
        # NOT to change the default image to some other one
        # ^(like Text SetLabel changes the Label)
        # similiar to SetArtwork
        self.current_url = None
        print "SetDefault Updating Artwork"
        self.UpdateArtwork(self.default_image)


    def SetArtworkFromURL(self, url = None):

        if url == self.current_url:
            print "[URLImage] Duplicate URL"
            return

        else:
            # set back the defualt art
            print "Defaulting for URL loading"
            self.UpdateArtwork(self.default_image)
            # update current_url
            self.current_url = url

        if url == None:
            return

        d = client.getPage(url.encode("ascii"), method = "GET", agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2b4) Gecko/20091124 Firefox/3.6b4 (.NET CLR 3.5.30729)')
        d.addCallback(lambda data: self.UpdateArtwork(wx.ImageFromStream(StringIO.StringIO(data))))
        d.addErrback(self.error)

    def UpdateArtwork(self, img):
        # ALBUM ART
        # From: http://www.blog.pythonlibrary.org/2010/03/26/creating-a-simple-photo-viewer-with-wxpython/
        # scale the image, preserving the aspect ratio
        self.current_img = img
        print "Update Artwork"
        img = self.scale(img)
        self.SetBitmap(img.ConvertToBitmap())

    def scale(self, img):
        print "Calling Scale"
        # size the window to the correct dimensions
        # get the size the sizer thinks is best

        wW = self.GetBestVirtualSize()[0] # the width that the sizer reccomends
        wH = self.GetBestVirtualSize()[1] # the height the sizer recommends
        W = img.GetWidth()
        H = img.GetHeight()

        # modifiy the sizer recommendations to fit the image aspect ratio
        # use bigger side as the base
        if wW > wH:
            # round to nearest integer value sense we are using future division
            # get the new wH base on the image aspect ratio
            wH = round(H/W * wW) # H/W how many H pixels per one W
        else:
            wW = round(W/H * wH) # W/H how many W pixels per one H

        self.setting_size = True
        print time.time()
        print "Setting size in scale..."
        self.current_size = (wW, wH)
        print self.current_size
        self.SetSize((wW, wH))
        print time.time()
        self.setting_size = False
        print time.time()
        # then scale the image based on the panel size
        return img.Scale(wW, wH)


    def error(self, err_):
        ''' Error callback for fetching the album art'''
        self.current_url = None# no current (succesful) url artwork set
        self.SetArtwork(self.default_image)
        log.msg("Error getting Album Artwork")
        log.err(err_)

Upvotes: 1

Views: 4257

Answers (1)

Joran Beasley
Joran Beasley

Reputation: 114098

how bout just

    print "Setting size in scale..."
    #unbind Size Event
    self.UnBind(wx.EVT_SIZE)

    self.current_size = (wW, wH)
    print self.current_size
    self.SetSize((wW, wH))
    print time.time()
    self.Update() #force update so it does not trigger after rebind
    #rebind Size Event
    self.Bind(wx.EVT_SIZE, self.OnSize, self)

Upvotes: 2

Related Questions