My Tkinter Threading nightmare

H

half.italian

Hi all,

I don't really understand how to properly use threading in my programs,
however I have managed to get by so far using them improperly. Once
again I have come up to what I think is something that won't work
because of the way the program is set up.

I have a long running process running underneath a gui in a different
thread, and a progress bar that updates the status of the operation.
What I would like to do is use something like :

self.progressWindow.protocol('WM_DELETE_WINDOW', self.callback)

and setup the callback to kill the operation and then the window when
the user clicks on the X of the progress window. I've implemented this
in my code, but clicking on the X does nothing until I kill the main
thread (i think), and then my callback is run. I've also had problems
in the past of the gui not refreshing properly, which leads me to
believe that both of these are thread related. Any help would be
immensely appreciated.

~Sean DiZazzo

~~~~~~~~~~~~~~~~~~~

import sys, os, time

libdir = "/usr/local/sw/lib"
sys.path.append(libdir)

import ProgressBarView, Folder, P

from Tkinter import *
from tkFileDialog import askopenfilenames
from tkMessageBox import showerror, showinfo
import pexpect, ssh_session

import P
import Pmw

rootWin = Tk()

class Window(Frame):
label = None
initialdir = "/"

dest = "/tmp"
folder = None
logpath = "/tmp/Send.log"
copyindex = 0
files = []
progressWindow = None
session = None
password = P.P()
ssh = ssh_session.ssh_session("root", "XX.XX.XX.XX",
password.Decrypt(password.sean))

timeout = 30

def __init__(self, parent=None):
Frame.__init__(self, parent)
rootWin.title("Send to Blah")
rootWin.resizable(width=0,height=0)
self.pack()
self.progress_and_log()
f = Frame(self)
openfunc = (lambda self=self, name=None: self.openfolder())
Button(f, text="Choose", command=openfunc).pack(side="left")
self.label = Label(f, text=' <--- Choose files', bg="grey", width=40,
justify='left', anchor='w')
self.label.pack(side="left", anchor='w', padx=5)
f.pack(padx=10, pady=10, side='left', fill='both', anchor='w')
submitfunc = (lambda self=self, name=None: self.submit())
b = Button(self, text="Send", command=submitfunc)
b.pack(side="right", padx=5)


def openfolder(self):
self.files = []
self.files_tuple = askopenfilenames(parent=rootWin, initialdir="/")

for file in self.files_tuple:
self.files.append(file)

self.files.sort()

label = "Click button to send files -->"
self.st.configure(text_state = 'normal')
self.st.clear()
self.st.insert('end', "Files to be copied:\n")
self.st.insert('end', "~~~~~~~~~~~~~~~~~~~\n")
for file in self.files:
self.st.insert('end', os.path.split(file)[-1] + "\n")
self.st.insert('end', " \n \n")
self.st.configure(text_state = 'disabled')
self.label.config(text='Click to send files ---> ', justify='right',
anchor='e')

def callback(self):
"""Need to get the pid here and kill the running process"""

def submit(self):
files = self.files
dest = Folder.Folder(self.dest)

for file in files:
if self.ssh.exists(os.path.join(dest.path,
os.path.split(file)[-1])):
showerror("error", "The file '%s' already exists" %
os.path.join(dest.path, os.path.split(file)[-1]))
return 0

import threading
geometry = None

for file in files:
self.progressWindow = Toplevel(rootWin)
self.progressWindow.protocol('WM_DELETE_WINDOW', self.callback)
self.progressWindow.geometry(geometry)
self.progressWindow.title("Progress")
self.progressWindow.resizable(width=0,height=0)

self.proglabel = Label(self.progressWindow, text='Copying...',
anchor=NW, justify=LEFT, width=30)
self.proglabel.pack(fill=X, expand=1)
self.progressBar =
ProgressBarView.ProgressBarView(self.progressWindow)
self.progressBar.pack(fill=X)

threading.Thread(target=self.threadedCopy,args=(file, dest)).start()

#grab the location from the first window
if geometry is None:
self.progressBar.updateProgress(0)
geometry = self.progressWindow.geometry()

self.incrementProgress(self.progressWindow, file, dest)

self.label.config(text=" <--- Choose files", justify='left',
anchor='w')
self.files=[]

def progress_and_log(self):
self.st = Pmw.ScrolledText(rootWin, borderframe = 1,
usehullsize = 1,
hull_width = 400,
hull_height = 150,
text_wrap='none',
#text_font = fixedFont,
text_padx = 4,
text_pady = 4,
)
self.st.configure(text_state = 'disabled')

self.st.pack(padx = 5, pady = 5, fill = 'both', expand = 1)


def threadedCopy(self, file, dest):
self.ssh.scp(file, os.path.join(dest.path, os.path.split(file)[1]))

def appendToLog(self, src, dest):
dest_size = self.ssh.size(dest.path)
try:
dest_size = float(dest_size)
except:
dest_size = 0

if src.size == dest_size:
status = "SUCCESS"
else:
status = "FAILED"
entry = "%s - %s %s, %s\n" % (time.ctime(time.time()), status,
src.name, src.readableSize)

f = open(self.logpath, 'a')
f.write(entry)
f.close()

self.st.configure(text_state = 'normal')
self.st.insert('end', entry)
self.st.configure(text_state = 'disabled')


def incrementProgress(self, window, file, dest):
import File
src = File.File(file)
dest = File.File(os.path.join(dest.path, os.path.split(file)[-1]))
start = time.time()


p = 0
last_dest = 0
same_count = 0

while p < 100:
d_size = self.ssh.size(dest.path)
try:
float(d_size)
except:
"Couldn't turn %s into a float" % d_size
continue

if last_dest == d_size and same_count == 40:
showerror("error", "Error copying %s\nRemoving the partially
transferred file and continuing..." % dest.path)
self.ssh.ssh("sudo rm %s" % dest.path)

if d_size == None:
d_size = 1000

self.proglabel.config(text=src.name)

p = (float(d_size) / float(src.size)) * 100.0

d_size = self.ssh.size(dest.path)
if last_dest == d_size:
same_count = same_count +1

print "P:", p
self.progressBar.updateProgress(p)
self.update_idletasks()
time.sleep(0.25)
last_dest = d_size

self.appendToLog(src, dest)
window.destroy()
return


if __name__ == "__main__":
Window().mainloop()
 
P

Paul Rubin

and setup the callback to kill the operation and then the window when
the user clicks on the X of the progress window. I've implemented this
in my code, but clicking on the X does nothing until I kill the main
thread (i think), and then my callback is run. I've also had problems
in the past of the gui not refreshing properly, which leads me to
believe that both of these are thread related. Any help would be
immensely appreciated.

That was more code than I was willing to sit and read, but basically
the tkinter thread blocks unless there's tkinter events. The usual
trick for what you're trying to do is to set up a tk.after event to
run every 20 msec or so. That can check for commands on a queue to
launch operations, kill windows, or whatever. In your Window class
you'd have something like (this is pseudocode, I don't remember the
exact params and args off the top of my head so you'll have to check
them):

def __init__(self, ...):
...
self.after(20, self.idle)

def idle(self):
# read and execute any commands waiting on the queue
while True:
try:
func, args, kw = self.cmd_queue.get(block=False)
except QueueEmpty:
return
func (*args, **kw)

cmd_queue is a queue that you set up ahead of time, and you use it to
send commands into your gui thread from other threads, such as window
kill. Don't call gui functions directly as tkinter is not
thread-safe.
 
P

Paul Rubin

Paul Rubin said:
def idle(self):
# read and execute any commands waiting on the queue
while True:
try:
func, args, kw = self.cmd_queue.get(block=False)
except QueueEmpty:
return
func (*args, **kw)

Whoops, I forgot, you have to set up the after event again at the end
of this:

def idle(self):
# read and execute any commands waiting on the queue
while True:
try:
func, args, kw = self.cmd_queue.get(block=False)
except QueueEmpty:
return
func (*args, **kw)
self.after(20, self.idle)
 
H

half.italian

of this:

def idle(self):
# read and execute any commands waiting on the queue
while True:
try:
func, args, kw = self.cmd_queue.get(block=False)
except QueueEmpty:
return
func (*args, **kw)
self.after(20, self.idle)

Thanks Paul. That has made more sense than all of my scrounging
combined, but I still need to mess around with the idea and code some.
How can I put something like the window delete into the command queue?
Isn't window.protocol() just binding the close window button to a
function?

I'll look deeper and experiment and try to make sense of it once again
tomorrow. :)

~Sean
 
P

Paul Rubin

Thanks Paul. That has made more sense than all of my scrounging
combined, but I still need to mess around with the idea and code some.
How can I put something like the window delete into the command queue?
Isn't window.protocol() just binding the close window button to a
function?

self.progressWindow.protocol('WM_DELETE_WINDOW', self.callback)

becomes

gui.cmd_queue.put(self.progressWindow.protocol,
('WM_DELETE_WINDOW', self.callback))

where gui is the Window object containing your gui.

I think there are some recipes like this in ASPN, that give more
detail about how to do this stuff.
 
A

Aahz

self.progressWindow.protocol('WM_DELETE_WINDOW', self.callback)

becomes

gui.cmd_queue.put(self.progressWindow.protocol,
('WM_DELETE_WINDOW', self.callback))

where gui is the Window object containing your gui.

I think there are some recipes like this in ASPN, that give more
detail about how to do this stuff.

You can find a simple example at http://pythoncraft.com/ in the threads
tutorial.
 

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

Forum statistics

Threads
473,768
Messages
2,569,575
Members
45,054
Latest member
LucyCarper

Latest Threads

Top