PyGTK + threading; windows unexpectedly freezes - python

I'm trying to manage files containing "other files" (lots of small files stored in a few large files). The "engine" works quite good, but I have some problems with GUI. Some functions are threaded because of processing time, for example deleting or adding files. Here's part of the code:
import gtk, os, data, time, threading, subprocess
import Image, re
gtk.gdk.threads_init()
#some unimportant code - creating window with some buttons and IconView item (to show elements in currently opened directory)
def _del_thread(self): #deletes items selected in gtk.IconView
dl, fl = self.items #self.items stores selected dirs and files
for d in dl:
self.changestatus('Deleting: '+data.dirs[d][0]+'...') #prints the text at StatusBar (data.dirs[d][0] is a filename)
data.RemoveDir(d) #recursively removes all content in directory d
for f in fl:
self.changestatus('Usuwanie pliku: '+data.files[f][0]+'...')
data.PopFile(f) #removes single file
self.changestatus('') #clears the StatusBar
self.refresh() #some elements are deleted - refresh IconView
def _add_thread(self, fl): #adds files listed in fl
for f in fl:
self.changestatus('Adding: '+f[-50:]+'...')
#some unimportant code (recognizing file's extension and generating a thumbnail
data.PutFile(f, thumb, self.dir) #adds the file with generated thumbnail to currently opened directory (self.dir)
self.changestatus('') #clears the StatusBar
self.refresh() #some elements are added - refresh IconView
def Delete(self, widget): #triggered by clicking a context-menu item
md = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, \
gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL, \
"Do you want to remove selected items?")
res = md.run()
md.destroy()
if res == gtk.RESPONSE_OK:
t = threading.Thread(target=self._del_thread) #start new thread
t.start()
def Add(self, widget): #triggered by clicking a context-menu item
chooser = gtk.FileChooserDialog(title='Select files to add...',action=gtk.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
chooser.set_select_multiple(True)
response = chooser.run()
if response == gtk.RESPONSE_OK:
#star new thread with selected files' list as an argument
t = threading.Thread(target=self._add_thread, args=(chooser.get_filenames(),))
t.start()
chooser.destroy()
There's also third function run in a thread, but it's unimportant. It is fast (~1 sec), always executes fine and never freezes the window. The other two ones (shown above) sometimes executes well, sometimes not. For example, I select some files in FileChooserDialog, the adding thread starts, StatusBar showing consecutively the files are being added now, and at the end the window freezes. After that the window looks like that (sorry for the polish description):
Why does thread sometimes process without problems, and sometimes doesn't?
Best regards,
mopsiok

I think I found the problem. According to http://faq.pygtk.org/index.py?file=faq20.006.htp&req=show , I had to add gtk.threads_enter() before every gtk function in thread, and gtk.threads_close() after it. Now it seems to work fine.

Related

Tcl_AsyncDelete: async handler deleted by the wrong thread, but I didn't delete from wrong thread?

My python program allows users to make a maze by drag and drop interference. It even searches for the correct path in the maze. However 720 variables need to be defined and binded for the grid where the maze will be built, for this purpose I used a loop which takes some time to work, henceforth I made a loading screen. When the program loads fully, the loading screen closes and it's thread dies but when I close the main window, I get the error(not when the loading screen closes but when I close the main window) Tcl_AsyncDelete: async handler deleted by the wrong thread. Here's a simplified version of my code to show what I'm trying to do:
from tkinter import *
from threading import Thread
loaded = False
def load():
load = Tk()
Label(load, text = "Loading...").pack()
def check():
if loaded:
load.destroy()
else:
load.after(1, check)
load.mainloop()
t = Thread(target = load)
t.start()
root = Tk()
#defining the 720 variables
for x in range(0, 720):
print(x)
exec("var" + str(x) + " = " + str(x))
loaded = True
root.mainloop()
I did read a few other questions on stack overflow, and I found out this happens when you delete the window from another thread(not the main thread), however I am deleting the loading screen from the thread in which it is running itself, not main thread. Any help?
This error specifically is caused by there being multiple instances of Tk in one app. There should always be only one instance of Tk in a tkinter app. If you want to create secondary windows, use Toplevel for them instead of Tk.
But you'll notice that if you do replace load = Tk with load = Toplevel, load doesn't show at all. When using threads in tkinter applications, it's a good idea to keep all of the tkinter stuff in one thread. If you split any tkinter widgets or operations into separate threads, it can cause problems. You should put the for loop inside load(), and put everything that's currently inside load() where the for loop was. This prevents any tkinter function calls or widget creation from happening in two different threads.
Third, I strongly recommend that you don't define variables in a loop like that. It makes it troublesome when trying to access the variables after creating them, and it's slow. For so many numbers, you can create a list and just append the numbers to the list. In my example, the list is called numbers.
Fourth, you will need to use global in your load function, so that the loading variable can be used properly in the thread. You also have two variables named load: the function and the window. You should rename one of them so that one doesn't overshadow the other. In my example, I rename load the window to load_window.
To make the Toplevel show on top, you can set load_window.wm_attributes("-topmost", True).
Here is what the code should look like:
from tkinter import *
# import time # Uncomment this to show the loading window for a little longer
from threading import Thread
loaded = False
numbers = []
def load():
global loaded, numbers
# Add the numbers to the list
numbers = [x for x in range(0, 720)]
# time.sleep(1) # Uncomment this to show the loading window for a little bit longer
loaded = True
root = Tk()
load_window = Toplevel(root)
Label(load_window, text = "Loading...").pack()
t = Thread(target = load)
t.start()
def check():
global loaded, load_window
if loaded:
load_window.destroy()
else:
load_window.after(1, check)
check()
load_window.wm_attributes("-topmost", True)
load_window.mainloop()
root.mainloop()
Note that when you run the code, you get a _tkinter.TclError:
Traceback (most recent call last):
File "/home/user/test.py", line 29, in <module>
load_window.wm_attributes("-topmost", True)
File "/usr/lib/python3.8/tkinter/__init__.py", line 1976, in wm_attributes
return self.tk.call(args)
_tkinter.TclError: bad window path name ".!toplevel"
This is because load() runs so fast that it destroys loaded_window before -topmost is set. As you probably know, calling functions on a destroyed widget raises errors. So this whole loading window would only really be needed with higher numbers. Even when I use range(0, 900000) instead of range(0, 720), the code runs, but it's still so fast that you only see load_window for a split second.
If you still want to see the loading window, you can uncomment the import time and time.sleep(... lines, and it will cause load_window to wait a second before closing.

How do I layout, and close this dialogbox in PyQt?

I'm writing a Flash Card application using Python and PyQt and I have a problem with dialogboxes. First, here's the section of my code which concerns the aforementioned dialogbox:
def openFile(self):
dialog = QDialog()
dialog.setGeometry(200, 100, 50, 100)
list = QListWidget(dialog)
for file in files:
list.addItem(file.replace('.flashcard', ''))
list.itemClicked.connect(self.listClicked)
dialog.exec_()
def listClicked(self, item):
file_to_read = item.text() + '.flashcard'
self.flashcard_list = FlashCard.read_card(file_to_read)
self.front_text.clear()
self.back_text.clear()
self.front_text.insertPlainText(self.flashcard_list[0].front)
self.back_text.insertPlainText(self.flashcard_list[0].back)
Although I'm using PyCharm, I can't actually debug the code since deubgging these sorts of applications is beyond my skill level. That's why I don't know why when I add codes like dialog.setLayout(layout) to the script, it won't run and it doesn't produce any errors. Also, I would like to know how to close this dialogbox since if I change the dialog to self.dialog to carry it over to another function, the code won't run, again, without producing any errors.
Thanks for your help.

pyside widget.show not blocking

I wrote a little application, that saves small videos it generates. The code pasted should do the following:
show dialog to the user to choose a directory
if directory is not empty show widget that has a circle running around so there's a feedback that something is going on
then generate the movies (which takes some time and most if it has handled with multiprocessing pool)
(not included in snippet, but the code would go on to do more background stuff, so the loading widget would stay on for a while
Code:
def saveMovie(self):
self.pause()
if not os.path.exists("outputs"):
os.makedirs("outputs")
dir = QtGui.QFileDialog.getExistingDirectory(self,dir="outputs",options=QtGui.QFileDialog.ShowDirsOnly)
if not dir == '':
self.loading = True
self.loadingWidget.show()
name = os.path.basename(dir) + "_"
settings = sm.fetch_settings()
swarms,l,l1,l2 = self.generate_movies()
Now instead of this, what happens is that the dialog disappears and the loading widget is shown only after the program has existed from self.generate_movies(). What am I missing?

Python GUI programming using drag and drop, also incorporating stdout redirect

I'm new to programming & new to python. I've just developed my first script, it prosesses file, but at the moment only from the commandline.
This is just a hobby to me so my job does not depend on it :-)
I've spent a few days now trying to get my head around python gui development & have come to the conclusion that I must be stupid.
I've looked at wxpython & Tkinter & do not understand either, although Tkinter seems to be the easier out of the two. I've even looked at wysiwyg tools like Boa Contrictor & wxglade. I do not even understand how to use those. I would prefer to just stick with my editor & code manually anyway.
My problem is this:
I would like to create a desktop window with either 1 or two objects, depending on what works best. If just one object then a text box of some sort, if 2 objects then a text box & an image.
I want to be able to drag file from a file manager & drop them on my script window, this is just to pass the filenames to my script.
I than want to redirect stdout to an object within my desktop window so that all script output appears within the desktop window.
I'm not sure if one object can do both things or not. If it can than just a text box would suffice, else drop files onto image & have redirected output going to the text box.
I have found drag & drop examples on the web but nothing which incorporates stdout redirect, & I've not been able to successfully modify any of the examples that I've come across.
If somee kind sole has the time to demonstrate how to achieve what I want & explain how its works I would greatfully appreciate it!
----EDIT ----
I've been playing around with 2 examples & have managed to hash the 2 together in order to get what I wanted working. Code is below. It's not cleaned up yet ( old comments etc... ), but it works.
#!/usr/bin/python
# The next two lines are not necessary if you installed TkDnd
# in a proper place.
import os
from Tkinter import *
os.environ['TKDND_LIBRARY'] = '/home/clinton/Python/tkdnd2.6/'
import Tkinter
from untested_tkdnd_wrapper import TkDND
class Redir(object):
# This is what we're using for the redirect, it needs a text box
def __init__(self, textbox):
self.textbox = textbox
self.textbox.config(state=NORMAL)
self.fileno = sys.stdout.fileno
def write(self, message):
# When you set this up as redirect it needs a write method as the
# stdin/out will be looking to write to somewhere!
self.textbox.insert(END, str(message))
root = Tkinter.Tk()
dnd = TkDND(root)
textbox = Tkinter.Text()
textbox.pack()
def handle(event):
event.widget.insert(END, event.data)
content = textbox.get("0.0",Tkinter.END)
filename = content.split()
dnd.bindtarget(textbox, handle, 'text/uri-list')
#Set up the redirect
stdre = Redir(textbox)
# Redirect stdout, stdout is where the standard messages are ouput
sys.stdout = stdre
# Redirect stderr, stderr is where the errors are printed too!
sys.stderr = stdre
# Print hello so we can see the redirect is working!
print "hello"
# Start the application mainloop
root.mainloop()
Examples are: python drag and drop explorer files to tkinter entry widget
And also the example provided kindly by Noelkd.
In order for this code to work you must create the wrapper from first example. Also currently code just displays dragged file in window, however variable is in place to be passed onto the script which runs behind the gui interface.
If you want to use Tkinter:
from Tkinter import *
import tkFileDialog
class Redir(object):
# This is what we're using for the redirect, it needs a text box
def __init__(self, textbox):
self.textbox = textbox
self.textbox.config(state=NORMAL)
self.fileno = sys.stdout.fileno
def write(self, message):
# When you set this up as redirect it needs a write method as the
# stdin/out will be looking to write to somewhere!
self.textbox.insert(END, str(message))
def askopenfilename():
""" Prints the selected files name """
# get filename, this is the bit that opens up the dialog box this will
# return a string of the file name you have clicked on.
filename = tkFileDialog.askopenfilename()
if filename:
# Will print the file name to the text box
print filename
if __name__ == '__main__':
# Make the root window
root = Tk()
# Make a button to get the file name
# The method the button executes is the askopenfilename from above
# You don't use askopenfilename() because you only want to bind the button
# to the function, then the button calls the function.
button = Button(root, text='GetFileName', command=askopenfilename)
# this puts the button at the top in the middle
button.grid(row=1, column=1)
# Make a scroll bar so we can follow the text if it goes off a single box
scrollbar = Scrollbar(root, orient=VERTICAL)
# This puts the scrollbar on the right handside
scrollbar.grid(row=2, column=3, sticky=N+S+E)
# Make a text box to hold the text
textbox = Text(root,font=("Helvetica",8),state=DISABLED, yscrollcommand=scrollbar.set, wrap=WORD)
# This puts the text box on the left hand side
textbox.grid(row=2, column=0, columnspan=3, sticky=N+S+W+E)
# Configure the scroll bar to stroll with the text box!
scrollbar.config(command=textbox.yview)
#Set up the redirect
stdre = Redir(textbox)
# Redirect stdout, stdout is where the standard messages are ouput
sys.stdout = stdre
# Redirect stderr, stderr is where the errors are printed too!
sys.stderr = stdre
# Print hello so we can see the redirect is working!
print "hello"
# Start the application mainloop
root.mainloop()
What this does is creates a window with a button and a text box, with stdout redirect.
Currently in Tkinter you can't drag and drop files on to the open tk window(you can if you use tkdnd), so I have included a different way of getting the path of a file.
The way I have included to select a file is the askopenfilename dialog from tkFileDialog, this opens up a file browser and the path file selected is returned as a string.
If you have any questions or this doesn't quite do what your looking for please leave a comment!
Have a look at GTK. It is a really powerful library. Not the simplest, that's a fact, but once you get to understand how things work, it becomes much easier.
Here's the official tutorial
If oyu are still using Python2, I think you should use PyGTK but it has been replaced by gl (which is described in the above tutorial). A good tutorial for PyGTK can be found here.
For a static interface, you can use glade which produces XML files that are then read by GTKBuilder to create the "real" interface. The first tutorial that I've found is available here

python gui tree walk

I am trying to figure out how to walk a tree and then display the output in a window that a user can navigate through much like that on the left hand side of my computer. Eventually I plan to have a fully browsable window just like you have on the right hand side. Here is what I have so far, I guess it is a mix of pseudo and actual code. This is for use on a Linux machine using python. I'm not looking for any code but mainly help as to how I can accomplish this with tkinter. Perhaps it is just me but I cannot find much help that helps me solve my problem - most just tell me how to display the directories etc. Any help would be greatly appreciated.
I want this window to look like this
My Documents <--------starting directory
My pictures<------subdirectory
picture1.jpg<-inside of subdirectoy
picture2.jpg
1234.exe<---------random file inside of my documents
I want to have a small folder picture next to a directory or a subdirectory also.
start at root
create window with tk
for dirname,subdirList,filelist in os.walk(root)
create new item(dirname)
for i in subdirList: #not sure what I would have to do to only
have subdirs showing once the directory was
clicked once
append i to item 1
for fname in fileList:
append fname to item 1
else:
item +=1
You can do it using the widget ttk.Treeview, there is a demo dirbrowser.py that does that. So all I can do here is give a stripped version of it and explain how it works. First, here is the short version:
import os
import sys
import Tkinter
import ttk
def fill_tree(treeview, node):
if treeview.set(node, "type") != 'directory':
return
path = treeview.set(node, "fullpath")
# Delete the possibly 'dummy' node present.
treeview.delete(*treeview.get_children(node))
parent = treeview.parent(node)
for p in os.listdir(path):
p = os.path.join(path, p)
ptype = None
if os.path.isdir(p):
ptype = 'directory'
fname = os.path.split(p)[1]
oid = treeview.insert(node, 'end', text=fname, values=[p, ptype])
if ptype == 'directory':
treeview.insert(oid, 0, text='dummy')
def update_tree(event):
treeview = event.widget
fill_tree(treeview, treeview.focus())
def create_root(treeview, startpath):
dfpath = os.path.abspath(startpath)
node = treeview.insert('', 'end', text=dfpath,
values=[dfpath, "directory"], open=True)
fill_tree(treeview, node)
root = Tkinter.Tk()
treeview = ttk.Treeview(columns=("fullpath", "type"), displaycolumns='')
treeview.pack(fill='both', expand=True)
create_root(treeview, sys.argv[1])
treeview.bind('<<TreeviewOpen>>', update_tree)
root.mainloop()
It starts by listing the files and directories present in the path given by sys.argv[1]. You don't want to use os.walk here as you show only the contents directly available in the given path, without going into deeper levels. The code then proceeds to show such contents, and for directories it creates a dummy children so this Treeview entry will be displayed as something that can be further expanded. Then, as you may notice, there is a binding to the virtual event <<TreeviewOpen>> which is fired whenever the user clicks an item in the Treeview that can be further expanded (in this case, the entries that represent directories). When the event is fired, the code ends up removing the dummy node that was created earlier and now populates the node with the contents present in the specified directory. The rest of the code is composed of details about storing additional info in the Treeview to make everything work.
I think tkinter would be a bad choice for this. Other libraries like wxPython, PyQt or GTK does have GUI components which will help you achieve this with minimum effort.

Categories