How do I handle multiple EVT_TEXT events in wxPython? - python

This is one part of a two part question (other part is here)
So here's what I'm looking for: A function which is bound to the EVT_TEXT event of a text control that waits a few seconds, then calls another function at the end of the delay time. Thats easy enough, but, I'd like it to reset the delay time every time a new EVT_TEXT event is generated. The effect I'm looking for is to have a user type into the text control, then after I assume they're done, I run the function described in the other part of this question which spell checks what they've written.
So the simple approach I tried was this:
def OnEdit(self, event):
for i in range(0,3):
print i
time.sleep(1)
However, this just forces a 3 second wait, no matter what. How do I "break in" to this function to reset the counter? Thanks in advance.
EDIT: Turns out the way to do this was with threading. Yippee

The full threading answer, built with the help of this tutorial:
from threading import *
import wx
import time
EVT_RESULT_ID = wx.NewId()
def EVT_RESULT(win, func):
win.Connect(-1, -1, EVT_RESULT_ID, func)
class MyGui(wx.Frame):
def __init__(self):
self.spellchkthrd = None
#lots of stuff
self.input = wx.TextCtrl(self.panel, -1, "", size=(200, 150), style=wx.TE_MULTILINE|wx.TE_LEFT|wx.TE_RICH)
self.Bind(wx.EVT_TEXT, self.OnEdit, self.input)
EVT_RESULT(self, self.OnSplCheck)
def OnEdit(self, event):
if not self.spellchkthrd:
self.spellchkthrd = SpellCheckThread(self)
else:
self.spellchkthrd.newSig()
def OnSplCheck(self, event):
self.spellchkthrd = None
#All the spell checking stuff
class ResultEvent(wx.PyEvent):
def __init__(self):
wx.PyEvent.__init__(self)
self.SetEventType(EVT_RESULT_ID)
class SpellCheckThread(Thread):
def __init__(self, panel):
Thread.__init__(self)
self.count = 0
self.panel = panel
self.start()
def run(self):
while self.count < 1.0:
print self.count
time.sleep(0.1)
self.count += 0.1
wx.PostEvent(self.panel, ResultEvent())
def newSig(self):
print "new"
self.count = 0

Related

Background infinite loop in python

Using python 3.8.5, i've try to create a class object which have a inside infinite loop which will update a value that i can have to read later... Unfortunaly, my knowledge on this field are a little poor.
The aims of this trial is to dissociate an app which has a state from the gui i've made...
Here is one not working trial i've made.
import asyncio
from time import sleep
class value_holder:
def __init__(self):
self.value = 0
asyncio.create_task(self.infinite_loop())
async def infinite_loop(self):
while True:
self.value += 1
sleep(3)
v = value_holder()
while True:
print(v.value)
sleep(1)
I'm actually clueless so if someone have any clue or keyword for helping me in this search of solution, i will be very thankful
Best regards
You could work with a timer instead :
import threading
from time import sleep
class value_holder:
value = 0
timer = None
delay = 1
def __init__(self, delay):
self.value = 0
self.delay = delay
def run(self):
self.value += 1
print(self.value)
if self.timer != None: self.start()
def start(self):
self.timer = threading.Timer(self.delay, self.run)
self.timer.start()
def stop(self):
self.timer.cancel()
v = value_holder(3.0)
v.start()
sleep(9) #==> 1, 2, 3
v.stop()
print("Stopped")

Need advice to keep GUI responsive

Basically, what I have is a GUI with some QLineEdits, a "search button" and a table. You hit the button and a class called DataGrabber searches a database for data, processes them, returns a list with dictionaries with which the table is filled, accordingly. These searches can take a while and I need to keep my GUI responsive. Also, I want the statusbar message to change as long as the search is going on (something like "Searching." -> "Searching.." -> "Searching...", the functionality is not rly important here, it's just about understanding how I can handle this properly).
I started with threading everything and created a queue between the thread that handles the search and the function that handles the statusbar, to know when the search is done. But that seems really goofy. Especially since Qt provides all kind of tools, like QThread and Signals. But I'm rly lost right now. What would be the best way to handle the responsiveness when having such a time-consuming action like a database search? And what would be the best way to tell the main/child thread that the search is finished?
Here is a reduced version of what I have right now:
class GUI(Ui_MainWindow, InitGlobals):
def __init__(dialog):
...
self.start_button_3.clicked.connect(\
lambda: self.start_search(self.result_tab_3))
...
def start_search():
...
search_paras = [3,
self.name_box_3.text(),
self.project_combo_3.currentText(),
self.voltage_box.text(),
self.volume_box.text()]
queue = Queue()
thr = Thread(target=self.search_thread, args=(queue, search_paras,))
thr.start()
data_lst = statusbar_load(queue, self.statusbar, option="loader")
thr.join()
self.statusbar.showMessage("Search completed...")
for dic in data_lst:
self.write_to_table(dic, tab)
def search_thread(self, queue, args):
grabber = DataGrabber(self.db_config)
...
if args[0] == 3:
queue.put(grabber.alpha_search(args[1], args[2],
args[3], args[4]))
queue.task_done()
def statusbar_load(queue, statusbar_obj, option="spinner"):
data = None
i = 0
while data is None:
try:
data = queue.get(timeout=0.1)
except Empty:
if option == "spinner":
status = ["-", "\\", "|", "/"]
statusbar_obj.showMessage("Searching [" + status[i%4] + "]")
....
i = i + 1
return data
This can be handled with signals. You can use signals to send the results to the GUI and to update the GUI of the progress.
Here is a quick example of the implementation with a progress bar and status label. These can be included in a status bar if you would like:
class GUITest(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
layout = QtWidgets.QGridLayout()
self.button = QtWidgets.QPushButton('Run')
self.button.clicked.connect(self.run)
self.result_box = QtWidgets.QTextBrowser()
self.label = QtWidgets.QLabel()
self.progress_bar = QtWidgets.QProgressBar()
self.progress_bar.setVisible(False)
self.progress_bar.setMinimum(0)
self.progress_bar.setMaximum(100)
self.progress_bar.setValue(0)
layout.addWidget(self.button)
layout.addWidget(self.result_box)
layout.addWidget(self.label)
layout.addWidget(self.progress_bar)
self.setLayout(layout)
def run(self):
self.progress_bar.setVisible(True)
self.label.setText('Searching...')
self.thread = QtCore.QThread()
self.data_grabber = DataGrabber()
self.data_grabber.moveToThread(self.thread)
self.data_grabber.update_progress.connect(self.update_progress_bar)
self.data_grabber.results.connect(self.display_results)
self.data_grabber.finished.connect(self.complete)
self.data_grabber.finished.connect(self.thread.quit)
self.data_grabber.finished.connect(self.data_grabber.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.started.connect(self.data_grabber.run)
self.thread.start()
def update_progress_bar(self):
self.progress_bar.setValue(self.progress_bar.value() + 1)
def complete(self):
self.label.setText('Complete')
self.progress_bar.setVisible(False)
def display_results(self, results):
for key, value in results.items():
self.result_box.append('%s: %s' % (key, value))
class DataGrabber(QtCore.QObject):
finished = QtCore.pyqtSignal()
update_progress = QtCore.pyqtSignal()
results = QtCore.pyqtSignal(dict) # set the type of object you are sending
def __init__(self):
super().__init__()
self.count = 0
def run(self):
# search database here and emit update_progress when appropriate
while self.count <= 100:
self.update_progress.emit()
self.count += 1
time.sleep(0.02)
self.send_results() # when done, send the results
self.finished.emit()
def send_results(self):
results = {'one': 'Result One', 'two': 'Result Two', 'three': 'Result Three'}
self.results.emit(results)

Python multipul Event Timer

I have a water pump with pressure sensors. One on the input (low) and one on the output (high). My problem is my low pressure sensor. Sometimes the low pressure is just at the cut-off point causing the motor to start and stop quickly - this is not desirable. The system is running on a home-made PLS.
I'm a beginner at programming, 3 months, but the system is working for the most part. I need help on creating a timer between low pressure alarm events. I am thinking that the system can have 3 events within 30 seconds, but if any one event occurs in less than 5 seconds the system should shut down.
So if less than 5 seconds between the first event and second event the motor shuts down for good. The same goes for for second to third and third to fourth event. On the fourth event if less than 30 seconds occurs between first event and the fourth, the system also shuts down for good. Keep in mind that this is a part of a much larger loop. Here is the code I was able to create:
def Systemofftimer():
EventCounter = (0)
OneTimeLoopVarable = (0)
While True
if (is_low_pressure_alarm_on() and (OneTimeLoopVarable ==0)):
Timer = time.time()
EventCounter = EventCounter + (1)
OneTimeLoopVarable = 1
if EventCounter == (2) and (time.time() - Timer >= (10))
EventCounter = EventCounter + (1)
stop_motor()
if EventCounter == (3) and (time.time() - Timer >= (20))
EventCounter = EventCounter + (1)
stop_motor()
if EventCounter == (4) and (time.time() - Timer >= (30))
EventCounter = EventCounter + (1)
stop_motor()
else:
start_motor()
I would actually use a different approach for this: simply make your threshold for turning on larger than your threshold for turning of. For example:
That way you don't need to deal with the timing of it and can still eliminate the jittery nature around your state transition. You can also tune this to account for how noisy your sensors are.
Edit:
Below I've mocked up the piece of your system you're asking about. It's probably way more than you were initially looking for, but I wanted to test make sure it all worked properly before I posted so you're welcome to use it in whole or in part. As for the timer you asked about, it's based on Hans Then's post from this thread. To trigger the alarm, you just call TriggerAlarm() on the PumpSystem class. It will log that an alarm was triggered and then check the two conditions you mentioned in your question (5 sec and 30 sec errors). Each element of self.alarms contains the number of alarms that happened in a particular second, and each second the timer triggers to remove the oldest second from the list and create a fresh one. If you run the program, you can trigger alarms by pressing spacebar and see how the list is updated. The MockUp class is just meant to test and demonstrate how this works. I imagine you'll remove it if you decide to plug some portion of this into what you're working on. Anyway, here's the code.
from threading import Timer, Thread, Event
class PumpSystem():
def __init__(self):
self.alarms = [0 for x in range(30)]
self.Start()
return None
def SetUpdateFlag(self, flag):
self.update_flag = flag
return True
def Start(self):
self.stop_flag = Event()
self.thread = ClockTimer(self.OnTimerExpired, self.stop_flag)
self.thread.start()
return True
def Stop(self):
self.stop_flag.set()
return True
def TriggerAlarmEvent(self):
self.alarms[-1] += 1
self.CheckConditions()
self.update_flag.set()
return True
def OnTimerExpired(self):
self.UpdateRunningAverage()
def CheckConditions(self):
# Check if another error has triggered in the past 5 seconds
if sum(self.alarms[-5:]) > 1:
print('5 second error')
# Check if more than 3 errors have triggered in the past 30 seconds
if sum(self.alarms) > 3:
print('30 second error')
return True
def UpdateRunningAverage(self):
self.alarms.append(0)
self.alarms.pop(0)
self.update_flag.set()
return True
class ClockTimer(Thread):
def __init__(self, callback, event):
Thread.__init__(self)
self.callback = callback
self.stopped = event
return None
def SetInterval(self, time_in_seconds):
self.delay_period = time_in_seconds
return True
def run(self):
while not self.stopped.wait(1.0):
self.callback()
return True
## START MOCKUP CODE ##
import tkinter as tk
class MockUp():
def __init__(self):
self.pump_system = PumpSystem()
self.update_flag = Event()
self.pump_system.SetUpdateFlag(self.update_flag)
self.StartSensor()
return None
def StartSensor(self):
self.root = tk.Tk()
self.root.protocol("WM_DELETE_WINDOW", self.Exit)
self.alarms = tk.StringVar()
w = tk.Label(self.root, textvariable=self.alarms, width=100, height=15)
self.alarms.set(self.pump_system.alarms)
w.pack()
self.root.after('idle', self.ManageUpdate)
self.root.bind_all('<Key>', self.ManageKeypress)
self.root.mainloop()
return True
def ManageUpdate(self):
if self.update_flag.isSet():
self.alarms.set(self.pump_system.alarms)
self.update_flag.clear()
self.root.after(1, self.ManageUpdate)
return True
def ManageKeypress(self, event):
if event.keysym == 'Escape':
self.Exit()
if event.keysym == 'space':
self.pump_system.TriggerAlarmEvent()
return True
def Exit(self):
self.pump_system.Stop()
self.root.destroy()
mockup = MockUp()
This may look like a lot, but half is the mockup class that you can probably just ignore. Let me know if there's anything that you're confused about and I'd be happy to explain what's happening.

Thread synchronization in GTK3+ and Python

I'm currently learning both Python and GTK 3+ and I've a problem when synchronizing threads. I'll try to be quick and clear:
I have to make a Social Network client. As the purpose is to learn how to create a GUI the "access to the social network API" will be simulated, but I have to "wait" for network responses with time.sleep(). Calling time.sleep() in the main thread freezes the GUI (it stops the execution of Gtk.Main()) so I have to make all my connections in a separate thread.
And here lies my problem. When I'm authenticating a user (verifying_credentials) I need to wait from that thread to finish to continue the execution of the main program. If I try a Thread.join GUI freezes. I've tried using queues, but queue.get is also blocking Gtk.main().
I've tried emitting a signal when my thread is finished , but the handler starts in the same thread, so when I try to modify the GUI (which I need) , program crashes (you're not supposed to touch the GUI from anywhere except main thread).
My solution ? I do busy-waiting / active-waiting , which is by definition an antipattern. I keep asking if the thread has finished and forcing cicles of Gtk.main()
There has to be another way, a more elegant / efficient way than mine.
I don't know if I can signal another thread, or there is a way of using queues without blocking the main thread. Any help will be very much appreciated.
The python code :
from os.path import abspath, dirname, join
import gettext
import threading
import math
import time
import random
import locale
from gi.repository import Gtk, Gdk, GLib, GObject
import list
APP = "UDC_Social_API"
user_list = {
"user": "password",
"admin": "admin",
"carnotan": "1234"
}
class UDC_Social_API:
def on_applicationwindow1_key_press_event(self, widget, event):
# keyname = Gdk.keyval_name(event.keyval)
# print (_("Key %(name)s (%(val)d) was pressed" )%{"name":keyname, "val":event.keyval})
if event.keyval == 65293:
self.on_login_button_clicked()
def delay(self):
if self.delay_option.get_active():
time.sleep(math.exp(random.random()*5))
else:
pass
def active_waiting(self):
while self.finished is False:
Gtk.main_iteration_do(False)
self.finished = False
self.z_handler(None)
def verify_credentials(self, user, password):
GLib.idle_add(self.active_waiting)
self.delay()
if user in user_list:
if password == user_list.get(user):
self.authentication = True
self.finished = True
else:
self.authentication = False
self.finished = True
else:
self.authentication = False
self.finished = True
def on_login_button_clicked(self, data=None):
user = self.user_entry.get_text()
password = self.password_entry.get_text()
thread = threading.Thread(target=self.verify_credentials, args=(user, password))
thread.daemon = True
thread.start()
def z_handler(self, data=None):
if self.authentication is False:
self.message_dialog.set_markup(_("User/Password incorrect\nPlease, verify login information"))
self.message_dialog.run()
self.message_dialog.hide()
return False
else:
self.window.hide()
print ("Success!")
def on_applicationwindow1_destroy(self, data=None):
Gtk.main_quit()
def on_gtk_about_activate(self, menuitem, data=None):
self.aboutdialog.run()
self.aboutdialog.hide()
def on_gtk_cut_activate(self, widget):
# Get the bounds of the selected text
bounds = self.focus.get_selection_bounds()
# if the bounds of the selection are not an empty tuple,
# put the selection in the variable chars
# and copy it to the clipboard
# (get_selection_bounds returns an empty tuple if there is no selection)
# then delete the selection
if bounds:
chars = self.focus.get_chars(*bounds)
self.clipboard.set_text(chars, -1)
self.focus.delete_text(bounds[0], bounds[1])
else:
pass
def on_gtk_copy_activate(self, widget):
# Get the bounds of the selected text
bounds = self.focus.get_selection_bounds()
# if the bounds of the selection are not an empty tuple,
# put the selection in the variable chars
# and copy it to the clipboard
# (get_selection_bounds returns an empty tuple if there is no selection)
if bounds:
chars = self.focus.get_chars(*bounds)
self.clipboard.set_text(chars, -1)
else:
pass
def on_gtk_paste_activate(self, widget):
# Get the text from the clipboard
text = self.clipboard.wait_for_text()
if text is not None:
# If there's text selected in the target
# delete it and paste the contents of the clipboard
bounds = self.focus.get_selection_bounds()
if bounds:
self.focus.delete_text(bounds[0], bounds[1])
self.focus.insert_text(text, bounds[0])
# else insert the text in the current position of the cursor in the target
else:
pos = self.focus.get_position()
self.focus.insert_text(text, pos)
else:
pass
def on_entry_focus(self, widget, event):
self.focus = widget
def create_menubar(self):
self.file_menu=self.builder.get_object("menuitem1")
self.edit_menu=self.builder.get_object("menuitem2")
self.options_menu=self.builder.get_object("option")
self.help_menu=self.builder.get_object("menuitem4")
self.languages_menu=self.builder.get_object("menuitem3")
self.delay_option = self.builder.get_object("delay_option")
self.gtk_quit_menu=self.builder.get_object("gtk_quit_menu")
self.gtk_cut_menu=self.builder.get_object("gtk_cut_menu")
self.gtk_copy_menu=self.builder.get_object("gtk_copy_menu")
self.gtk_paste_menu=self.builder.get_object("gtk_paste_menu")
self.gtk_about_menu=self.builder.get_object("gtk_about_menu")
self.galician_option=self.builder.get_object("radiomenuitem1")
self.spanish_option=self.builder.get_object("radiomenuitem2")
self.english_option=self.builder.get_object("radiomenuitem3")
def set_menubar_names(self):
self.file_menu.set_label(_("_File"))
self.edit_menu.set_label(_("_Edit"))
self.options_menu.set_label(_("_Options"))
self.help_menu.set_label(_("_Help"))
self.languages_menu.set_label(_("_Languages"))
self.delay_option.set_label(_("_Delay"))
self.gtk_quit_menu.set_label(_("Quit"))
self.gtk_copy_menu.set_label(_("Copy"))
self.gtk_cut_menu.set_label(_("Cut"))
self.gtk_paste_menu.set_label(_("Paste"))
self.gtk_about_menu.set_label(_("About"))
self.galician_option.set_label(_("_Galician"))
self.spanish_option.set_label(_("_Spanish"))
self.english_option.set_label(_("_English"))
def create_login_box(self):
self.user_entry = self.builder.get_object("user_entry")
self.password_entry = self.builder.get_object("password_entry")
self.user_label=self.builder.get_object("user_label")
self.password_label=self.builder.get_object("password_label")
self.login_button=self.builder.get_object("login_button")
def set_login_box_names(self):
self.user_entry.set_placeholder_text(_("user"))
self.password_entry.set_placeholder_text(_("password"))
self.user_label.set_label(_("User"))
self.password_label.set_label(_("Password"))
self.login_button.set_label(_("Login"))
def create_about_dialog(self):
self.aboutdialog = self.builder.get_object("aboutdialog1")
self.aboutdialog.set_transient_for(self.window)
def set_about_dialog(self):
self.aboutdialog.set_comments(_("Developed for GTK 3+ and Python 3.4"))
def reset_names(self):
self.set_menubar_names()
self.set_login_box_names()
def on_radiomenuitem1_toggled(self, widget):
if widget.get_active():
self.lang_gl_ES.install()
self.reset_names()
self.window.queue_draw()
else:
pass
def on_radiomenuitem2_toggled(self, widget):
if widget.get_active():
self.lang_es_ES.install()
self.reset_names()
self.window.queue_draw()
else:
pass
def on_radiomenuitem3_toggled(self,widget):
if widget.get_active():
self.lang_en_US.install()
self.set_menubar_names()
self.window.queue_draw()
else:
pass
def set_languages(self):
WHERE_AM_I = abspath(dirname(__file__))
locale.setlocale(locale.LC_ALL, '')
locale.bindtextdomain(APP, WHERE_AM_I)
locale_path = WHERE_AM_I +'/'
self.builder.set_translation_domain(APP)
gettext.find(APP,localedir=locale_path,languages=['gl_ES'])
gettext.find(APP,localedir=locale_path,languages=['es_ES'])
gettext.find(APP,localedir=locale_path,languages=['en_US'])
gettext.install(APP,locale_path)
gettext.textdomain(APP)
gettext.bindtextdomain(APP,locale_path)
self.lang_gl_ES=gettext.translation(APP,localedir=locale_path, languages=['gl_ES'])
self.lang_es_ES=gettext.translation(APP,localedir=locale_path, languages=['es_ES'])
self.lang_en_US=gettext.translation(APP,localedir=locale_path, languages=['en_US'])
def set_signals(self):
handlers = {
"on_applicationwindow1_destroy": self.on_applicationwindow1_destroy,
"on_gtk_about_activate": self.on_gtk_about_activate,
"on_login_button_clicked": self.on_login_button_clicked,
"on_applicationwindow1_key_press_event": self.on_applicationwindow1_key_press_event,
"on_entry_focus": self.on_entry_focus,
"on_gtk_cut_activate": self.on_gtk_cut_activate,
"on_gtk_copy_activate": self.on_gtk_copy_activate,
"on_gtk_paste_activate": self.on_gtk_paste_activate,
"on_radiomenuitem1_toggled": self.on_radiomenuitem1_toggled,
"on_radiomenuitem2_toggled": self.on_radiomenuitem2_toggled,
"on_radiomenuitem3_toggled": self.on_radiomenuitem3_toggled
}
self.builder.connect_signals(handlers)
def __init__(self):
# GObject.signal_new("z_signal", Gtk.ApplicationWindow, GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ())
self.builder = Gtk.Builder()
self.builder.add_from_file("p1.glade")
self.window = self.builder.get_object("applicationwindow1")
self.set_languages()
self.create_menubar()
self.create_login_box()
self.create_about_dialog()
self.reset_names()
self.set_signals()
self.focus = None
self.finished = False
self.authentication = False
# self.statusbar = self.builder.get_object("statusbar1")
# self.context_id = self.statusbar.get_context_id("status")
# self.status_count = 0
self.message_dialog = self.builder.get_object("messagedialog1")
self.message_dialog.set_transient_for(self.window)
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.window.show_all()
if __name__ == "__main__":
GObject.threads_init()
main = UDC_Social_API()
Gtk.main()
Glade file is in pastebin, because it will exceed post size limit.
http://pastebin.com/8S3k7f6J
Thanks in advance for any help you could provide.
You can use GLib.idle_add to schedule a callback to be executed by the event loop in the main thread of your program. This means it provides a safe way to schedule a GUI update from a background thread. So, you can just let your background thread run normally, let the main thread return control to the event loop, and then make the appropriate GUI updates from the background thread via GLib.idle_add once it's done:
def verify_credentials(self, user, password):
self.delay()
if user in user_list:
if password == user_list.get(user):
self.authentication = True
else:
self.authentication = False
else:
self.authentication = False
# Schedule z_handler to be called by the event loop in the main thread.
GLib.idle_add(z_handler, None)
def z_handler(self, data=None):
if not self.authentication:
self.message_dialog.set_markup(_("User/Password incorrect\nPlease, verify login information"))
self.message_dialog.run()
self.message_dialog.hide()
return False
else:
self.window.hide()
print ("Success!")
You're actually pretty close to using this same method, you're just doing it in an awkward way - you're scheduling active_waiting to run in the main thread, which waits until the background thread is done, and then calls z_handler. Scheduling z_handler directly, after the background thread is done with its work, is much simpler.

Using multiple threads with wxPython

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?
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
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

Categories

Resources