Question

I am new to python and gui programming.I am writing a program that will list all the directories in root(/) and all the sub-directories in these directories.I am using treeview to show these directories and sub-directories.To speed up my program i am using multi-threading,but i am facing a problem that the tree is displayed only after all the threads are executed.I want tree to be displayed as any thread is executed and other nodes append to tree dynamically as other threads are executed.Thanks in advance. Here is my code

import os
import threading
import gtk

class FileBrowser:      
    def __init__(self):
        self.window = gtk.Window()
        self.window.show_all()
        self.window.connect("destroy", gtk.main_quit)

        box = gtk.VBox()
        box.show()

        self.scrolled_w = gtk.ScrolledWindow()
        self.scrolled_w.show()
        self.window.add(box)

        button = gtk.Button("start")
        button.show()
        button.connect("clicked", self.start_scanning)
        button.set_size_request(30, 50)
        box.pack_start(button, False, False, 4)

        self.model=gtk.TreeStore(str)
        self.treeview = gtk.TreeView(self.model)
        self.treeview.show()

        col = gtk.TreeViewColumn("FileName")
        cell = gtk.CellRendererText()

        self.treeview.append_column(col)
        col.pack_start(cell, 0)
        col.set_attributes(cell, text=0)        

        box.pack_start(self.scrolled_w, 10)
        self.scrolled_w.add(self.treeview)
        self.window.set_size_request(600, 300)

    def start_scanning(self, w):
        self.model.clear()
        main_dir = "/"
        list_dir = os.listdir(main_dir)
        no_of_threads = len(list_dir)
        threads = []

        for i in range(no_of_threads):
            t = threading.Thread(target=self.thread_scanning,
                                 args=(main_dir,list_dir[i],))
            threads.append(t)
            t.start()   
            for t in threads:
                t.join()

    def thread_scanning(self, main_d, list_d):
        path = main_d+""+list_d
        if os.path.isdir(path):
            list_subd = os.listdir(path)
            par = self.model.append(None, [list_d])
            for sub in list_subd:
                self.model.append(par, [sub])

    def main(self):
        gtk.main()


if __name__=="__main__":
    fb = FileBrowser()
    fb.main()
Was it helpful?

Solution

So there were two basic issues with your code.

  1. As @windfinder pointed out, you need to run gobject.threads_init() for threading to work properly with your gtk-application if you want the threads to update your gui. Actually it can cause many weird problems if you don't run gobject.threads_init()

  2. The callback of the button doesn't finnish until all threads have finnished since you ask the threads to join before you return control to the main-loop. You can fix this by adding a timeout via gobject.

I did a couple of changes to your code (I also asked the threads to sleep so I could see that the gui was updated while the threads are still running).

#!/usr/bin/env python

import gobject
import time  # This is just used for slowing down the threads
import os
import threading
import gtk


class FileBrowser:

    def __init__(self):
        self.window = gtk.Window()
        self.window.show_all()
        self.window.connect("destroy", self._destroy)

        box = gtk.VBox()
        box.show()

        self.scrolled_w = gtk.ScrolledWindow()
        self.scrolled_w.show()
        self.window.add(box)

        self.status = gtk.Label("...")  # I added a status label to see when scan is done
        self.status.show()
        box.pack_start(self.status, False, False, 2)

        self.button = gtk.Button("start")
        self.button.show()
        self.button.connect("clicked",self.start_scanning)
        self.button.set_size_request(30,50)
        box.pack_start(self.button,False,False,4)

        self.model=gtk.TreeStore(str)
        self.treeview = gtk.TreeView(self.model)
        self.treeview.show()

        col = gtk.TreeViewColumn("FileName")
        cell = gtk.CellRendererText()

        self.treeview.append_column(col)
        col.pack_start(cell,0)
        col.set_attributes(cell,text=0)


        box.pack_start(self.scrolled_w,10)
        self.scrolled_w.add(self.treeview)
        self.window.set_size_request(600,300)

        self.threads = list()  # I made this be part of the full application
        """ That allows us to wait for all threads on close-down, don't know how
        necessary it is."""

    def start_scanning(self,w):

        self.button.set_sensitive(False)  # To disallow stacking scan-stats
        self.status.set_text("Scanning...")  # Let user know it is started
        self.model.clear()
        main_dir= os.path.expanduser("~")  # I just wanted my home-dir instead
        list_dir = os.listdir(main_dir)
        no_of_threads = len(list_dir)
        for i in range(no_of_threads):
            t = threading.Thread(target=self._thread_scanning,args=(main_dir,list_dir[i],))
            self.threads.append(t)
            t.start()

        gobject.timeout_add(200, self._callback)  # This will cause the main app to
        #check every 200 ms if the threads are done.

    def _callback(self):

        if threading.active_count() == 1:  # If only one left, scanning is done
            self.status.set_text("Done!")
            self.button.set_sensitive(True)  # Allow button being pressed again
            return False  # False make callback stop

        print threading.active_count()
        return True

    def _thread_scanning(self,main_d,list_d):
        path = os.sep.join((main_d, list_d))  # Made use of os's sep instead...
        if os.path.isdir(path):
            list_subd = os.listdir(path)
            par = self.model.append(None,[list_d])
            for sub in list_subd:
                self.model.append(par,[sub])

        time.sleep(3)  # Useless other than to delay finish of thread.

    def main(self):

        gtk.main()


    def _destroy(self, *args, **kwargs):

        #Own destroy that waits for all threads...
        for t in self.threads:
            t.join()

        gtk.main_quit(*args, **kwargs)

if __name__=="__main__":
    gobject.threads_init()
    fb=FileBrowser()
    fb.main()
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top