Re: TkInter: Problem with propagation of resize events through geometry manager hierarchy?

2009-02-08 Thread James Stroud

Randy Smith wrote:

The cropping and scrolling works fine.  But when I try to add
responding to resize events, I get into trouble.  Specifically:
* When I naively change the size of the image shown to be borderwidth
  less than the size indicated in the configure event, the size of the
  image shown grows gradually but inexorably when I start the test
  app.  (Sorta scary, actually :-})
* When I fiddle a bit to figure out what the actual difference in size
  is between the Configure event and the image that can be displayed,
  I get a vibrating, jagged display of the image.

Investigation suggests that multiple configure events are hitting the
label in response to each user resize with different sizes.  I'm
guessing that when I resize the image in response to those different
events, that creates new resize events propagating through the window
manager hierarchy, which creates new configure events, which means my
handler changes the image size, which ... you get the idea.


I can't test your code because I don't have the test image and for some 
reason it does not recognize a tiff of my own. But, just glancing at 
your code, it looks like a quick-fix would be to set self.zoom to a 
sentinel at the end of refresh() and return 'break' at the top of the 
methods that use self.zoom if it is said sentinel value (e.g. if 
self.zoom == WHATEVER_SENTINEL: return 'break'). You may also want to 
return 'break' for event responders that should terminate the event 
chain. This is a general technique to stop a lot of unwanted event 
propagation.


James


--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com
--
http://mail.python.org/mailman/listinfo/python-list


Re: TkInter: Problem with propagation of resize events through geometry manager hierarchy?

2009-02-08 Thread curiouserrandy
On Feb 8, 6:27 am, James Stroud jstr...@mbi.ucla.edu wrote:

 I can't test your code because I don't have the test image and for some
 reason it does not recognize a tiff of my own. But, just glancing at
 your code, it looks like a quick-fix would be to set self.zoom to a
 sentinel at the end of refresh() and return 'break' at the top of the
 methods that use self.zoom if it is said sentinel value (e.g. if
 self.zoom == WHATEVER_SENTINEL: return 'break'). You may also want to
 return 'break' for event responders that should terminate the event
 chain. This is a general technique to stop a lot of unwanted event
 propagation.

Thanks!  I hadn't known about the return 'break' technique.  But
I don't follow your sentinel suggestion; how would that sentinel
ever get reset?  It seems as if the first time through the event
chain it'd be set to the sentinel, and the routines that pay
attention to it would never execute.  What am I missing?
I tried simply returning 'break' at the end of refresh() and
that made no change in behavior.

(Note that zoom should be constant at 1.0 for the life of
this program; I put it in because I'm planning to put in expansion/
contraction of images after I get resize  scrolling working
together.  Eliminating all multiplications by self.zoom confirms
this belief; no change in behavior).

One thing I've been looking for (including in the source :-J) is
a description of the precise process that the geometry manager
goes through in figuring out and then setting sizes for the
various widgets (on resize or, apparently, startup).  I suspect
with that + the return 'break' technique I could get this to
work.  But I haven't been having any luck finding that documentation.

If you'd like me to send you the test.tiff image, I'm happy to, but
it's nothing special; just a screen capture of a random google maps
satellite view that I use for testing.

 -- Randy



 James

 --
 James Stroud
 UCLA-DOE Institute for Genomics and Proteomics
 Box 951570
 Los Angeles, CA 90095

 http://www.jamesstroud.com

--
http://mail.python.org/mailman/listinfo/python-list


Re: TkInter: Problem with propagation of resize events through geometry manager hierarchy?

2009-02-08 Thread James Stroud

curiouserra...@gmail.com wrote:

Thanks!  I hadn't known about the return 'break' technique.  But
I don't follow your sentinel suggestion; how would that sentinel
ever get reset?


Presumably you would set it from some kind of input. Basically, if you 
don't need to zoom, you wouldn't bother scaling the image. It is the 
scaling step that is expanding your image.


You might want to set any borders to 0.

It seems as if the first time through the event

chain it'd be set to the sentinel, and the routines that pay
attention to it would never execute.  What am I missing?
I tried simply returning 'break' at the end of refresh() and
that made no change in behavior.


You will want to return 'break' at the end of callbacks, like 
display_tag_and_size()



One thing I've been looking for (including in the source :-J) is
a description of the precise process that the geometry manager
goes through in figuring out and then setting sizes for the
various widgets (on resize or, apparently, startup).  I suspect
with that + the return 'break' technique I could get this to
work.  But I haven't been having any luck finding that documentation.


I've never found that either. I usually tweak things by trial and error. 
Tkinter (Tk) has some bad habits. Most of the time it is very hard to 
get the precise dimensions of widgets by asking them. The problem is 
exacerbated by platform specific modifications like TkAqua. Ive found 
the best approach is to tweak the math so that the sizing comes out 
consistent and then test and patch for every platform you support. 
Sometimes, if you experiment enough, you can figure out by inference 
what Tk is actually doing under the hood.


The other, more sane option, is to attempt to make guis that don't rely 
on precise size information. Its less satisfying than the pixel level 
control one hopes for, but makes for a faster development process.


Also, you might want to look into Pmw. It has scrolled canvas and 
scrolled frame widgets that might be helpful to you. There is no need to 
reinvent the wheel except for your own enlightenment.


James


--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095

http://www.jamesstroud.com
--
http://mail.python.org/mailman/listinfo/python-list


TkInter: Problem with propagation of resize events through geometry manager hierarchy?

2009-02-07 Thread Randy Smith


Hi!  I'm looking for help with a Tkinter program's handling of resize.
I'm trying to do a fairly simple widget that shows a cropped part of a
larger image, and let's you navigate within the larger image through a
variety of methods.  The widget hierarchy is:

root
  ImageWidget (my class)
Label (contains the image)
Horizontal Scroll Bar
Vertical scroll bar

The cropping and scrolling works fine.  But when I try to add
responding to resize events, I get into trouble.  Specifically:
* When I naively change the size of the image shown to be borderwidth
  less than the size indicated in the configure event, the size of the
  image shown grows gradually but inexorably when I start the test
  app.  (Sorta scary, actually :-})
* When I fiddle a bit to figure out what the actual difference in size
  is between the Configure event and the image that can be displayed,
  I get a vibrating, jagged display of the image.

Investigation suggests that multiple configure events are hitting the
label in response to each user resize with different sizes.  I'm
guessing that when I resize the image in response to those different
events, that creates new resize events propagating through the window
manager hierarchy, which creates new configure events, which means my
handler changes the image size, which ... you get the idea.  However,
everything seems to work fine if I leave out the scroll bars and just
have a label in a frame inside the root window; the image resizes
fine.  If the scroll bars are in place but I don't have the image
resize bound to the configure event, I get two sets of resize events
propagaing through the system on startup; without, I just get one.

Event lists and code included below.  Any help would be appreciated.
Thanks!

  -- Randy Smith

-- Event list on startup with scroll bars:

receiving widget: width height
root :  220 220
root :  1 1
iwidget :  220 220
root :  220 220
vscroll :  16 204
root :  16 204
hscroll :  204 16
root :  204 16
ilabel :  204 204
root :  204 204
vscroll :  15 205
root :  15 205
hscroll :  205 15
root :  205 15
ilabel :  205 205
root :  205 205
root :  219 219
ilabel :  205 205
root :  205 205
hscroll :  205 15
root :  205 15
vscroll :  15 205
root :  15 205
iwidget :  219 219
root :  219 219
vscroll :  15 204
root :  15 204
hscroll :  204 15
root :  204 15
ilabel :  204 204
root :  204 204

-- Event list on startup without scroll bars

root :  204 204
root :  1 1
iwidget :  204 204
root :  204 204
ilabel :  204 204
root :  204 204

-- Code, without image resize.  If you want to see the vibration,
   uncomment the line
self.label.bind(Configure, self.reconfigure, +)
   To actually run it you'll need an image test.tiff in the current
   directory (any image of size  200x200 will do) and access to the
   python imaging library (PIL), but I hope the code is pretty clear
   (other than the math transforming between various coordinate
   systems, which I don't believe is relevant; focus on
   reconfigure(), refresh, and __init__).

#!/usr/bin/python

import traceback
from Tkinter import *
from PIL import Image
import ImageTk

debug = 4

def display_args(*args):
print Args: , args

def display_event(event):
print event.__dict__

def display_tag_and_size(tag, event):
print tag, : , event.width, event.height

class NotYetImplemented(Exception): pass

def mapnum(x, fromrange, torange):
assert fromrange[0] = x  fromrange[1], (fromrange[0], x,  
fromrange[1])

assert torange[0]  torange[1], (torange[0], torange[1])
## Need to force floating point
x *= 1.0
return (x - fromrange[0]) / (fromrange[1] - fromrange[0]) *  
(torange[1] - torange[0]) + torange[0]


class ImageWidget(Frame):
def __init__(self, parent, gfunc, image_size,
 starting_zoom=1,
 starting_ul=(0,0),
 starting_size = None):
Create an Image Widget which will display an image based  
on the
function passed.  That function will be called with the  
arguments

(zoom_factor, (xstart, xend), (ystart, yend)) and must return a
TkInter PhotoImage object of size (xend-xstart, yend-ystart).
IMAGE_SIZE describes the base size of the image being  
backed by

gfunc.
starting_* describes the starting window on the image.

## Default starting size to whole image
if not starting_size: starting_size = image_size

## Init parent
Frame.__init__(self, parent)
self.bind(Configure,
  lambda e, t=iwidget: display_tag_and_size(t, e))
## Base image parameters
self.generator_func = gfunc
self.isize = image_size

## Modifier of base image size for coords currently working in
self.zoom = starting_zoom

## Interval of augmented (zoomed) image currently shown
## Note that these must be integers; these map directly to  
pixels

self.xint =