Tkinter question

R

Rotwang

Apologies in advance if this is a totally stupid question, I've tried
looking at the Tkinter documentation on the web but since I'm something
of an ignoramus when it comes to programming generally I didn't
understand what I was reading. Anyway...

I've written a module that allows me to manipulate sound data, and I've
been trying to add a method to my sound class that shows me what a
waveform looks like while I'm working on it in IDLE. After reading a bit
about Tkinter, and via some trial and error, I came up with something a
bit like this:

def draw(self, w, h):
out = Tkinter.Canvas(width = w, height = h)
# a load of out.create_line(...)'s go here
out.pack()
out.mainloop()

It works, but the problem is that I can't do anything else with IDLE
until I close the image window. Is there a way around this, so that I
can leave the image open while I continue to do other stuff?
 
J

James Mills

def draw(self, w, h):
       out = Tkinter.Canvas(width = w, height = h)
       # a load of out.create_line(...)'s go here
       out.pack()
       out.mainloop()

It works, but the problem is that I can't do anything else with IDLE until I
close the image window. Is there a way around this, so that I can leave the
image open while I continue to do other stuff?
From reading the documentation myself (pydoc)...

It would seem your only option is to make a thread
out of this (not my preferred way - I was hoping it was
possible to poll the Tk event system...).

cheers
James
 
E

eb303

Apologies in advance if this is a totally stupid question, I've tried
looking at the Tkinter documentation on the web but since I'm something
of an ignoramus when it comes to programming generally I didn't
understand what I was reading. Anyway...

I've written a module that allows me to manipulate sound data, and I've
been trying to add a method to my sound class that shows me what a
waveform looks like while I'm working on it in IDLE. After reading a bit
about Tkinter, and via some trial and error, I came up with something a
bit like this:

def draw(self, w, h):
        out = Tkinter.Canvas(width = w, height = h)
        # a load of out.create_line(...)'s go here
        out.pack()
        out.mainloop()

It works, but the problem is that I can't do anything else with IDLE
until I close the image window. Is there a way around this, so that I
can leave the image open while I continue to do other stuff?

Just run your program directly, either from a terminal or a DOS
console or by double-clicking on it in a file manager if the proper
file associations have been defined (they are by default on Windows).

There might be some IDLE trick to make it work, but I don't use IDLE
myself.

HTH
- Eric -
 
R

Rotwang

James said:
[...]

From reading the documentation myself (pydoc)...

It would seem your only option is to make a thread
out of this (not my preferred way - I was hoping it was
possible to poll the Tk event system...).

Thanks, I don't know anything about threading at the moment but I'll
look into it.

BTW, another problem: whenever I call a widget.quit() method, the widget
in question crashes. IDLE carries on working but the widget window stays
there, not responding, and if I tell my OS to kill it then IDLE
restarts. Is this a bug? I'm using Windows 7 and Python 2.6.4.
 
R

Rotwang

eb303 said:

Just run your program directly, either from a terminal or a DOS
console or by double-clicking on it in a file manager if the proper
file associations have been defined (they are by default on Windows).

Thanks for the suggestion, but I think in this case that would defeat
the point of the draw method (which is there so I can see what something
looks like in between using the interpreter interactively to mess about
with it).
 
E

eb303

James said:
[...]
From reading the documentation myself (pydoc)...
It would seem your only option is to make a thread
out of this (not my preferred way - I was hoping it was
possible to poll the Tk event system...).

Thanks, I don't know anything about threading at the moment but I'll
look into it.

From my experience, mixing Tkinter with threads is a bad idea. As most
GUI toolkits, it really doesn't like to be manipulated from different
threads, so you might end up getting weird problems or even crashes.

By the way, did you try to remove the line out.mainloop() from your
'draw' function? This is the line that blocks the IDLE GUI, since it
initiates a secondary event loop that will only exit when you do a
out.quit(), so that might be a solution.
BTW, another problem: whenever I call a widget.quit() method, the widget
in question crashes. IDLE carries on working but the widget window stays
there, not responding, and if I tell my OS to kill it then IDLE
restarts. Is this a bug? I'm using Windows 7 and Python 2.6.4.

The 'quit' method just exits the mainloop. It doesn't destroy the
widget. So if your application doesn't actually exit, the widget will
just stay there. If you want to destroy the it too, you have to call
explicitely widget.destroy().

HTH
- Eric -
 
R

Rotwang

eb303 said:

From my experience, mixing Tkinter with threads is a bad idea. As most
GUI toolkits, it really doesn't like to be manipulated from different
threads, so you might end up getting weird problems or even crashes.

By the way, did you try to remove the line out.mainloop() from your
'draw' function?

I didn't. How do I get Python to display the draw window, other than by
using mainloop()?

This is the line that blocks the IDLE GUI, since it
initiates a secondary event loop that will only exit when you do a
out.quit(), so that might be a solution.


The 'quit' method just exits the mainloop. It doesn't destroy the
widget. So if your application doesn't actually exit, the widget will
just stay there. If you want to destroy the it too, you have to call
explicitely widget.destroy().

That worked like a charm, thanks!

Here's another problem I've run into today: I've just added a bit of
code so that it's possible to resize the draw window and the contents
will be resized automatically. The method now looks something like this:

out = Tkinter.Tk()
slave = Tkinter.Canvas(out, width = wh[0], height = wh[1])
slave.grid()
# I put the canvas widget inside a tk widget instead of just
# using the former because I want keypresses to do things, and
# it doesn't seem to be possible to bind keyboard events to a
# canvas
# draw something
slave.pack()

def resize(b):
wh[:] = [b.width, b.height]
slave.config(width = wh[0], height = wh[1])
# resize the contents of slave

out.bind('<Configure>', resize)
out.mainloop()


The trouble is, when I call the method the window it spawns slowly grows
larger, until I move or resize it myself by grabbing one of the edges;
after this everything works as intended. If I add the line "print wh"
after "wh[:] = [b.width, b.height]", the output looks like this (the
default value of wh is [640,480]:

[644, 484]
[648, 488]
[648, 488]
[648, 488]
[652, 492]
[652, 492]
[652, 492]
[656, 496]
[656, 496]
[656, 496]
[660, 500]
etc.

My only guess as to why this is happening is that Tkinter is resizing
out to be 4 pixels wider and taller than slave, and the line
"slave.config(...)" consequently leads to resize being called again. But
this doesn't explain why it stops happening when I resize the window
intentionally, nor why the window apparently only gets larger every
third time resize is called. The problem goes away if I replace "wh[:] =
[b.width, b.height]" with

wh[:] = [b.width - 4, b.height - 4]

but this seems like a rather ad-hoc and ugly solution, and I'd rather
understand what's going on. Can anyone help?
 
E

eb303

eb303 said:

From my experience, mixing Tkinter with threads is a bad idea. As most
GUI toolkits, it really doesn't like to be manipulated from different
threads, so you might end up getting weird problems or even crashes.
By the way, did you try to remove the line out.mainloop() from your
'draw' function?

I didn't. How do I get Python to display the draw window, other than by
using mainloop()?

Well, mainloop doesn't actually display anything. It's just the event
loop for tk. So since you run your program within IDLE, there is
already one running. What does it do if you delete the mainloop()
line? Doesn't your window appear at all?
This is the line that blocks the IDLE GUI, since it
initiates a secondary event loop that will only exit when you do a
out.quit(), so that might be a solution.
The 'quit' method just exits the mainloop. It doesn't destroy the
widget. So if your application doesn't actually exit, the widget will
just stay there. If you want to destroy the it too, you have to call
explicitely widget.destroy().

That worked like a charm, thanks!

Here's another problem I've run into today: I've just added a bit of
code so that it's possible to resize the draw window and the contents
will be resized automatically. The method now looks something like this:

out = Tkinter.Tk()
slave = Tkinter.Canvas(out, width = wh[0], height = wh[1])
slave.grid()
        # I put the canvas widget inside a tk widget instead of just
        # using the former because I want keypresses to do things, and
        # it doesn't seem to be possible to bind keyboard events to a
        # canvas
# draw something
slave.pack()

(Hope this line is a mistake: gridding *and* packing slave will
probably result in tk thinking for ages how it should display it in
its parent…)
def resize(b):
        wh[:] = [b.width, b.height]
        slave.config(width = wh[0], height = wh[1])
        # resize the contents of slave

You don't need at all to resize the slave explicitely. You should do
the following:
- Tell 'out' that its first row and first column should resize
themselves when the window is resized by doing:
out.grid_rowconfigure(1, weight=1)
out.grid_columnconfigure(1, weight=1)
- Make sure slave is actually put in the cell in first row and column,
and that all its sides will stick to the cell borders:
slave.grid(row=1, column=1, sticky='nswe')

If you do that, the slave.config in the resize function shouldn't be
needed anymore.
out.bind('<Configure>', resize)

If using the grid options, better do a slave.bind(…) here, which will
call the binding when the canvas is resized, which is obvioulsy when
you want to update its contents.
out.mainloop()

The trouble is, when I call the method the window it spawns slowly grows
larger, until I move or resize it myself by grabbing one of the edges;
after this everything works as intended. If I add the line "print wh"
after "wh[:] = [b.width, b.height]", the output looks like this (the
default value of wh is [640,480]:

[644, 484]
[648, 488]
[648, 488]
[648, 488]
[652, 492]
[652, 492]
[652, 492]
[656, 496]
[656, 496]
[656, 496]
[660, 500]
etc.

My only guess as to why this is happening is that Tkinter is resizing
out to be 4 pixels wider and taller than slave, and the line
"slave.config(...)" consequently leads to resize being called again. But
this doesn't explain why it stops happening when I resize the window
intentionally, nor why the window apparently only gets larger every
third time resize is called. The problem goes away if I replace "wh[:] =
[b.width, b.height]" with

        wh[:] = [b.width - 4, b.height - 4]

but this seems like a rather ad-hoc and ugly solution, and I'd rather
understand what's going on. Can anyone help?

You get the size for the outer window and apply it to the canvas
inside it. Since the canvas has a border which is 2 pixels wide by
default, this resizes the window containing it to take this border
into account. So your binding is called again, and so on… But using
the options to grid described above, there should never be any need to
resize the canvas explicitely.

HTH
- Eric -
 
R

Rotwang

eb303 said:
[...]

I didn't. How do I get Python to display the draw window, other than by
using mainloop()?

Well, mainloop doesn't actually display anything. It's just the event
loop for tk. So since you run your program within IDLE, there is
already one running. What does it do if you delete the mainloop()
line? Doesn't your window appear at all?
No.

[...]

Here's another problem I've run into today: I've just added a bit of
code so that it's possible to resize the draw window and the contents
will be resized automatically. The method now looks something like this:

out = Tkinter.Tk()
slave = Tkinter.Canvas(out, width = wh[0], height = wh[1])
slave.grid()
# I put the canvas widget inside a tk widget instead of just
# using the former because I want keypresses to do things, and
# it doesn't seem to be possible to bind keyboard events to a
# canvas
# draw something
slave.pack()

(Hope this line is a mistake: gridding *and* packing slave will
probably result in tk thinking for ages how it should display it in
its parent…)

Not a mistake so much as a symptom of my inept, cargo-cult approach to
programming. Needless to say you're correct that it shouldn't be there,
though taking it out doesn't give a noticeable improvement in
performance (perhaps because the canvas is the only thing there so
there's no conflict).

def resize(b):
wh[:] = [b.width, b.height]
slave.config(width = wh[0], height = wh[1])
# resize the contents of slave

You don't need at all to resize the slave explicitely. You should do
the following:
- Tell 'out' that its first row and first column should resize
themselves when the window is resized by doing:
out.grid_rowconfigure(1, weight=1)
out.grid_columnconfigure(1, weight=1)
- Make sure slave is actually put in the cell in first row and column,
and that all its sides will stick to the cell borders:
slave.grid(row=1, column=1, sticky='nswe')

If you do that, the slave.config in the resize function shouldn't be
needed anymore.

Indeed! That works great.

If using the grid options, better do a slave.bind(…) here, which will
call the binding when the canvas is resized, which is obvioulsy when
you want to update its contents.

Right, though before you suggested making the sides of the canvas
sticky, resizing the window didn't reconfigure slave.

You get the size for the outer window and apply it to the canvas
inside it. Since the canvas has a border which is 2 pixels wide by
default, this resizes the window containing it to take this border
into account. So your binding is called again, and so on… But using
the options to grid described above, there should never be any need to
resize the canvas explicitely.

Thanks very much for your help.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,997
Messages
2,570,240
Members
46,830
Latest member
HeleneMull

Latest Threads

Top