Re: TkInter: Problem with propagation of resize events through geometry manager hierarchy?
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?
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?
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?
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 =