HELP! Displaying Bitmaps in nested sizers problem - Newbie

Hi, please could someone point a programming newbie in the right
direction?

I'm working on my first wxPython app, using Python 2.5.2 and wxPython
2.6.3.2.2, under Ubuntu Linux 8.04 (Hardy Heron). Eventually I hope it
will be a front end to dgen, the megadrive/genesis emulator.

There's a menu bar, tool bar, status bar and a main panel. The main
panel has four children, 2 list boxes, a static bitmap and a static box.
I've then nested two vertical box sizers into a horizontal box sizer.
The result is a roughly quartered main panel - two list boxes on the
left (listing roms and save games), a static bitmap on the top-right
(in-game screenshots) and a static box on the bottom-right (rom
info/codes).

It looks ok and at start-up it will load a bitmap into the top-right
static bitmap, scaling it to the required initial size. If I click on
another item in the list of roms, it will load another bitmap, again
scaling it to the right size no matter what the size of the main window.
My problem comes when maximising/minimising or re-sizing the main
window. The sizers reposition the items lovely, except for the static
bitmap which stays the same size, i.e. if window is maximised the image
won't fill the whole sizer, but it will once I select another rom/game
in the list; if I then minimise or re-size from this state most of the
image is now obscured.

I sort of expected this and had already thought that I may have to
capture the resize event of the app and place some code there that will
re-scale the bitmap. However when I capture the re-size event to a
rescalebitmap function it would appear to get executed before the gui is
initially fully built and throws up errors (i.e. (python:17605):
Gtk-WARNING **: gtk_widget_size_allocate(): attempt to allocate widget
with width -4 and height 17). Not only that but all the sizers seem to
get messed up and I end up with a near blank panel except for some kind
of small mess in the top-left hand corner of the panel. If you run the
attached code commenting/uncommenting out line 65 as required, you'll
see what I mean.

It's driving me mad. Could someone please advise me of an alternative
method to get the image to fill the sizer on resize of the main
window/panel or the proper way to bind the SIZE/SIZING events. I would
be very grateful.

Thanks in advance.

Rgds,

Martin

Cut-Down Sample Code Attached:

test.py (3.19 KB)

Hi Martin,

Hi, please could someone point a programming newbie in the right
direction?

I'm working on my first wxPython app, using Python 2.5.2 and wxPython
2.6.3.2.2, under Ubuntu Linux 8.04 (Hardy Heron). Eventually I hope it
will be a front end to dgen, the megadrive/genesis emulator.

There's a menu bar, tool bar, status bar and a main panel. The main
panel has four children, 2 list boxes, a static bitmap and a static box.
I've then nested two vertical box sizers into a horizontal box sizer.
The result is a roughly quartered main panel - two list boxes on the
left (listing roms and save games), a static bitmap on the top-right
(in-game screenshots) and a static box on the bottom-right (rom
info/codes).

It looks ok and at start-up it will load a bitmap into the top-right
static bitmap, scaling it to the required initial size. If I click on
another item in the list of roms, it will load another bitmap, again
scaling it to the right size no matter what the size of the main window.
My problem comes when maximising/minimising or re-sizing the main
window. The sizers reposition the items lovely, except for the static
bitmap which stays the same size, i.e. if window is maximised the image
won't fill the whole sizer, but it will once I select another rom/game
in the list; if I then minimise or re-size from this state most of the
image is now obscured.

I sort of expected this and had already thought that I may have to
capture the resize event of the app and place some code there that will
re-scale the bitmap. However when I capture the re-size event to a
rescalebitmap function it would appear to get executed before the gui is
initially fully built and throws up errors (i.e. (python:17605):
Gtk-WARNING **: gtk_widget_size_allocate(): attempt to allocate widget
with width -4 and height 17). Not only that but all the sizers seem to
get messed up and I end up with a near blank panel except for some kind
of small mess in the top-left hand corner of the panel. If you run the
attached code commenting/uncommenting out line 65 as required, you'll
see what I mean.

It's driving me mad. Could someone please advise me of an alternative
method to get the image to fill the sizer on resize of the main
window/panel or the proper way to bind the SIZE/SIZING events. I would
be very grateful.

Thanks in advance.

Rgds,

Martin

Cut-Down Sample Code Attached:

Try adding a "self.mainPanel.Layout()" at the end of the method. You may need to call Refresh() too, but I think the Layout call will do the trick, although you may need to use wx.CallAfter to make sure the Layout() or the refresh method itself is called after the event processing finishes. Was that clear as mud?

Mike

Mike, thankyou. Fairly clear. I think I understood, similar to things
I've already googled, but sadly no it doesn't work.

I think the fact that I've bound the EVT_SIZE method, the application
decides not to do any of the routines it would normally do during a
resize. I believe it decides only to do exactly what I've specified in
the method I've bound it to and ignores all of its' other 'normal'
duties, which probably is what the wxPython developers intended! I've
tried things like event.Skip(), event.Dispatch(), event.Yield() - all
no.

The beginning of the method is the part giving the errors so calling
self.mainPanel.Layout()/Refresh() at the end does nothing different, nor
does it work if I put it at the beginning of the method.

I don't really know where to put wx.CallAfter in the RescaleBitmap
method, as wx.CallAfters' parameters (as I understand it) is to call
another method/function only after the method it's in has completed, but
it's the actual RescaleBitmap method I've written that's causing the
problem. I also tried this in the __init__:

self.Bind(wx.EVT_SIZE, wx.CallAfter(self.RescaleBitmap))

but all that gave is the following error:

Traceback (most recent call last):
  File
"/usr/lib/python2.5/site-packages/wx-2.6-gtk2-unicode/wx/_core.py", line
13535, in <lambda>
    lambda event: event.callable(*event.args, **event.kw) )
TypeError: RescaleBitmap() takes exactly 2 arguments (1 given)

This can't be beyond the capabilities of wxPython? I know it must be
something I'm doing wrong as there must be image displaying programs
that handle the re-size event and scale their images somehow? Would I
need to define a custom event (probably beyond my current capabilities)?

Sadly, I'm close to giving up on this one, but many thanks for all your
help.

Rgds,

Martin

···

On Thu, 2008-11-13 at 10:06 -0600, Mike Driscoll wrote:

Try adding a "self.mainPanel.Layout()" at the end of the method. You may
need to call Refresh() too, but I think the Layout call will do the
trick, although you may need to use wx.CallAfter to make sure the
Layout() or the refresh method itself is called after the event
processing finishes. Was that clear as mud?

Mike

Martin Collins wrote:

self.Bind(wx.EVT_SIZE, wx.CallAfter(self.RescaleBitmap))

but all that gave is the following error:

Traceback (most recent call last):
  File
"/usr/lib/python2.5/site-packages/wx-2.6-gtk2-unicode/wx/_core.py", line
13535, in <lambda>
    lambda event: event.callable(*event.args, **event.kw) )
TypeError: RescaleBitmap() takes exactly 2 arguments (1 given)

that's because Rescalebitmap apparently takes an argument. You can do:
self.Bind(wx.EVT_SIZE, wx.CallAfter(self.RescaleBitmap, parameter))

This can't be beyond the capabilities of wxPython?

not at all.

Sorry, I"ve lost track of how you are trying to do this.

My suggestion:

write your own auto-sizing bitmap class, and test it out by putting it, in a very simple app, just a frame at first.

I'd probably draw the bitmap on a panel with a DC.DrawBitmap call in the OnPaint handler. You can resize the bitmap to the right size in an EVT_SIZE handler. It should be pretty straighforward.

If you have trouble with that, post your simple app here.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@noaa.gov

Martin Collins wrote:

I think the fact that I've bound the EVT_SIZE method, the application
decides not to do any of the routines it would normally do during a
resize. I believe it decides only to do exactly what I've specified in
the method I've bound it to and ignores all of its' other 'normal'
duties, which probably is what the wxPython developers intended!

Yes it is. Specifically in this case the code that processes the sizers to cause them to do their layout job is in the default EVT_SIZE handler. By intercepting that event you are telling the system that you will handle it and that the default handler should not be called. If you do want the default (or other bound handlers) to be called then you can use event.Skip() and the event processing system will act as if it didn't actually find your handler and it will keep looking for one.

I've
tried things like event.Skip(), event.Dispatch(), event.Yield() - all
no.

event.Skip makes a difference in your sample code for me.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Christopher Barker wrote:

Martin Collins wrote:

self.Bind(wx.EVT_SIZE, wx.CallAfter(self.RescaleBitmap))

but all that gave is the following error:

Traceback (most recent call last):
  File
"/usr/lib/python2.5/site-packages/wx-2.6-gtk2-unicode/wx/_core.py", line
13535, in <lambda>
    lambda event: event.callable(*event.args, **event.kw) )
TypeError: RescaleBitmap() takes exactly 2 arguments (1 given)

that's because Rescalebitmap apparently takes an argument. You can do:
self.Bind(wx.EVT_SIZE, wx.CallAfter(self.RescaleBitmap, parameter))

That will still be a problem though. Bind takes a function to be caled when the event happens, but the return value of wx.CallAfter is None, so Bind will not actually bind anything to the event. (Actually it will try to unbind the handler for that event.)

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Oh Joy, I did it! I'm so pleased. Thanks everyone for all your help. It
wasn't as difficult as I made it in the end. I still bound wx.EVT_SIZE
to a method OnSize and in there put the following code:

def OnSize(self, event):
                
    appSize = event.GetSize()
    width = appSize[0] - 2
    height = appSize[1] - 86
    self.mainPanel.SetSize((width, height))
    self.RescaleBitmap(self.GetSelectedGameIndex())

All that needed doing was to manually set the size of the main panel and
those lovely child sizers did the rest. Main panel has in this instance,
a fixed size in relation to the application window and hey presto,
magic! Mind you it didn't help that I had missed off some brackets so
that last line was reading as follows (for ages):

self.RescaleBitmap(self.GetSelectedGameIndex)

so rather than return an integer, as GetSelectedGameIndex should do, it
was returning null without throwing up an error msg in the console,
which caused more head scratching.

Problems like these can only make me a better programmer (I doubt I
could have got any worse!!).

Cheers,

Martin

···

On Fri, 2008-11-14 at 15:59 -0800, Robin Dunn wrote:

Martin Collins wrote:
> I think the fact that I've bound the EVT_SIZE method, the application
> decides not to do any of the routines it would normally do during a
> resize. I believe it decides only to do exactly what I've specified in
> the method I've bound it to and ignores all of its' other 'normal'
> duties, which probably is what the wxPython developers intended!

Yes it is. Specifically in this case the code that processes the sizers
to cause them to do their layout job is in the default EVT_SIZE handler.
By intercepting that event you are telling the system that you will
handle it and that the default handler should not be called. If you do
want the default (or other bound handlers) to be called then you can use
event.Skip() and the event processing system will act as if it didn't
actually find your handler and it will keep looking for one.

> I've
> tried things like event.Skip(), event.Dispatch(), event.Yield() - all
> no.

event.Skip makes a difference in your sample code for me.