Example of a non-scaling scrolling image view?

I want a widget that shows PNG images.

The widget will vary in size depending on the user sizing the window it is in but I don’t want the image it shows to be scaled. Iinstead I want the image to be shown at its actual size occupying the top-left if it is smaller than the widget or with scrollbars appearing if it is larger than the widget. The images the widget will be given will vary in size, so it needs to adapt whenever it is given a new image.

Does anyone have or can direct me to an example widget that has this kind of behaviour?

There are likely samples of this all over, but it’s fairly straight forward. I would approach it like this:

  1. Derive a new class from wx.Window
  2. In the init set a bitmap attribute to wx.NullBitmap, and bind the EVT_PAINT event to a handler method, and set the background style to wx.BG_STYLE_PAINT.
  3. Give it a method to set the bitmap attribute, and have it call self.Refresh after the attribute is set.
  4. In the EVT_PAINT handler do these steps:
  5. Create a wx.AutoBufferedPaintDC
  6. Set the dc’s background brush (dc.SetBackground) to whatever you want the window to look like when there is no bitmap, or in the areas that the image does not cover.
  7. Call dc.Clear()
  8. if the bitmap attribute’s IsOk() method returns True, call dc.DrawBitmap to draw it.
···

On Wednesday, June 19, 2019 at 12:55:42 AM UTC-7, Mark wrote:

I want a widget that shows PNG images.

The widget will vary in size depending on the user sizing the window it is in but I don’t want the image it shows to be scaled. Iinstead I want the image to be shown at its actual size occupying the top-left if it is smaller than the widget or with scrollbars appearing if it is larger than the widget. The images the widget will be given will vary in size, so it needs to adapt whenever it is given a new image.

Does anyone have or can direct me to an example widget that has this kind of behaviour?

Robin

Thanks for that. I got it to work fine without scrollbars (once I’d set the BackgroundStyle), but my attempts to get scrollbars working have failed.

If you run the attached and resize the window to be smaller than the image on Gtk the scrollbars appear and don’t work. On Windows the scrollbars appear and do work, but the drawing gets all messed up. This is frustrating since I feel as though I’m really close!

imageview.py (3.59 KB)

···

On Thursday, 20 June 2019 02:43:27 UTC+1, Robin Dunn wrote:

On Wednesday, June 19, 2019 at 12:55:42 AM UTC-7, Mark wrote:

I want a widget that shows PNG images.

The widget will vary in size depending on the user sizing the window it is in but I don’t want the image it shows to be scaled. Iinstead I want the image to be shown at its actual size occupying the top-left if it is smaller than the widget or with scrollbars appearing if it is larger than the widget. The images the widget will be given will vary in size, so it needs to adapt whenever it is given a new image.

Does anyone have or can direct me to an example widget that has this kind of behaviour?

There are likely samples of this all over, but it’s fairly straight forward. I would approach it like this:

  1. Derive a new class from wx.Window
  2. In the init set a bitmap attribute to wx.NullBitmap, and bind the EVT_PAINT event to a handler method, and set the background style to wx.BG_STYLE_PAINT.
  3. Give it a method to set the bitmap attribute, and have it call self.Refresh after the attribute is set.
  4. In the EVT_PAINT handler do these steps:
  5. Create a wx.AutoBufferedPaintDC
  6. Set the dc’s background brush (dc.SetBackground) to whatever you want the window to look like when there is no bitmap, or in the areas that the image does not cover.
  7. Call dc.Clear()
  8. if the bitmap attribute’s IsOk() method returns True, call dc.DrawBitmap to draw it.

Robin

Oops, I somehow missed the “Scrolling” in the first message. There are a few ways to set things up to scroll, some more flexible but complex, some more simple. Your attempt is the former.

The main thing you are missing is adjusting the window to the scrolled offset when painting. You can do that with a call to self.PrepareDC(dc) in your on_paint method. To simplify even further, your entire update_scrollbars can be eliminated if you just set the virtual size to be the bitmap’s size whenever the bitmap changes, and scrolling can be enabled by calling SetScrollRate. The ScrolledCanvas will take care of everything else. See attached.

imageview.py (3.33 KB)

···

On Thursday, June 20, 2019 at 2:14:06 AM UTC-7, Mark wrote:

Thanks for that. I got it to work fine without scrollbars (once I’d set the BackgroundStyle), but my attempts to get scrollbars working have failed.

If you run the attached and resize the window to be smaller than the image on Gtk the scrollbars appear and don’t work. On Windows the scrollbars appear and do work, but the drawing gets all messed up. This is frustrating since I feel as though I’m really close!

Robin

On Thursday, 20 June 2019 02:43:27 UTC+1, Robin Dunn wrote:

On Wednesday, June 19, 2019 at 12:55:42 AM UTC-7, Mark wrote:

I want a widget that shows PNG images.

The widget will vary in size depending on the user sizing the window it is in but I don’t want the image it shows to be scaled. Iinstead I want the image to be shown at its actual size occupying the top-left if it is smaller than the widget or with scrollbars appearing if it is larger than the widget. The images the widget will be given will vary in size, so it needs to adapt whenever it is given a new image.

Does anyone have or can direct me to an example widget that has this kind of behaviour?

There are likely samples of this all over, but it’s fairly straight forward. I would approach it like this:

  1. Derive a new class from wx.Window
  2. In the init set a bitmap attribute to wx.NullBitmap, and bind the EVT_PAINT event to a handler method, and set the background style to wx.BG_STYLE_PAINT.
  3. Give it a method to set the bitmap attribute, and have it call self.Refresh after the attribute is set.
  4. In the EVT_PAINT handler do these steps:
  5. Create a wx.AutoBufferedPaintDC
  6. Set the dc’s background brush (dc.SetBackground) to whatever you want the window to look like when there is no bitmap, or in the areas that the image does not cover.
  7. Call dc.Clear()
  8. if the bitmap attribute’s IsOk() method returns True, call dc.DrawBitmap to draw it.

Robin

Thanks Robin, that works perfectly on both Gtk and Windows!

Out of curiosity, is it OK to replace if bitmap.IsOk(): with if bitmap:?

Also, is there any reason you wrote self.SetVirtualSize(bitmap.Size) rather than self.VirtualSize = bitmap.Size?

I’m tending to use property getters/setters wherever they are available but don’t really know what the recommended style is.

Mark wrote:

Out of curiosity, is it OK to replace `if bitmap.IsOk():` with `if bitmap:`?

No. "if self.bitmap:" is always going to succeed. The "if" statement in Python only checks for a few values of truthiness: the value None, the value False, or an empty sequence (string, list, tuple, set, dictionary) will evaluate false. Everything else, including a bitmap object bound to wxNullBitmap, will evaluate to true.

···

--
Tim Roberts, timr@probo.com
Providenza & Boekelheide, Inc.

Mark wrote:

Out of curiosity, is it OK to replace if bitmap.IsOk(): with if bitmap:?

No. “if self.bitmap:” is always going to succeed. The “if” statement
in Python only checks for a few values of truthiness: the value None,
the value False, or an empty sequence (string, list, tuple, set,
dictionary) will evaluate false. Everything else, including a bitmap
object bound to wxNullBitmap, will evaluate to true.

Actually, wxPython adds bool and nonzero methods to the wx.Bitmap class that return the value of IsOk(), so yes, it will work.

wx.NullBitmap.IsOk()

False

print(‘ok’ if wx.NullBitmap else ‘not ok’)

not ok

bool(wx.NullBitmap)

False

bmp = wx.Bitmap(10,10)

bmp.IsOk()

True

print(‘ok’ if bmp else ‘not ok’)

ok

bool(bmp)

True

···

On Thursday, June 20, 2019 at 12:59:14 PM UTC-7, Tim Roberts wrote:

Robin