1

I'm having trouble implementing a thread correctly to keep my application from locking up and experiencing weird behavior. The app is designed to log into a ubuntu based server or ubuntu embedded server and search for log files that may be in the clear. The embedded server works, but the app keeps locking up while the search is occurring. The siteserver will not process. I have yet to code the local file search. I would like to add a progress bar once I figure out how to implement threads. I thought this would be straight forward since I've been learning and working with Python for several months now, but working with a GUI has its challenges. I'm still a neophyte and open to all the criticisms; it only helps me to become a better programmer. Any help is greatly appreciated. Here is the code below:

#!c:\python27

import wx
import os
import re
import paramiko
import string
import fileinput
import os.path
import dircache
import sys
import time
import datetime, time
import wx

from wxGui import *



class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame("SecureTool v2.0.0", (50, 60), (458, 332))
        frame.Show()
        self.SetTopWindow(frame)
        return True



class MyFrame(wx.Frame):
    def __init__(self, title, pos, size):
        wx.Frame.__init__(self, None, -1, title, pos, size)

        toolbar = self.CreateToolBar()
        toolbar.Realize()
        menuFile = wx.Menu()
        menuFile.Append(1, "&About...")
        menuFile.AppendSeparator()
        menuFile.Append(2, "E&xit")
        menuBar = wx.MenuBar()
        menuBar.Append(menuFile, "&File")
        menu2 = wx.Menu()
        menu2.Append(wx.NewId(), "&Copy", "Copy in status bar")
        menu2.AppendSeparator()
        menu2.Append(wx.NewId(), "C&ut", "")
        menu2.AppendSeparator()
        menu2.Append(wx.NewId(), "Paste", "")
        menu2.AppendSeparator()
        menu2.Append(wx.NewId(), "&Options...", "Display Options")
        menuBar.Append(menu2, "&Edit")

        self.SetMenuBar(menuBar)
        self.CreateStatusBar()
        self.SetStatusText("Welcome to SecureTool!")
        self.Bind(wx.EVT_MENU, self.OnAbout, id=1)
        self.Bind(wx.EVT_MENU, self.OnQuit, id=2)
        panel = wx.Panel(self)
        panel.SetBackgroundColour('LIGHT GREY')
        #Close button
        button = wx.Button(panel, label="EXIT", pos=(229, 160), size=(229, 80))
        self.Bind(wx.EVT_BUTTON, self.OnQuit, button)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
        #Embed Server button
        button2 = wx.Button(panel, label="Embed Server", pos=(0, 160), size=(229, 80))
        self.Bind(wx.EVT_BUTTON, self.OnIP, button2)
        #Site Server
        button3 = wx.Button(panel, label="SITESERVER", pos=(0, 80), size=(229, 80))
        self.Bind(wx.EVT_BUTTON, self.OnUsrPswd, button3)
        #Local Search
        button4 = wx.Button(panel, label="LOCAL SEARCH", pos=(229, 80), size=(229, 80))
        self.Bind(wx.EVT_BUTTON, self.OnOpen, button4)

        EVT_RESULT(self, self.OnResult)
        self.worker = None        

    def OnIP(self, event):
        ip_address = 0
        result = ''
        dlg = wx.TextEntryDialog(None, "Enter the IP Address.",
        'Embed Server Connect', 'xxx.xxx.xxx.xxx')
        if dlg.ShowModal() == wx.ID_OK:
            ip_address = dlg.GetValue()
        if ip_address:    
            cmsg = wx.MessageDialog(None, 'Do you want to connect to: ' + ip_address,
                                    'Connect', wx.YES_NO | wx.ICON_QUESTION)
            result = cmsg.ShowModal()

        if result == wx.ID_YES:
            self.DispConnect(ip_address)

            cmsg.Destroy()
        dlg.Destroy()
        return True

    def OnUsrPswd(self, event):
        passwrd = 0
        result = ''
        result = wx.TextEntryDialog(None, 'Enter Weekly Password', 'Site Server login','')
        if result.ShowModal() == wx.ID_OK:
            passwrd = result.GetValue()
        if passwrd:
            psmsg = wx.MessageDialog(None, 'Do you want to connect to the Siteserver?', 'Connect',
                                 wx.YES_NO | wx.ICON_QUESTION)
            result = psmsg.ShowModal()
        if result == wx.ID_YES:
            self.SiteserverConnect(passwrd)

            psmsg.Destroy()
        result.Destroy()
        return True

    def ErrMsg(self):
        ermsg = wx.MessageDialog(None, 'Invalid Entry!', 'ConnectionDialog', wx.ICON_ERROR)
        ermsg.ShowModal()
        ermsg.Destroy()

    def GoodConnect(self):
        gdcnt = wx.MessageDialog(None, 'You are connected!', 'ConnectionStatus', wx.ICON_INFORMATION)
        gdcnt.ShowModal()
        #if gdcnt.ShowModal() == wx.ID_OK:
        gdcnt.Destroy()

    def OnFinish(self):
        finish = wx.MessageDialog(None, 'Job is finished!', 'WorkStatus', wx.ICON_INFORMATION)
        finish.ShowModal()
        finish.Destroy()


    def DispConnect(self, address):
        pattern = r"\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"
        port = 22
        user = 'root'
        password ='******'
        if re.match(pattern, address):
            ssh = paramiko.SSHClient()
            ssh.load_system_host_keys()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(address,port,user,password)
            Ssh = ssh
            self.GoodConnect()
            self.OnSearch(Ssh)
        else:
            self.ErrMsg()

    def SiteserverConnect(self, password):
        port = 22
        user = 'root2'
        address = '10.5.48.2'
        if password:
            ssh = paramiko.SSHClient()
            ssh.load_system_host_keys()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(address,port,user,password)
            Ssh = ssh
            self.GoodConnect()
            self.OnSiteSearch(Ssh)
        else:
            self.ErrMsg()

    def startWorker(self,a, b, c):
        self.button2.Disable()
        self.thread = Thread(target=self.LongRunningSearch)
        self.thread.start()

    def OnSearch(self, sssh):
        self.startWorker(self.OnFinish, self.LongRunningSearch, wargs=[sssh])
        self.OnFinish()

    def LongRunningSearch(sssh):
        ssh = sssh
        apath = '/'
        apattern = '"*.txt" -o -name "*.log"' 
        rawcommand = 'find {path} -name "*.txt" -o -name "*.log"' 
        command1 = rawcommand.format(path=apath, pattern=apattern)
        stdin, stdout, stderr = ssh.exec_command(command1)
        filelist = stdout.read().splitlines()
        ftp = ssh.open_sftp()
        for afile in filelist:
            (head, filename) = os.path.split(afile)

        paths = '/dispenser_result.log'
        temp = ftp.file(paths, 'w')
        from time import strftime
        temp.write("{0:^75}".format("Company -Security Report" ) + strftime("    %Y-%m-%d %H:%M:%S") + "\n\n")   
        ustring = wx.TextEntryDialog(None, 'Enter a search string below:', 'Search', 'String Name')
        if ustring.ShowModal() == wx.ID_OK:
            userstring = ustring.GetValue()

        if userstring:
        userStrHEX = userstring.encode('hex')
        userStrASCII = ''.join(str(ord(char)) for char in userstring)
        regex = re.compile(r"(%s|%s|%s)" % ( re.escape( userstring ), re.escape( userStrHEX ), re.escape( userStrASCII )))      
    else:
        sys.exit('You Must Enter A String!!!')

    count = 0
    for afile in filelist:
        (head, filename) = os.path.split(afile)
        if afile.endswith(".log") or afile.endswith(".txt"):
            f=ftp.open(afile, 'r')
            for i, line in enumerate(f.readlines()):
                result = regex.search(line)
                if result:
                    count += 1
                    ln = str(i)
                    pathname = os.path.join(afile)
                    template = "\n\nLine: {0}\nFile: {1}\nString Type: {2}\n\n"
                    output = template.format(ln, pathname, result.group())
                    ftp.get(afile, 'c:\\Extracted\\' + filename)
                    temp.write(output)
                    break
            else:
                #print "No Match in: " + os.path.join(afile)
                temp.write("\nNo Match in: " + os.path.join(afile))
            f.close()

    for fnum in filelist:
        #print "\nFiles Searched: ", len(filelist)
        #print "Files Matched: ", count
        num = len(filelist)

        temp.write("\n\nFiles Searched:" + '%s\n' % (num))
        temp.write("Files Matched:"+ '%s\n' % (count))
        temp.write("Search String:"+ '%s\n' % (userstring))
        break
        temp.close()
    defaultFolder = "DispenserLogResults"
    if not defaultFolder.endswith(':') and not os.path.exists('c:\\Extracted\\DispenserLogResults'):
        os.mkdir('c:\\Extracted\\DispenserLogResults')
    else:
         pass
    ftp.get(paths, 'c:\\Extracted\\DispenserLogResults\\dispenser_result.log')

    ftp.remove(paths)

    re.purge()
    ftp.close()
    ssh.close()

    def OnSiteSearch(self, sssh):
        ssh = sssh
        apath = '/var/log/apache2 /var/opt/smartmerch/log/'
        apattern = '"*.log"' 
        rawcommand = 'find {path} -type f -name "*.log"' 
        command1 = rawcommand.format(path=apath, pattern=apattern)
        stdin, stdout, stderr = ssh.exec_command(command1)
        filelist = stdout.read().splitlines()
        ftp = ssh.open_sftp()
        for afile in filelist:
            (head, filename) = os.path.split(afile)

        paths = '/var/tmp/siteserver_result.log'
        temp = ftp.file(paths, 'w')
        from time import strftime
        temp.write("{0:^75}".format("Gilbarco - SQA Security Report" ) + strftime("    %Y-%m-%d %H:%M:%S") + "\n\n")   
        temp.write("\n{0:^75}".format("SiteServer Logs" ))
        ustring = wx.TextEntryDialog(None, 'Enter a search string below:', 'Search', 'String Name')
        if ustring.ShowModal() == wx.ID_OK:
            userstring = ustring.GetValue()

        if userstring:
            userStrHEX = userstring.encode('hex')
            userStrASCII = ''.join(str(ord(char)) for char in userstring)
            regex = re.compile(r"(%s|%s|%s)" % ( re.escape( userstring ), re.escape( userStrHEX ), re.escape( userStrASCII )))      
        else:
            sys.exit('You Must Enter A String!!!')

        count = 0
        for afile in filelist:
            (head, filename) = os.path.split(afile)
            if afile.endswith(".log") or afile.endswith(".txt"):
                f=ftp.open(afile, 'r')
                for i, line in enumerate(f.readlines()):
                    result = regex.search(line)
                    if result:
                        count += 1
                        ln = str(i)
                        pathname = os.path.join(afile)
                        template = "\n\nLine: {0}\nFile: {1}\nString Type: {2}\n\n"
                        output = template.format(ln, pathname, result.group())
                        ftp.get(afile, 'c:\\Extracted\\' + filename)
                        temp.write(output)
                        break
                else:
                    temp.write("\nNo Match in: " + os.path.join(afile))
                f.close()

        for fnum in filelist:
            num = len(filelist)

            temp.write("\n\nFiles Searched:" + '%s\n' % (num))
            temp.write("Files Matched:"+ '%s\n' % (count))
            temp.write("Search String:"+ '%s\n' % (userstring))
            break
            temp.close()
        defaultFolder = "SiteServerLogResults"
        if not defaultFolder.endswith(':') and not os.path.exists('c:\\Extracted\\SiteServerLogResults'):
            os.mkdir('c:\\Extracted\\SiteServerLogResults')
        else:
            pass
        ftp.get(paths, 'c:\\Extracted\\SiteServerLogResults\\siteserver_result.log')

        ftp.remove(paths)

        re.purge()
        ftp.close()
        ssh.close()
        self.OnFinish()

    def OnOpen(self,e):
        self.dirname = ''
        dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN)
        if dlg.ShowModal() == wx.ID_OK:
            self.filename = dlg.GetFilename()
            self.dirname = dlg.GetDirectory()
            f = open(os.path.join(self.dirname, self.filename), 'r')
            self.control.SetValue(f.read())
            f.close()
        dlg.Destroy()   

    def OnQuit(self, event):
        self.Close(True)

    def OnAbout(self, event):
        wx.MessageBox("This is sQAST v2.0.0",
            "About secureTool", wx.OK | wx.ICON_INFORMATION, self)



    def OnCloseWindow(self, event):
        self.Destroy()

if __name__ == '__main__':
    app = MyApp(False)
    app.MainLoop()

Traceback Error after running:

Traceback (most recent call last):
File "C:\SQA_log\wxGui.py", line 87, in OnIP
self.DispConnect(ip_address)
File "C:\SQA_log\wxGui.py", line 143, in DispConnect
self.OnSearch(Ssh)
File "C:\SQA_log\wxGui.py", line 169, in OnSearch
self.startWorker(self.OnFinish, self.LongRunningSearch, wargs=[sssh])
suffa
  • 3,606
  • 8
  • 46
  • 66
  • Ref: Bounty. I meant to say the OnSearch function. – suffa Oct 09 '11 at 16:19
  • I have added some examples and more explanations to my answer. – Fenikso Oct 10 '11 at 09:59
  • I tried some other combinations and this is the error that keeps occurring now: Traceback (most recent call last): File "C:\SQA_log\Gui_Thread.py", line 174, in OnIP self.DispConnect(ip_address) File "C:\SQA_log\Gui_Thread.py", line 226, in DispConnect self.OnSearch(Ssh) File "C:\SQA_log\Gui_Thread.py", line 264, in OnSearch t = WorkerThread() TypeError: __init__() takes exactly 2 arguments (1 given) – suffa Oct 12 '11 at 19:03
  • Of course. `t= WorkerThread()` is wrong if you have `notify_window` as a required parameter. Still not sure why do you use such a complicated approach? Why do not you use `wx.lib.delayedresult`? – Fenikso Oct 13 '11 at 06:26
  • I have added ability to abort the thread to both my examples. I have also noticed that using `WorkerThread` as a `Thread` subclass complicates things for you as you lose ability to read class attributes of `MyFrame` inside the "threaded" function. – Fenikso Oct 13 '11 at 07:26
  • BTW: You are using 10 years old and very complicated example to build your code ;-). – Fenikso Oct 13 '11 at 07:38
  • @Fenikso That's why I'm here, trying to learn ... catch up with the times, if you will. I'm not ashamed of my ignorance; we all have to start somewhere .... I will attempt the wx.lib.delayed result. You told me what was wrong! Can you give me a push in the right direction? – suffa Oct 13 '11 at 13:21
  • I would revert your code to the point when it has no treading at all in it. It should be running fine, just freezing your GUI when some long callback is called. Then just mark with comment which callback method is freezing your GUI. We can go from there. – Fenikso Oct 14 '11 at 08:33
  • @Fenikso Okay, I have no threading. When I start the app, I select Embed Server button. I'm immediately prompted for ip_address(OnIp method). Once I enter, a prompts asks do I want to connect ... Finally, I get to the prompt that ask for a search string. After I enter the search string (OnSearch), the Gui locks-up. – suffa Oct 14 '11 at 16:32
  • If the new answer combined with the examples does not help, I give up :-). – Fenikso Oct 14 '11 at 17:30
  • I suppose that even if GUI locks up, it unfreezes when search is finished, correct? – Fenikso Oct 14 '11 at 17:33
  • @Fenikso - that is correct, if I don't touch the gui ... You may give up, but I won't. I don't see what you obviously do, with your vast expertise, and knowledge in Python. I will keep plugging away until I finally see what you see so easily, so much so that you're exasperated with my lethargic analyzing of this problem. Thanks anyways. – suffa Oct 14 '11 at 18:09
  • So in this case the second answer should work and it should be really easy to implement. Fingers crossed. – Fenikso Oct 14 '11 at 18:49

4 Answers4

4

I don't see any threading in your application at all. Whenever you make a call to something that will take a while, that something needs to run in a separate thread or it will block the main loop of your GUI.

You should read the following wiki entry on threading in wxPython: http://wiki.wxpython.org/LongRunningTasks

I have used the information therein to successfully create threaded wxPython applications. There's also a simple threading tutorial here: http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/

Hope that helps. If you get stuck, you should post to the official wxPython mailing group: https://groups.google.com/forum/#!forum/wxpython-users Those guys will set you straight.

Mike Driscoll
  • 32,629
  • 8
  • 45
  • 88
  • I have been looking at some tutorials on threading ... maybe you can give me a push in the right direction. Should I call the OnSearch function in a threaded class, to run as a separate thread? The problem with tutorials is that you can't ask questions, even the most mundane, seemingly-insignificant .... – suffa Oct 05 '11 at 20:06
  • Yeah, that looks like one. The SiteSearch one might be one too. You can tell which ones they are when you fire them off. If your application freezes when you run those methods, than those methods need to be in a thread. – Mike Driscoll Oct 05 '11 at 20:21
4

In your particular case I would do something like:

1) Encapsulate the long task and separate it from the GUI reaction, simplify your method:

def OnSearch(self, sssh):
    self.LongRunningSearch(sssh) # Move all the blocking code here,
                                 # just NOT the GUI reaction !
                                 # Meaning self.OnFinish()...
    self.OnFinish()

2) Verify that it still runs fine. Then modify the method to add the thread:

def OnSearch(self, sssh):
    startWorker(self.OnFinish, self.LongRunningSearch, wargs=[sssh])

self.OnSearch will end immediately and self.OnFinish will be called after the thread running self.LongRunningSearch has finished. It may still need some tuning as I am unable to run your code on my computer.

Fenikso
  • 9,251
  • 5
  • 44
  • 72
  • I want to clarify that I'm following you correctly: I need to add a LongRunningSearch method to my code above (similar to the LongTask method in your code: Example 2)? All of code in OnSearch method except for ustring = wx.TextEntry...(GUI) should be moved to LongRunningSearch? – suffa Oct 14 '11 at 19:19
  • Your code has a bit confusing structure. Try moving everything but `self.OnFinish()` to LongRunningSearch. You are still using `ShowModal()` so you are basically freezing your GUI at this point. In ideal world you would have your GUI separated from the main functionality code. – Fenikso Oct 14 '11 at 19:32
  • I followed step 1, and it still ran as before (w/ lockup). So, I went to step 2 (modify the method to add the thread). startWorker has me a little confused because I'm associating what I'm doing with your example number 2 below, where I see in the OnStartButton method you have self.thread.start(). – suffa Oct 14 '11 at 20:15
  • You are missing the import `from wx.lib.delayedresult import startWorker` and you have added `self` which should not be there. – Fenikso Oct 15 '11 at 15:37
  • I'm still trying tie the lose ends together, but considering you pointed me in the right direction ... I'm appreciative. – suffa Oct 16 '11 at 16:29
  • here is the traceback error after add the import... Traceback (most recent call last): File "C:\SQA_log\wxGui.py", line 93, in OnIP self.DispConnect(ip_address) File "C:\SQA_log\wxGui.py", line 154, in DispConnect self.OnSearch(Ssh) File "C:\SQA_log\wxGui.py", line 186, in OnSearch self.startWorker(self.OnFinish, self.LongRunningSearch, wargs=[sssh]) TypeError: startWorker() got an unexpected keyword argument 'wargs' – suffa Oct 16 '11 at 20:52
  • I changed the self.startWorker()... to just startWorker()... and I don't get the traceback error. However, as soon as the search prompt comes up (ustring = TextEntryDialog....), the app crashes. – suffa Oct 16 '11 at 20:58
  • Yeah, you should not write your `startWorker`, just import the one from `wx`. Now you probably need to move back to main thread (and back to `OnSearch` just before `startWorker`) anything what is not the long running task. Especially any GUI interactions. As I said before, it may be helpful to redesign you code to separate GUI and application functionality. – Fenikso Oct 17 '11 at 03:49
3

You can also have a look at convenience module for threading implemented in the wx, wx.lib.delayedresult. It is very easy to use and to add when you find it is needed. I am not sure why it is ignored so often. I have written an example which uses it some time ago here.

It basically needs you to create two functions / methods. First, which will be ran in another thread, and second, which will be ran after another thread finishes. Then you just call startWorker(LongTaskDone, LongTask).

Example 1: Using wx.lib.delayedresult. wx.CallAfter is used to show progress in GUI thread using gauge widget. Official Documentation.

from time import sleep
import wx
from wx.lib.delayedresult import startWorker

class MainWindow(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.panel = wx.Panel(self)
        self.startButton = wx.Button(self.panel, label="Long Task")
        self.abortButton = wx.Button(self.panel, label="Abort")
        self.abortButton.Disable()
        self.gauge = wx.Gauge(self.panel, size=(-1, 20))
        self.shouldAbort = False

        self.startButton.Bind(wx.EVT_BUTTON, self.OnStartButton)
        self.abortButton.Bind(wx.EVT_BUTTON, self.OnAbortButton)

        self.windowSizer = wx.BoxSizer()
        self.windowSizer.Add(self.panel, 1, wx.ALL | wx.EXPAND)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.startButton)
        self.sizer.Add(self.abortButton)
        self.sizer.Add((10, 10))
        self.sizer.Add(self.gauge)

        self.border = wx.BoxSizer()
        self.border.Add(self.sizer, 1, wx.ALL | wx.EXPAND, 5)

        self.panel.SetSizerAndFit(self.border)  
        self.SetSizerAndFit(self.windowSizer)   
        self.Show()

    def OnStartButton(self, e):
        self.startButton.Disable()
        self.abortButton.Enable()
        startWorker(self.LongTaskDone, self.LongTask)

    def OnAbortButton(self, e):
        self.shouldAbort = True

    def LongTask(self):
        for a in range(101):
            sleep(0.05)
            wx.CallAfter(self.gauge.SetValue, a)
            if self.shouldAbort:
                break
        return self.shouldAbort

    def LongTaskDone(self, result):
        r = result.get()
        if r:
            print("Aborted!")
        else:
            print("Ended!")
        self.startButton.Enable()
        self.abortButton.Disable()
        self.shouldAbort = False
        self.gauge.SetValue(0)

app = wx.App(False)
win = MainWindow(None)
app.MainLoop()

Example 2: Using standard threading module. In some cases this may be more "ugly". I would recommend using wx.lib.delayedresult. Official Documentation.

from time import sleep
from threading import Thread
import wx

class MainWindow(wx.Frame):
    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.panel = wx.Panel(self)
        self.startButton = wx.Button(self.panel, label="Long Task")
        self.abortButton = wx.Button(self.panel, label="Abort")
        self.abortButton.Disable()
        self.gauge = wx.Gauge(self.panel, size=(-1, 20))
        self.shouldAbort = False
        self.thread = None

        self.startButton.Bind(wx.EVT_BUTTON, self.OnStartButton)
        self.abortButton.Bind(wx.EVT_BUTTON, self.OnAbortButton)

        self.windowSizer = wx.BoxSizer()
        self.windowSizer.Add(self.panel, 1, wx.ALL | wx.EXPAND)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.startButton)
        self.sizer.Add(self.abortButton)
        self.sizer.Add((10, 10))
        self.sizer.Add(self.gauge)

        self.border = wx.BoxSizer()
        self.border.Add(self.sizer, 1, wx.ALL | wx.EXPAND, 5)

        self.panel.SetSizerAndFit(self.border)  
        self.SetSizerAndFit(self.windowSizer)   
        self.Show()

    def OnStartButton(self, e):
        self.startButton.Disable()
        self.abortButton.Enable()
        self.thread = Thread(target=self.LongTask)
        self.thread.start()

    def OnAbortButton(self, e):
        self.shouldAbort = True

    def LongTask(self):
        for a in range(101):
            sleep(0.05)
            wx.CallAfter(self.gauge.SetValue, a)
            if self.shouldAbort:
                break
        wx.CallAfter(self.LongTaskDone, self.shouldAbort)

    def LongTaskDone(self, r):
        if r:
            print("Aborted!")
        else:
            print("Ended!")
        self.startButton.Enable()
        self.abortButton.Disable()
        self.shouldAbort = False
        self.gauge.SetValue(0)

app = wx.App(False)
win = MainWindow(None)
app.MainLoop()

Note: threading.Lock may be needed for thread-safe passing of more complicated variables and data than simple boolean flag.

Edit: Added examples.

Edit: Added ability to abort the thread.

Edit: Simplified the examples. Got rid of timer based on tom10's idea and also deleted threading.Lock as it is not needed here.

Community
  • 1
  • 1
Fenikso
  • 9,251
  • 5
  • 44
  • 72
  • I have been trying to implement this concept to my code, to no avail. The code works perfectly when implementing a simple timer as the LongTask. Almost every example that I've seen with threads have used a timer as the long task. When code that does something is implemented, it does not appear to be that straight forward. – suffa Oct 12 '11 at 11:35
  • @user706808 - I usually use this for long reading from internet, long computations, or similar tasks in my applications. What exactly does not work for you? What exactly is not "appearing to be that straight forward"? Do you get errors? Does it still freeze your GUI? – Fenikso Oct 12 '11 at 11:38
  • 1
    @user706808 - If you would like to get real help with your code, you have to create as small as possible example of **runnable** code we can try on our computers to recreate your problem. This **[example](http://stackoverflow.com/questions/5732952/draw-on-image-buffer-memorydc-in-separate-thread/5753888#5753888)** uses another thread to compute fractal. – Fenikso Oct 12 '11 at 11:42
  • Wow, I see that I have the code out there before attempting to use a thread. Editing ... – suffa Oct 12 '11 at 16:51
1

In wxPython, all calls to methods of GUI objects and event handlers need to happen in the main thread. Because of this, you need to be careful when using threading with wx, but it's usually easy to make the required modifications.

In your code, for example, you could do the call to wx.TextEntryDialog from the main thread, and then pass this information to the search thread.

If you need to request action of the GUI from the thread, the easiest way is to use wx.CallAfter, though there are other approaches that work as well.

tom10
  • 67,082
  • 10
  • 127
  • 137
  • I did not know that I can use wx.CallAfter to update GUI even from running thread. I always thought that it is called **after** the thread finishes. Thanks for the insight! – Fenikso Oct 13 '11 at 07:50
  • My guess is that "after" refers to after the current event processing in the GUI is finished. Just to be clear on this then, wx.CallAfter can be used to update the GUI from a running thread. An example of this is Listing 18.5 in the *wxPython in Action* book, or the last example here: http://wiki.wxpython.org/LongRunningTasks – tom10 Oct 13 '11 at 16:41