Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[tkinter] widget size adjustment

1,076 views
Skip to first unread message

Pierre-Alain Dorange

unread,
Jun 19, 2016, 2:29:11 PM6/19/16
to
Hi,

I got a small interface handle with tkinter / Gridmanager.
I configure row and column to follow user window size adjustement, that'
fine. but i do not know how to adjust the main widget : a canvas
displaying a portion of a big image.
I bind a resize event that works, but do not know what to do from that.

Any clue or advice or tutorial ?
--
Pierre-Alain Dorange <http://microwar.sourceforge.net/>

Ce message est sous licence Creative Commons "by-nc-sa-2.0"
<http://creativecommons.org/licenses/by-nc-sa/2.0/fr/>

Rick Johnson

unread,
Jun 21, 2016, 12:48:15 PM6/21/16
to
On Sunday, June 19, 2016 at 1:29:11 PM UTC-5, Pierre-Alain Dorange wrote:
> I got a small interface handle with tkinter / Gridmanager.
> I configure row and column to follow user window size
> adjustement, that' fine. but i do not know how to adjust
> the main widget : a canvas displaying a portion of a big
> image. I bind a resize event that works, but do not know
> what to do from that.
>
> Any clue or advice or tutorial ?

I'm sorry, but your explanation is lacking, and could be
the reason you have not received help so far. I'll attempt
to guess what the problem is, and then you can refine the
question if none of these solutions is what you're looking
for.

(1) If you want a widget to fill *ALL* of the available space
within it's parent window, you'll need to set the
appropriate options for the "pack" or "grid" geometry
manager -- depending on which one you are using. Note: i
won't bother discussing the "place" manager. (See CODE1 and
CODE2 below)

Pro tip: If your top-window *ONLY* contains a single child
widget that fills the entire space, *ALWAYS* use pack!

(2) If the contents of your canvas exceed the viewable
portion of it's viewable area, then you'll need to add
scroll-bars and set the scrollregion option so that you can
view the "clipped portions". (See CODE3 below)

(3) Or perhaps you want the canvas content(s) to resize
dynamically as the canvas expands and contacts?

(4) Something else entirely...?

============================================================
CODE EXAMPLES
============================================================

## BEGIN: CODE1 ##
import Tkinter as tk
from Tkconstants import *
root = tk.Tk()
canvas = tk.Canvas(root, bg='red')
canvas.pack(fill=BOTH, expand=YES, padx=5, pady=5)
root.mainloop()
## END: CODE1 ##

## BEGIN: CODE2 ##
import Tkinter as tk
from Tkconstants import *
root = tk.Tk()
root.rowconfigure(1, weight=1)
root.columnconfigure(1, weight=1)
canvas = tk.Canvas(root, bg='red')
canvas.pack(fill=BOTH, expand=YES, padx=5, pady=5)
root.mainloop()
## END: CODE2 ##

## BEGIN: CODE3 ##
import Tkinter as tk
from Tkconstants import *
#
ROOT_MSG = """\
In the two windows below, observe the consequences of the canvas
"scrollregion" option. The left window has scollregion set to contain
the entire contents, whilst the right window has not configured the
scrollregion at all, and as a result, the scrollbars are useless.
"""
#
class MyCanvas(tk.Canvas):
def __init__(self, master, **kw):
self.frame = tk.Frame(master)
self.frame.rowconfigure(0, weight=1)
self.frame.columnconfigure(0, weight=1)
self.hbar = tk.Scrollbar(self.frame, orient=HORIZONTAL)
self.block = tk.Frame(self.frame, width=18, height=18)
self.block.grid(row=1, column=1)
self.vbar = tk.Scrollbar(self.frame, orient=VERTICAL)
tk.Canvas.__init__(self, self.frame, **kw)
tk.Canvas.grid(self, row=0, column=0, sticky=N+S+E+W)
self.hbar.configure(command=self.xview)
self.vbar.configure(command=self.yview)
self.config(yscrollcommand=self.vbar.set,
xscrollcommand=self.hbar.set)
self.hbar.grid(row=1, column=0, sticky=W+E)
self.vbar.grid(row=0, column=1, sticky=N+S)
#
def fill_canvas_with_junk(canvas):
sx = 5
for r_ in range(10):
canvas.create_rectangle(sx, 5, sx+100, 3000, fill='gray')
canvas.create_line(sx, 5, sx+100, 3000)
sx += 200
#
root = tk.Tk()
root.geometry('+5+5')
root.title('RootWindow (aka: BWFL)')
w = tk.Label(root, text=ROOT_MSG, fg='red')
w.pack(fill=BOTH, expand=YES)
#
top1 = tk.Toplevel(root)
top1.geometry('400x300+5-50')
top1.title('Example1 (clipped regions accessable)')
canvas1 = MyCanvas(top1, bg='white')
canvas1.frame.pack(fill=BOTH, expand=YES, padx=5, pady=5)
fill_canvas_with_junk(canvas1)
sx,sy,ex,ey = canvas1.bbox(ALL)
canvas1['scrollregion'] = (0,0,ex+5,ey+5)
#
top2 = tk.Toplevel(root)
top2.geometry('400x300-5-50')
top2.title('Example2 (clipped regions *NOT* accessable)')
canvas2 = MyCanvas(top2, bg='white')
canvas2.frame.pack(fill=BOTH, expand=YES, padx=5, pady=5)
fill_canvas_with_junk(canvas2)
#
root.mainloop()
## END: CODE3 ##

PS: Hopefully there's no bugs in here :-)

Pierre-Alain Dorange

unread,
Jun 21, 2016, 1:24:56 PM6/21/16
to
Rick Johnson <rantingri...@gmail.com> wrote:

> I'm sorry, but your explanation is lacking, and could be
> the reason you have not received help so far.

Yes you're probably right. For my excuse i'm french and if i read
english, i'm not fluent with it for writing...

> I'll attempt
> to guess what the problem is, and then you can refine the
> question if none of these solutions is what you're looking
> for.

Thanks for you effort (and the code)

> (3) Or perhaps you want the canvas content(s) to resize
> dynamically as the canvas expands and contacts?

That is more close to my needs.

A picture is often better than words, here is a scren capture of my apps
before and after resing the main window :
<https://dl.dropboxusercontent.com/u/722984/pmx-grid.pdf>

It was a map viewer similar to online javascript mapviewer but in a
local apps : with the main central part a canvas widget (with a PIL
image put build and put in).

I would resze the canvas according to the space available when the user
resize the window.
As you can see in the screen capture, when resize, the main map widget
is not resized, only centered.

For this apps i used widgets, assembled with the grid manager, so with
cells (row and line coordinates). I allrady set the grid so that only
the central cell (where the map is deisplayed) will be resize (all other
row and colum do not resize).

I'm a beginner with tkinter, and i could not found a way to get the cell
size (the cell where the canvas widget is put).
I think i can resize the canvas, but i can't find a way to get the
available space after resize.

Perhaps using the grid manager is not the godd idea for that ?

I except it was more understandable.

Christian Gollwitzer

unread,
Jun 21, 2016, 4:18:41 PM6/21/16
to
Am 21.06.16 um 19:24 schrieb Pierre-Alain Dorange:
> A picture is often better than words, here is a scren capture of my apps
> before and after resing the main window :
> <https://dl.dropboxusercontent.com/u/722984/pmx-grid.pdf>
>
> It was a map viewer similar to online javascript mapviewer but in a
> local apps : with the main central part a canvas widget (with a PIL
> image put build and put in).
>
> I would resze the canvas according to the space available when the user
> resize the window.
> As you can see in the screen capture, when resize, the main map widget
> is not resized, only centered.

Perhaps your assumption is wrong. Maybe the canvas itself *is* resized,
so the white space you see around the image is the background of the
canvas. To test this easily, set a strong color for the background:

blabla = tk.Canvas(..., bg='red')

You should see red space appear when you resize the window. If you see
white space, then the options are not set properly and the canvas is not
resized.

I assume that you want the following: Upon resizing the main window, the
map image should be stretched to fill the space, right? Then you'll have
to do that on your own. First, bind a Configure event to the canvas

canvas.bind('<Configure>', callback)

The callback functino now gets called when the canvas needs to be
redrawn. In that callback function, you get the new width and height
from the event object. Resize the image accordingly (using a PIL function).

Christian

Rick Johnson

unread,
Jun 21, 2016, 5:03:19 PM6/21/16
to
On Tuesday, June 21, 2016 at 12:24:56 PM UTC-5, Pierre-Alain Dorange wrote:
>
> > (3) Or perhaps you want the canvas content(s) to resize
> > dynamically as the canvas expands and contacts?
>
> That is more close to my needs.
>
> A picture is often better than words, here is a scren
> capture of my apps before and after resing the main window

If your intention is to create a grid of images, then you
need to understand two basic realities of transforming
computer images. First there is stretching, and then there
is resizing. Stretching will distort the image. Resizing
will maintain the image integrity (in most cases).

Have you considered these realities?

For instance, it's easy to create an expanding grid of
widgets with Tkinter, here is a simple example that uses a
grid of buttons:

## BEGIN CODE ##
import Tkinter as tk
from Tkconstants import *
#
root = tk.Tk()
root.title('Dynamic Grid')
#
for _ in range(3):
root.rowconfigure(_, weight=1)
root.columnconfigure(_, weight=1)
#
for row in range(3):
for col in range(3):
w = tk.Button(root, text='{0}-{1}'.format(row,col))
w.grid(row=row, column=col, sticky=N+S+W+E)
#
root.mainloop()

However, what do you expect your images to do when the
window is morphed: Stretch or Resize?

Pierre-Alain Dorange

unread,
Jun 22, 2016, 2:19:05 AM6/22/16
to
Rick Johnson <rantingri...@gmail.com> wrote:

> However, what do you expect your images to do when the
> window is morphed: Stretch or Resize?

Resize.

The map image is a (small) portion of a virtual infitine map of the
world (build throught downloading tiles images from tile map servers) :
user can drag the map to see a different portion.
So i except the map to be resized to show a bigger portion of the whole
map : no streching.

Pierre-Alain Dorange

unread,
Jun 22, 2016, 1:42:57 PM6/22/16
to
Christian Gollwitzer <auri...@gmx.de> wrote:

> Perhaps your assumption is wrong. Maybe the canvas itself *is* resized,
> so the white space you see around the image is the background of the
> canvas. To test this easily, set a strong color for the background:
>
> blabla = tk.Canvas(..., bg='red')
>
> You should see red space appear when you resize the window. If you see
> white space, then the options are not set properly and the canvas is not
> resized.

I've allready a background by not displayed, so the canvas is not
automatilly resized.

>
> I assume that you want the following: Upon resizing the main window, the
> map image should be stretched to fill the space, right? Then you'll have
> to do that on your own. First, bind a Configure event to the canvas
>
> canvas.bind('<Configure>', callback)

I do this, the callback is called when window was resized, but
unfortunnally i was not able to get the "available space (in pixels)".

event.width and event.height just return the actual canvas size... not
useful

canvas.winfo_width() return the actual canvas size... not useful
just note that it works when user resize down, the returned values
correspond to the visual portion of the canvas. But do not work when
enlarge...

canvas.winfo_reqwidth() just return the size when the canvas was
originally created... not useful...
>
> The callback functino now gets called when the canvas needs to be
> redrawn. In that callback function, you get the new width and height
> from the event object. Resize the image accordingly (using a PIL function).

I miss something, here event.width just return me the canvas size, not
resized...

The full code was on github :
<https://github.com/padorange/pmx-bigmap>

the tkinter code is in pmx.py
the canvas class was TMap
GUI was created in main_gui.__init__()
callback function was main_gui.resize()

Christian Gollwitzer

unread,
Jun 22, 2016, 4:18:25 PM6/22/16
to
Am 22.06.16 um 19:42 schrieb Pierre-Alain Dorange:
> Christian Gollwitzer <auri...@gmx.de> wrote:
>
>> Perhaps your assumption is wrong. Maybe the canvas itself *is* resized,
>> so the white space you see around the image is the background of the
>> canvas. To test this easily, set a strong color for the background:
>>
>> blabla = tk.Canvas(..., bg='red')
>>
>> You should see red space appear when you resize the window. If you see
>> white space, then the options are not set properly and the canvas is not
>> resized.
>
> I've allready a background by not displayed, so the canvas is not
> automatilly resized.

If you do not see the background, then indeed the canvas is not resized,
which means the gridding options are wrong. Looking at your code, I see
this:

self.map.grid(row=1,column=1,rowspan=4,columnspan=2,padx=2,pady=2)

Here, you do not specify any sticky options, which means it should
default to centering. Try adding NSEW sticky options.

>> I assume that you want the following: Upon resizing the main window, the
>> map image should be stretched to fill the space, right? Then you'll have
>> to do that on your own. First, bind a Configure event to the canvas
>>
>> canvas.bind('<Configure>', callback)
>
> I do this, the callback is called when window was resized, but
> unfortunnally i was not able to get the "available space (in pixels)".
>
> event.width and event.height just return the actual canvas size... not
> useful

This is a consequence of the missing grid options. On resize, the canvas
is only moved which also triggers the Configure event. If you set the
grid options correctly, you should get the new size. You will then need
to compute an updated image.

Christian


Pierre-Alain Dorange

unread,
Jun 22, 2016, 4:53:26 PM6/22/16
to
Christian Gollwitzer <auri...@gmx.de> wrote:

>
> If you do not see the background, then indeed the canvas is not resized,
> which means the gridding options are wrong. Looking at your code, I see
> this:
>
> self.map.grid(row=1,column=1,rowspan=4,columnspan=2,padx=2,pady=2)
>
> Here, you do not specify any sticky options, which means it should
> default to centering. Try adding NSEW sticky options.

That's magic !
It works just fine...

If i do understand NSEW sticky option make the widget autoadjust to the
available space. The default option is just CENTERED... I do not explore
this, i just thought NSEW was also CENTERED.

Many Thanks.

Christian Gollwitzer

unread,
Jun 22, 2016, 5:05:54 PM6/22/16
to
Am 22.06.16 um 22:53 schrieb Pierre-Alain Dorange:
> Christian Gollwitzer <auri...@gmx.de> wrote:
>> If you do not see the background, then indeed the canvas is not resized,
>> which means the gridding options are wrong. Looking at your code, I see
>> this:
>>
>> self.map.grid(row=1,column=1,rowspan=4,columnspan=2,padx=2,pady=2)
>>
>> Here, you do not specify any sticky options, which means it should
>> default to centering. Try adding NSEW sticky options.
>
> That's magic !
> It works just fine...

:)

> If i do understand NSEW sticky option make the widget autoadjust to the
> available space. The default option is just CENTERED... I do not explore
> this, i just thought NSEW was also CENTERED.

IMHO the sticky options in grid are actually very intuitive, once you
understand them. The widget gets "pinned" on each side that is mentioned
in the sticky set. If it is not pinned in one direction, then it is
centered. So, for instance sticky=N means pin it on top (North), but
neither south, east nor west. This means, the widget is aligned to the
top of the cell and centered in left-right direction, but does not
resize. Whereas, sticky=NS means pin it north and south, which is
stretching in vertical direction to fill the height, center in
left-right. sticky=NSEW pins it at all four sides which consequently
stretches it in both directions.

BTW, the Tkinter wrapper is a bit clumsy for this option. In the
original Tk, the sticky option is just a string. You can still pass that
and do

sticky='nsew'

instead of the clumsy

sticky=Tkinter.N+Tkinter.S+Tkinter.E+Tkinter.W

Christian

Zachary Ware

unread,
Jun 22, 2016, 5:19:11 PM6/22/16
to
On Wed, Jun 22, 2016 at 4:05 PM, Christian Gollwitzer <auri...@gmx.de> wrote:
> BTW, the Tkinter wrapper is a bit clumsy for this option. In the original
> Tk, the sticky option is just a string. You can still pass that and do
>
> sticky='nsew'
>
> instead of the clumsy
>
> sticky=Tkinter.N+Tkinter.S+Tkinter.E+Tkinter.W

There are constants in tkinter for this: NSEW, NS, NE, NW, SE, SW, and EW.

--
Zach

Christian Gollwitzer

unread,
Jun 22, 2016, 5:47:43 PM6/22/16
to
Am 22.06.16 um 23:18 schrieb Zachary Ware:
Yes, but this means that not only the combinations with three are
missing (NEW = stretch left-right and align at the top etc.), but also
you need to remember the exact order that the author of Tkinter has set.

I'm still thinking that passing a string is more in the spirit of Tk; do
sticky='snew' or sticky='news' if you wish, which actually works. Plus
you save the module prefix (unless you do "from tkinter import *"). I'm
not getting the rationale behind these constants. Why should I do
side=Tk.LEFT instead of side='left' ?

There are more points where Tkinter feels clunky compared to Tk in Tcl.
For instance, gridding widgets with row and columnspan options can be
done in Tcl in ASCII-art using -, x and ^ to indicate extensions:

grid .a -
grid .b .c
grid ^ x

This defines a 3x2 grid where .a extends to the right (columnspan=2), .b
extends downwards (rowspan =2), and the bottom right place is empty. The
same thing in Python requires to do explicit row/column-counting

a.grid(row=0, column=0, columnspan=2)
b.grid(row=1, column=0, rowspan=2)
c.grid(row=1, column=1)


This gets increasingly uglier with the number of widgets in a frame.
If grid were a standalone function, as it is in Tk, it would not be
difficult to provide something similar to the Tcl code. For whatever
reason it was decided that it should be a method of the widgets to be
placed and thus accepts only a single widget.

Christian
0 new messages