Discussion:
Tk event *after* resizing a window
(too old to reply)
Johannes Schacht
2014-07-29 11:23:59 UTC
Permalink
Hi experts,

I have a canvas and I want some action to happen when the window is resized. When I bind code to <Configure> it will be called repeatedly during the resizing process. That does not help me as the function takes a lot of resources and time to finish.

I need a function when the user *finished*, i.e. when he/she releases the button. I tried to bind to <ButtonRelease> but that does not work as the frame around the application that is used to resize the application does not seem to be part of the Tk window (".").

Does anyone know how to tackle this?

Johannes
Christian Gollwitzer
2014-07-29 11:50:42 UTC
Permalink
Hi Johannes,
Post by Johannes Schacht
I have a canvas and I want some action to happen when the window is
resized. When I bind code to <Configure> it will be called repeatedly
during the resizing process. That does not help me as the function
takes a lot of resources and time to finish.
unfortunately this is impossible, since an application program does not
get an event when the resizing is finished. Usually, a workaround is
employed which defers the updating by a short time like 100ms and
cancels the update request when a new configure event comes in. Like this:

bind .foo <Configure> updateRequest

proc updateRequest {} {
variable updid
if {[info exists updid]} { after cancel $updid }
set updid [after 100 doUpdate]
}

proc doUpdate {} {
variable updid
unset updid
# now do your stuff
}

(OTOMH, untested code)

If your doUpdate is *REALLY* slow, like >1s, this might still be
unaceptably lagging. Then you need to introduce cancellation points
inside the doUpdate function (i.e. calling "update" to process events
and checking for updid set) and then returning from the doUpdate proc.

Christian
Johannes Schacht
2014-07-29 13:53:52 UTC
Permalink
Post by Christian Gollwitzer
Hi Johannes,
Post by Johannes Schacht
I have a canvas and I want some action to happen when the window is
resized. When I bind code to <Configure> it will be called repeatedly
during the resizing process. That does not help me as the function
takes a lot of resources and time to finish.
unfortunately this is impossible, since an application program does not
get an event when the resizing is finished. Usually, a workaround is
employed which defers the updating by a short time like 100ms and
bind .foo <Configure> updateRequest
proc updateRequest {} {
variable updid
if {[info exists updid]} { after cancel $updid }
set updid [after 100 doUpdate]
}
proc doUpdate {} {
variable updid
unset updid
# now do your stuff
}
(OTOMH, untested code)
If your doUpdate is *REALLY* slow, like >1s, this might still be
unaceptably lagging. Then you need to introduce cancellation points
inside the doUpdate function (i.e. calling "update" to process events
and checking for updid set) and then returning from the doUpdate proc.
Christian
Thank you Christian. Bad news but good to know.
When doUpdate is canceled I guess that will happen at some arbitrary point in the execution, correct? Therefore I will have to deal with inconsistent data.
Robert Heller
2014-07-29 14:24:39 UTC
Permalink
Post by Johannes Schacht
Post by Christian Gollwitzer
Hi Johannes,
Post by Johannes Schacht
I have a canvas and I want some action to happen when the window is
resized. When I bind code to <Configure> it will be called repeatedly
during the resizing process. That does not help me as the function
takes a lot of resources and time to finish.
unfortunately this is impossible, since an application program does not
get an event when the resizing is finished. Usually, a workaround is
employed which defers the updating by a short time like 100ms and
bind .foo <Configure> updateRequest
proc updateRequest {} {
variable updid
if {[info exists updid]} { after cancel $updid }
set updid [after 100 doUpdate]
}
proc doUpdate {} {
variable updid
unset updid
# now do your stuff
}
(OTOMH, untested code)
If your doUpdate is *REALLY* slow, like >1s, this might still be
unaceptably lagging. Then you need to introduce cancellation points
inside the doUpdate function (i.e. calling "update" to process events
and checking for updid set) and then returning from the doUpdate proc.
Christian
Thank you Christian. Bad news but good to know.
When doUpdate is canceled I guess that will happen at some arbitrary point in the execution, correct? Therefore I will have to deal with inconsistent data.
No, when the *event* is canceled, that just means that doUpdate won't be
called. Once doUpdate is called, no other events will be delivered. If
doUpdate does not finish quickly, the the incoming events will be queued. When
doUpdate finishes, they will be fired off, which will cause a flury of after
events, that will get promptly canceled and successor ones canceled.

So, if doUpdate does not finish quickly, doUpdate itself needs to call update
at suitable places and check to see if another configure event arrived and
deal with that (eg returning at that point and letting the succcessor call
takeover). It is really *best* if the configure event code be as small and
tight as possible. If you are really doing a lot of processing there, you
probably need to rethink that code.

What *exactly* is your <Configure> event code doing?
--
Robert Heller -- 978-544-6933
Deepwoods Software -- Custom Software Services
http://www.deepsoft.com/ -- Linux Administration Services
***@deepsoft.com -- Webhosting Services
Johannes Schacht
2014-07-29 15:03:51 UTC
Permalink
Post by Robert Heller
Post by Johannes Schacht
Post by Christian Gollwitzer
Hi Johannes,
Post by Johannes Schacht
I have a canvas and I want some action to happen when the window is
resized. When I bind code to <Configure> it will be called repeatedly
during the resizing process. That does not help me as the function
takes a lot of resources and time to finish.
unfortunately this is impossible, since an application program does not
get an event when the resizing is finished. Usually, a workaround is
employed which defers the updating by a short time like 100ms and
bind .foo <Configure> updateRequest
proc updateRequest {} {
variable updid
if {[info exists updid]} { after cancel $updid }
set updid [after 100 doUpdate]
}
proc doUpdate {} {
variable updid
unset updid
# now do your stuff
}
(OTOMH, untested code)
If your doUpdate is *REALLY* slow, like >1s, this might still be
unaceptably lagging. Then you need to introduce cancellation points
inside the doUpdate function (i.e. calling "update" to process events
and checking for updid set) and then returning from the doUpdate proc.
Christian
Thank you Christian. Bad news but good to know.
When doUpdate is canceled I guess that will happen at some arbitrary point in the execution, correct? Therefore I will have to deal with inconsistent data.
No, when the *event* is canceled, that just means that doUpdate won't be
called. Once doUpdate is called, no other events will be delivered. If
doUpdate does not finish quickly, the the incoming events will be queued. When
doUpdate finishes, they will be fired off, which will cause a flury of after
events, that will get promptly canceled and successor ones canceled.
So, if doUpdate does not finish quickly, doUpdate itself needs to call update
at suitable places and check to see if another configure event arrived and
deal with that (eg returning at that point and letting the succcessor call
takeover). It is really *best* if the configure event code be as small and
tight as possible. If you are really doing a lot of processing there, you
probably need to rethink that code.
What *exactly* is your <Configure> event code doing?
--
Robert Heller -- 978-544-6933
Deepwoods Software -- Custom Software Services
http://www.deepsoft.com/ -- Linux Administration Services
There is an image on the canvas and it should shrink or grow as the window is being resized. Resizing takes a couple of hundred milliseconds and eats some memory.
Christian Gollwitzer
2014-07-29 15:31:24 UTC
Permalink
Post by Johannes Schacht
There is an image on the canvas and it should shrink or grow as the
window is being resized. Resizing takes a couple of hundred
milliseconds and eats some memory.
I assume the resizing is done in C. If it takes less than a second
(according to your description), then just do it as I wrote. The update
code will not be interrupted (as explained by Robert), but just executed
only if you don't move the mouse for longer than 100ms - that feels
quite natural.

About the fear for "inconsistent data" - I hope you always resize the
original image into the current size. Memory consumption shouldn't be
much higher than for both images in total (original + resized),
otherwise you should consider another library for resizing. (I've also
once made one, but it uses C++. If this is acceptable, I can dig out the
code)

To get these things really fluid, the only way is OpenGL, for example
tkZinc can do it, or with some trickery tcl3D.

Christuan
Johannes Schacht
2014-07-29 15:42:25 UTC
Permalink
Post by Christian Gollwitzer
Post by Johannes Schacht
There is an image on the canvas and it should shrink or grow as the
window is being resized. Resizing takes a couple of hundred
milliseconds and eats some memory.
I assume the resizing is done in C. If it takes less than a second
(according to your description), then just do it as I wrote. The update
code will not be interrupted (as explained by Robert), but just executed
only if you don't move the mouse for longer than 100ms - that feels
quite natural.
About the fear for "inconsistent data" - I hope you always resize the
original image into the current size. Memory consumption shouldn't be
much higher than for both images in total (original + resized),
otherwise you should consider another library for resizing. (I've also
once made one, but it uses C++. If this is acceptable, I can dig out the
code)
To get these things really fluid, the only way is OpenGL, for example
tkZinc can do it, or with some trickery tcl3D.
Christuan
It works like a charm :-)
w***@gmail.com
2014-07-30 22:58:39 UTC
Permalink
Post by Johannes Schacht
There is an image on the canvas and it should shrink or grow as the window is being resized. Resizing takes a couple of hundred milliseconds and eats some memory.
If you want fast image resizing, and your images have a static width:height ratio, and you have pre-selected images that your app will use (as opposed to say, a web browser or photo editor which will encounter all sorts of new images) then you can do something similar to what I've done before, which I'll describe.

Create a code module that has a procedure that returns a Tk image for a given image name and width (only one size dimension is necessary because the size ratio of the image is static).

So something like: [get_tk_image "purple_horse.png" 42]
--> image1

You call this when your resize binding triggers, and you can then configure your widget or canvas item to use the returned Tk image.

Now here's where the trickery comes into play. Let's say your application is for playing chess, and we want the PNG images of the chess pieces to resize on the fly, and we know the maximum size of each piece is 105x105 and the minimum 20x20. Script some command-line tool like Image Magick to resize the original image to generate an image file for every possible size; ie., 20x20, 21x21, ... 105x105.

So now you have ~85 image files for the white queen chess piece, and you package these files with your application. The pseudocode for get_tk_image would look something like this:

-if a Tk image of this image & size exists, return that.
-Else create a new Tk image from the PNG file for this image & size, and return that.

So instead of actually resizing images on the fly, which is really slow, you're at worse just reading an image file from disk, but there's a good chance you're just reconfiguring your widget to use a new Tk image, so now you're probably dealing with hundreds of microseconds instead of milliseconds. It's really really fast and works seamlessly with a <Configure> binding.

I can dig up and post some code if you're interested.
Johannes Schacht
2014-08-05 20:31:48 UTC
Permalink
Post by w***@gmail.com
Post by Johannes Schacht
There is an image on the canvas and it should shrink or grow as the window is being resized. Resizing takes a couple of hundred milliseconds and eats some memory.
If you want fast image resizing, and your images have a static width:height ratio, and you have pre-selected images that your app will use (as opposed to say, a web browser or photo editor which will encounter all sorts of new images) then you can do something similar to what I've done before, which I'll describe.
Create a code module that has a procedure that returns a Tk image for a given image name and width (only one size dimension is necessary because the size ratio of the image is static).
....
I in fact implemented such a caching mechanism. Scaled images are kept for reuse once they were created. What I would have liked to do is to pre-create these images in a parallel thread. I couldn't do it because I would not know how to make the 'main' thread aware of the Tk images that were created in a thread. The images are TCL commands unknown by the current interpreter.
Rich
2014-08-06 01:38:14 UTC
Permalink
Post by Johannes Schacht
Post by w***@gmail.com
Post by Johannes Schacht
There is an image on the canvas and it should shrink or grow as
the window is being resized. Resizing takes a couple of hundred
milliseconds and eats some memory.
Create a code module that has a procedure that returns a Tk image
for a given image name and width (only one size dimension is
necessary because the size ratio of the image is static).
....
I in fact implemented such a caching mechanism. Scaled images are
kept for reuse once they were created. What I would have liked to do
is to pre-create these images in a parallel thread. I couldn't do it
because I would not know how to make the 'main' thread aware of the
Tk images that were created in a thread. The images are TCL commands
unknown by the current interpreter.
To create in a parallel thread you would have to do one of two things
to get them into the current interpreter.

1) pass them through the filesystem (imagename write file - imagename
read file)

2) pass them via the -data option via either message passing or via tsv
shared variables.

Both methods incur overhead (#1 in the save/load to the filesystem, #2
in encoding/decoding the string rep. of the image bitmap).
Donal K. Fellows
2014-08-06 10:00:17 UTC
Permalink
Post by Rich
To create in a parallel thread you would have to do one of two things
to get them into the current interpreter.
1) pass them through the filesystem (imagename write file - imagename
read file)
2) pass them via the -data option via either message passing or via tsv
shared variables.
Both methods incur overhead (#1 in the save/load to the filesystem, #2
in encoding/decoding the string rep. of the image bitmap).
A third method would be to pass the buffer over (and lock the
originating thread out of it until the return message is sent back)
using your own events rather than those defined by the thread package.
That would be quite a bit of work, especially as you'd have to be
careful with the memory management (watch out for thread-local storage
management) but would quite possibly be the fastest technique of all.

Donal.
--
Donal Fellows — Tcl user, Tcl maintainer, TIP editor.
Continue reading on narkive:
Loading...