Reputation: 2295
I am writing a wxpython program.
If you open a file in the program, it counts the number of lines in the file and displays it in a staticText
Widget.
Here's the relevant code:
In class Frame
def on_select_file(self, event):
'''Event handler for selecting file'''
filepath = getFile() # This is a seperate method which asks the user to select a file and returns the path
threading.Thread(target = methods.count_lines, args = (filepath, self.staticText)).start()
methods.py
def count_lines(filepath, staticText):
with open(filepath) as f:
for i, _ in enumerate(f): pass
staticText.SetLabel(str(i+1))
Most of the files I'm dealing with are very large (3-4 GB) with around 25 million lines. So if I open a large file, it takes a few tens of seconds to count and display the no. of lines. However if I select a large file and before the staticText
widget is updated, open another file which is smaller, the staticText
widget shows the new count, but after some time shows the count of the previous file. I understand this is because the previous thread was still running and updated the widget after it ended.
I tried working around it by passing a flag variable as a parameter to the counter function, to check if the widget has been updated. However it does not seem to work. Is there any other way to avoid this?
Upvotes: 1
Views: 1008
Reputation: 2295
I modified the code in the Sir Digby Chicken Caesa's answer to suit my need. Rather than creating a custom event and having it fired after some time interval, I set up the line counting thread such that it could be aborted every time a file open event occurs and a previous instance of this thread is still running.
Here's a short example of the implementation:
import threading.Thread
class Line_Counter(threading.Thread):
def __init__(self, staticText, filename):
threading.Thread.__init__(self)
self.staticText = staticText
def run(self):
self.exit = False
with open(self.filename) as f:
if self.exit:
return
for count, _ in enumerate(f): pass
self.staticText.SetLabel(str(count + 1))
def abort(self):
self.exit = True
class MainFrame(wx.Frame):
def __init__(self, parent, id, filename):
wx.Frame.__init__(self, parent, id, 'Threaded File Loader')
self.line_counter = False # Set up a dummy thread variable
#### remaining code
self.file_open_button.Bind( wx.EVT_BUTTON, self.OnOpenFile ) # Event handler for opening file
def OnOpenFile(self, event):
filepath = getFile() # Creates a file open dialog and returns the file path
if self.line_counter: # Checks if a counter thread is already running
self.line_counter.abort() # Stops the thread if True
self.line_counter = Line_Counter(self.staticText, filename).start() # Starts a new thread
Upvotes: 0
Reputation: 3123
Essentially, create a custom event type that contains the current line count as a member, and in your worker thread periodically post an event of that type to the class that contains your static text widget using wx.PostEvent(). Then, when the main thread resumes and processes its event loop, you can use the line count reported by the received event(s) to set the text string.
Something like this should work:
import time
from threading import *
import wx
import os.path
EVT_LINE_NUMBER_UPDATE_ID = wx.NewId()
class LineNumberUpdateEvent(wx.PyEvent):
def __init__(self, lineNum):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_LINE_NUMBER_UPDATE_ID)
self.lineNumber = lineNum
class WorkerThread(Thread):
def __init__(self, notify_window, filename):
Thread.__init__(self)
self._notify_window = notify_window
self._filename = filename
self.start()
def run(self):
with open(this._filename,"r") as file:
count = 0;
for line in file:
count++
if count % 50 == 0: # post an event every 50 lines
wx.PostEvent(self._notify_window, LineNumberUpdateEvent(count))
wx.PostEvent(self._notify_window, LineNumberUpdateEvent(count)) # last event
class MainFrame(wx.Frame):
def __init__(self, parent, id, filename):
wx.Frame.__init__(self, parent, id, 'Threaded File Loader')
self.status = wx.StaticText(self, -1, '', pos=(0,100))
self.Bind(EVT_LINE_NUMBER_UPDATE_ID, self.OnLineNumberUpdate)
if (os.path.isfile(filename))
self.worker = WorkerThread(self,filename)
def OnLineNumberUpdate(self, event):
self.status.SetLabel(str(event.lineNumber))
This was adapted from an example posted on the wx Wiki:
http://wiki.wxpython.org/LongRunningTasks
Upvotes: 3