Prevent recursion when changing QTreeWidgetItem flags - python-3.x

I'm trying to make a very basic password manager and I've encountered an issue when trying to edit an item. When the button "Edit Password" is pressed, it makes the currently selected item editable, and I would like it to be removed once the user is done making modifications. Trying to remove the flag ItemIsEditable causes it to go into infinite recursion on the line item.setFlags(item.flags() & ~Qt.ItemIsEditable).
# app class -------------------------------------------------------------------------- #
class App(QApplication):
# initialisation ----------------------------------------------------------------- #
def __init__(self, argv):
super().__init__(argv)
self.__ready = False
self.__setup__()
self.__load_data__()
self.__ready = True
# private methods ---------------------------------------------------------------- #
def __load_data__(self):
self.data_tree.headerItem().setText(0, "Client")
self.data_tree.headerItem().setText(1, "Workstation")
self.data_tree.headerItem().setText(2, "Login")
self.data_tree.headerItem().setText(3, "Password")
for level_1, client in enumerate(self.data["clients"]):
row_1 = QTreeWidgetItem(self.data_tree)
self.data_tree.topLevelItem(level_1).setText(0, client["name"])
for level_2, workstation in enumerate(client["workstations"]):
row_2 = QTreeWidgetItem(row_1)
self.data_tree.topLevelItem(level_1).child(level_2).setText(
1, workstation["name"]
)
for level_3, login in enumerate(workstation["logins"]):
row_3 = QTreeWidgetItem(row_2)
row_3.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
self.data_tree.topLevelItem(level_1).child(level_2).child(
level_3
).setText(2, login["username"])
self.data_tree.topLevelItem(level_1).child(level_2).child(
level_3
).setText(3, login["password"])
self.data_tree.setSortingEnabled(True)
def __setup__(self):
self.data = pd.read_json("list.json")
self.main_window = QMainWindow()
self.central_widget = QWidget(self.main_window)
self.data_tree = QTreeWidget(self.central_widget)
self.edit_password_button = QPushButton(self.central_widget)
self.central_widget.setGeometry(QRect(0, 0, 500, 500))
self.central_widget.setObjectName("central_widget")
self.data_tree.itemChanged.connect(self.save_password)
self.data_tree.setGeometry(QRect(0, 0, 500, 450))
self.data_tree.setObjectName("data_tree")
self.data_tree.sortByColumn(0, Qt.SortOrder.AscendingOrder)
self.main_window.setCentralWidget(self.central_widget)
self.main_window.setGeometry(QRect(200, 200, 500, 500))
self.main_window.setWindowTitle("Password Manager")
self.main_window.show()
self.edit_password_button.clicked.connect(self.edit_password)
self.edit_password_button.setGeometry(QRect(345, 455, 150, 40))
self.edit_password_button.setText("Edit Password")
# events ------------------------------------------------------------------------- #
#pyqtSlot()
def edit_password(self):
try:
item = self.data_tree.currentItem()
item.setFlags(item.flags() | Qt.ItemIsEditable)
self.data_tree.scrollToItem(item)
self.data_tree.editItem(item, 3)
except Exception as e:
print(e)
#pyqtSlot(QTreeWidgetItem, int)
def save_password(self, item, column):
if not self.__ready:
return
for client in self.data["clients"]:
if client["name"] == item.parent().parent().text(0):
for workstation in client["workstations"]:
if workstation["name"] == item.parent().text(1):
for login in workstation["logins"]:
if login["username"] == item.text(2):
login["password"] = item.text(3)
with open("list.json", "w") as file:
json.dump(json.loads(self.data.to_json()), file, indent=4)
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
def simulate_password(self):
keyboard.write(self.data_tree.currentItem().text(3))
def test_func(self):
print("test")
app = App(sys.argv)
keyboard.add_hotkey("ctrl+insert", app.simulate_password)
sys.exit(app.exec())

The itemChanged signal is emitted for all changes to an item, not just to its text. To avoid the recursion, you can temporarily block signals whilst setting the flags, so that the save_password slot doesn't get triggered again:
#pyqtSlot()
def edit_password(self):
item = self.data_tree.currentItem()
blocked = item.treeWidget().blockSignals(True)
item.setFlags(item.flags() | Qt.ItemIsEditable)
item.treeWidget().blockSignals(blocked)
self.data_tree.scrollToItem(item)
self.data_tree.editItem(item, 3)
#pyqtSlot(QTreeWidgetItem, int)
def save_password(self, item, column):
...
blocked = item.treeWidget().blockSignals(True)
item.setFlags(item.flags() & ~Qt.ItemIsEditable)
item.treeWidget().blockSignals(blocked)

Related

Python loop does not work accurately and reliably with time.sleep

What would be the best way to run a program once a day.
time.sleep is inaccurate on long runs.
I try to get def send_stock_data (self): run once a day, for example. time.sleep drifts too much for this purpose.
The program reads the excel file and sends the data by e-mail if the column 'Amount' is 0.
class progloop():
def prog_loop (self):
progress.pack_forget()
while run_cycle == True:
get_stock_and_send_mail()
sleep(48000)
continue
def __init__(self):#adds Threading to loop
t = threading.Thread(target=self.prog_loop)
t.start()
The above class currently handles the loop.
Below is the full script.
import threading
import tkinter as tk
import os
import smtplib
import datetime
import time
import pandas as pds
import traceback
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
from tkinter import BOTTOM,HORIZONTAL, Label, Menu, PhotoImage, messagebox, filedialog, ttk
from datetime import datetime
from time import sleep
root = tk.Tk()
sec = 0
action = None
run_cycle = True
stop_cycle = True
today = datetime.now()
clock_time = today.strftime('%H:%M:%S')
date = today.strftime('%d/%m/%Y')
weekday = datetime.today().strftime('%A')
def on_run():#start send_stock_data loop and timer
global run_cycle, action
global action
string_var.set('Application state: Running')
run_cycle = True
if not action:
timer()
def on_stop():#pause send_stock_data loop and timer
global run_cycle, action
string_var.set('Application state: Paused')
run_cycle = False
messagebox.showinfo('Attention','Program is paused, press Run to resume execution')
if action:
root.after_cancel(action)
action = None
def Info():#Info btn data
messagebox.showinfo('Application info','''-The application reads the stock balance from the excel file and sends out e-mail information if the part (s)
of the warehouse balance has run out of stock within the alarm limit.\n
-The default path to the excel file is specified in the Windows environment variables as 'FilePath'\n
-In case of problems contact the author
\n\nDefault alarm limit = 0 parts in stock
\n\nDefault Google smtp server:'smtp.gmail.com'\nDefault port: 587 ''')
def write_err_log():#writes an error log with traceback
traceback_err = traceback.format_exc()
date_time = [clock_time,date]
write_log = open("ErrorLog.txt", "a")
write_log.write(str(date_time)) and write_log.write(str(traceback_err))
write_log.close()
#Gui
string_var = tk.StringVar()
string_var.set('Application state: Waiting for commands')
root.iconbitmap(r'C:\\Users\\Lauri\\Visual_studio\\Bitmap_2.ico')
root.title('Warehouse assistant')
root.geometry('460x360')
root.resizable(False,False)
background_image = PhotoImage(file = r'C:\\Users\\Lauri\\Visual_studio\\BcImage.png')
background_label = tk.Label(image=background_image)
background_label.place(x=0, y=0, relwidth=1, relheight=1)
Label_header = ttk.Label(root, font=('underline', 12),text='Parts balance monitoring application').pack()
Label_start = ttk.Label(root,font=('underline',8), text='Press Run to start the application\nPress Pause to stop the application').pack()
Label_appState = ttk.Label(root, font=('underline', 10),textvariable=string_var).pack()
btnInfo = ttk.Button(root, text = 'Info', width= 15, command = Info).pack()
Start_btn = ttk.Button(root, text = 'Run', width= 15, command=lambda:[bar(),progloop(),root.after(1000,on_run())]).pack()
Stop_btn = ttk.Button(root, text = 'Pause', width= 15, command = on_stop).pack()
exit_btn = ttk.Button(root, text = 'Exit', width= 25, command =lambda:[root.after(1000,),root.destroy()]).pack(side=BOTTOM)
menu = Menu(root)
root.config(menu=menu)
contact_num = os.environ.get('Contact_number')
contact_email = os.environ.get('Contact_email')
helpmenu = Menu(menu)
menu.add_cascade(label='Contact', menu=helpmenu)
helpmenu.add_command(label=contact_email)
helpmenu.add_command(label=contact_num)
helpmenu.add_command(label= 'Exit', command=root.destroy)
progress = ttk.Progressbar(root, orient = HORIZONTAL,length = 158, mode = 'determinate')
def bar():#progressbar
progress['value'] = 5
root.update_idletasks()
root.after(50)
progress['value'] = 43
root.update_idletasks()
root.after(300)
progress['value'] = 68
root.update_idletasks()
root.after(50)
progress['value'] = 100
root.update_idletasks()
root.after(1)
progress.pack(side=BOTTOM)
class get_stock_and_send_mail: #send inventory balance information by email
def send_stock_data(self):
count = 0
pds.set_option('display.max_colwidth', -1)
pds.set_option('colheader_justify','left')
#reads excel file and searches for desired inventory balance value
while True:
file = os.environ.get('FilePath')#default path to file specified in the Windows environment variables as 'FilePath'
try:
dataFrame = pds.read_excel(file)
break
except PermissionError as error:
write_err_log()
messagebox.showerror(title='error',message= 'Permission Error Permission error has occured during read file?\n\n\n'+str(error)+"'.",
detail=traceback.format_exc())
except FileNotFoundError as error:
messagebox.showwarning(title='Select Excel File',
message='DEFAULT FILE NOT FOUND.\nChoose Excel file from directory.\nCONTINUE --> PRESS OK')
file = filedialog.askopenfilename()#manual path to file if default path doesn't exist
try:
dataFrame = pds.read_excel(file)
break
except FileNotFoundError:
write_err_log()
continue
df_bool = []
for Amount in dataFrame.Amount:
if Amount <= 0:
df_bool.append(True)
else:
df_bool.append(False)
if True in df_bool:
while run_cycle == True:
today = datetime.now()
clock_time = today.strftime('%H:%M:%S')
date = today.strftime('%d/%m/%Y')
weekday = datetime.today().strftime('%A')
Email_adress = os.environ.get('email_user')
Email_password = os.environ.get('email.password')
empty_stock = dataFrame.loc[dataFrame.Amount <= 0]
try:
with smtplib.SMTP('smtp.gmail.com',587) as server:
server.ehlo()
server.starttls()
server.ehlo_or_helo_if_needed()
server.set_debuglevel(1)
server.login(Email_adress,Email_password)
subject = 'no-reply'
body = f"""Row no.:{empty_stock}\nPart (s) is out of stock\n
Date: {weekday} {date}\n
Time: {clock_time}
\n\n\nThis was an automatically generated message.\nDo not reply to this message!
"""
msg = f'subject:{subject}\n\n{body}'
server.sendmail(Email_adress,Email_adress, msg)
server.quit
except smtplib.SMTPException as error:
write_err_log()
count +=1
if count <= 5:
continue
elif count >= 6:
messagebox.showerror(title='error',message= 'ERROR'+str(error)+"'.",
detail=traceback.format_exc())
break
def __init__(self):#adds Threading to loop
t = threading.Thread(target=self.send_stock_data)
t.start()
class progloop():
def prog_loop (self):
progress.pack_forget()
while run_cycle == True:
get_stock_and_send_mail()
sleep(48000)
continue
def __init__(self):#adds Threading to loop
t = threading.Thread(target=self.prog_loop)
t.start()
def timer(): #Runtime timer
global sec
if run_cycle == False:
return
sec += 1
time['text'] = f'Runtime: {sec} sec'
time.after(1000, timer)
time = Label(root, fg='black')
time.pack(side=BOTTOM)
root.mainloop()
If you really want to actually use a sleep, and time shifting/drifting over multiple days is the problem, what you can do, although not perfectly accurate, is to readjust the sleep time based on current time. Something like :
def seconds_since_midnight():
now = datetime.now()
return (now - now.replace(hour=0, minute=0, second=0, microsecond=0)).total_seconds()
def one_day_to(day_time_secs):
return 86400+day_time_secs-seconds_since_midnight()
And using for instance
sleep(one_day_to(3600*18))
to schedule at about 18h00 the next day, without drifting from day to day

PySide2: How to re-implement QFormLayout.takeRow()?

I've noticed that QFormLayout in Pyside2 does not have the takeRow method like its PyQt5 counterpart. I've attempted to subclass QFormLayout to incorporate a similar method, but I've run into Runtime Errors, as the removal behavor of the LabelRole item is different than the FieldRole item. Another issue being that the LabelRole item does not actually get taken off the row even when the row itself is removed.
The following is the test sample I've been working with using Python 3.8.6:
from PySide2.QtWidgets import *
import sys
class MyFormLayout(QFormLayout):
def __init__(self, *args, **kwargs):
super(MyFormLayout, self).__init__(*args, **kwargs)
self.cache = []
print(f"Formlayout's identity: {self=}\nwith parent {self.parent()=}")
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row) ## <-- This seems necessary to make the rowCount() decrement. Alternative?
label_item.widget().setParent(None) ## <-- Runtime Error Here?
field_item.layout().setParent(None)
self.cache.append(label_item.widget(), field_item)
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label_item, field_item
def restoreRow(self, insert_idx: int):
print(f"Called {self.restoreRow.__name__}")
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
to_insert = self.cache.pop()
self.insertRow(insert_idx, to_insert[0], to_insert[1])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
class MyWindow(QWidget):
def __init__(self):
super(MyWindow, self).__init__()
self.mainlay = MyFormLayout(self)
self.cmb = QComboBox()
self.cmb.addItems(["Placeholder", "Remove 1 and 2"])
self.cmb.currentTextChanged.connect(self.remove_rows_via_combo)
self.current_text = self.cmb.currentText()
self.hlay1, self.le1, self.btn1 = self.le_and_btn(placeholderText="1")
self.hlay2, self.le2, self.btn2 = self.le_and_btn(placeholderText="2")
self.hlay3, self.le3, self.btn3 = self.le_and_btn(placeholderText="3")
self.hlay4, self.le4, self.btn4 = self.le_and_btn(placeholderText="4")
self.remove_btn = QPushButton("Remove", clicked=self.remove_row_via_click)
self.restore_btn = QPushButton("Restore", clicked=self.restore_a_row_via_click)
self.mainlay.addRow("Combobox", self.cmb)
for ii, hlayout in zip(range(1, 5), [self.hlay1, self.hlay2, self.hlay3, self.hlay4]):
self.mainlay.addRow(f"Row {ii}", hlayout)
self.mainlay.addRow(self.remove_btn)
self.mainlay.addRow(self.restore_btn)
#staticmethod
def le_and_btn(**kwargs):
hlay, le, btn = QHBoxLayout(), QLineEdit(**kwargs), QPushButton()
hlay.addWidget(le)
hlay.addWidget(btn)
return hlay, le, btn
def remove_row_via_click(self):
self.mainlay.takeRow(1)
def restore_a_row_via_click(self):
self.mainlay.restoreRow(1)
def remove_rows_via_combo(self, text):
print(f"{self.remove_rows_via_combo.__name__} received the text: {text}")
if text == "Remove 1 and 2":
self.mainlay.takeRow(1)
self.mainlay.takeRow(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec_())
I would like to understand why the behavior of the role items is different and how the method may be properly re-implemented.
The problem is that the label was created internally by Qt from a string, rather than by explicitly creating a QLabel in Python. This means that when the row is removed, the last remaining reference is also removed, which deletes the label on the C++ side. After that, all that's left on the Python side is an empty PyQt wrapper - so when you try to call setParent on it, a RuntimeError will be raised, because the underlying C++ part no longer exists.
Your example can therefore be fixed by getting python references to the label/field objects before the layout-item is removed:
class MyFormLayout(QFormLayout):
...
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
# get refs before removal
label = label_item.widget()
field = field_item.layout() or field_item.widget()
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row)
label.setParent(None)
field.setParent(None)
self.cache.append((label, field))
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label, field

How do I make sure all GUI input variables can be accessed by certain classes and function calls?

When I provide an address /and or location to the entry bar and I press the "Get forecast" button the script fails at line 22. I think the error is raised because the str(address.get()) cant find the address variable, probably because it doesn't technically exist during that point of run time (I'm not able to log the error due to the structure of that function).
My question is; How do I make sure that my "get_hourly_forecast" function is able to access the address entry variable?
I have tried instantiating the address variable in various locations, e.g in the MainWeatherHub class, as well as in the MyWeatherApp class and then passing it as an argument to the MainWeatherHub in line 79, neither variation has worked. The current code shows the former variation.
import urllib, json, requests
from tkinter import *
from tkinter import ttk
def get_hourly_forecast(*args):
## params *args:
#A location argument
#Returns:
# A list of temps in Farenheit for the next 156 hours
API_KEY = 'removing my API key for security purposes'
try:
print('here') # The code makes it to here
curr_address = str(address.get()) # Code seems to fail here (not sure how to have the error print)
print('here')
geocode_url = "https://maps.googleapis.com/maps/api/geocode/json?address={}&key={}".format(cur_address, API_KEY)
response = requests.get(geocode_url)
response_dict = response.json()['results']
location = response_dict[0]['geometry']['location']
lat = location['lat']
lng = location['lng']
local_url_request = 'https://api.weather.gov/points/lat={}lng={}'.format(lat, lng)
response_one = requests.get(local_url_request)
json_dict_one = response_one.json()
local_props = json_dict_one['properties']
local_forecast_request = local_props['forecastHourly']
resposne_two = requests.get(local_forecast_request)
json_dict_two = resposne_two.json()
local_forecast_properites = json_dict_two['properties']
hourly_updates = local_forecast_properites['periods']
out = []
for i in hourly_updates:
for key, value in i.items():
if key == "temperature":
out.append(value)
current_weather.set(out[0])
except:
print("Not working.")
#############################################################
class MyWeatherApp:
"""
MyWeatherApp is the primary Frame for this GUI application
"""
def __init__(self, master):
super(MyWeatherApp, self).__init__()
self.master = master
# Create the main window Frame
master_style = ttk.Style()
master_style.configure('Master.TFrame')
self.master.title("My Weather")
self.master.geometry("500x500")
MWA = ttk.Frame(self.master, style='Master.TFrame')
MWA.place(relheight=1.0, relwidth=1.0)
# Run other widgets within this class
MainWeatherHub(MWA)
#############################################################
class MainWeatherHub(MyWeatherApp):
"""
The MainWeatherHub (MWH) is the top panel of the app
"""
def __init__(self, mainwindow):
super(MyWeatherApp, self).__init__()
self.mainwindow = mainwindow
# Create a Frame for the MainWeatherHub
MWH_style = ttk.Style()
MWH_style.configure('MWH.TFrame')
MWH = ttk.Frame(self.mainwindow, style='MWH.TFrame', relief='sunken')
MWH.place(relheight=0.33, relwidth=0.95, relx=0.025, rely=0.025)
# Create an entry widget to take a location
# and store that as a loction variable.
address = StringVar()
loc_entry = ttk.Entry(MWH, textvariable=address)
loc_entry.place(relheight=0.30, relwidth=.95, relx=0.025, rely=0.05)
# Get weather button finds weather for the users location
current_weather = StringVar()
get_weather_button = ttk.Button(loc_entry, text="Get Forecast", command=get_hourly_forecast)
get_weather_button.place(relheight=0.85,relwidth=0.2, relx=0.79, rely=0.075)
#Display weather in the Message widget
weath_display = Message(MWH, textvariable=current_weather)
weath_display.place(relwidth=0.95, relheight=0.55, relx=0.025, rely=0.375)
root = Tk()
my_gui = MyWeatherApp(root)
root.mainloop()
If this script works properly, it should return the current temperature in degrees Fahrenheit of the location that was provided in the entry bar.
You should send it as parameter
def get_hourly_forecast(cur_address):
geocode_url = "...".format(cur_address, API_KEY)
And then assing to button function which runs get_hourly_forecast with string
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
self.address = StringVar() # use self.
ttk.Button(loc_entry, text="Get Forecast", command=run_it)
def run_it(self):
get_hourly_forecast(self.address.get())
or using lambda
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
ttk.Button(loc_entry, text="Get Forecast", command=lambda:get_hourly_forecast(address.get()))
EDIT:
I see you use current_weather (StringVar from MainWeatherHub) in get_hourly_forecast to set value current_weather.set(out[0]).
You could send current_weather to get_hourly_forecast as parameter
def get_hourly_forecast(cur_address, current_weather):
geocode_url = "...".format(cur_address, API_KEY)
current_weather.set(out[0])
and
class MainWeatherHub(MyWeatherApp):
def __init__(self, mainwindow):
self.address = StringVar() # use self.
self.current_weather = StringVar() # use self.
ttk.Button(loc_entry, text="Get Forecast", command=run_it)
def run_it(self):
get_hourly_forecast(self.address.get(), self.current_weather)
but it could be better to return value from get_hourly_forecast
def get_hourly_forecast(cur_address):
geocode_url = "...".format(cur_address, API_KEY)
return out[0]
and get it in run_it
def run_it(self):
result = get_hourly_forecast(self.address.get())
if result is not None:
self.current_weather.set(result)
This way get_hourly_forecast doesn't work with StringVar and you can use it in other program which doesn't use StringVar.

big raw video file seeking in python gstreamer

I'm working on program, that should display big raw video, seek in it that I'd be able to set from and to times cuts in it, set black borders sizes to hide shaked borders of image. The crucial part of this project is seeking. I've tried 5min file cutoff and when I seek at the start of the video, it's OK but after middle something goes wrong. Since it seems there is no much examples and documentation I'm using this:
self.pipe.seek_simple(
Gst.Format.TIME,
Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
destSeek
)
My actual (non-mature) code:
import sys
import gi
gi.require_version('Gst', '1.0')
gi.require_version('Gtk', '3.0')
gi.require_version('GdkX11', '3.0')
gi.require_version('GstVideo', '1.0')
from gi.repository import GObject, Gst, Gtk, GdkX11, GstVideo, Gdk
GObject.threads_init()
Gst.init(None)
testGrab = "testRAW.mkv"
class VideoDec(Gst.Bin):
def __init__(self):
super().__init__()
# elements
q1 = Gst.ElementFactory.make('queue', None)
videoparse = Gst.ElementFactory.make('videoparse', None)
q2 = Gst.ElementFactory.make('queue', None)
self.add(q1)
self.add(videoparse)
self.add(q2)
videoparse.set_property('width', 720)
videoparse.set_property('height', 576)
videoparse.set_property('format', 4)
# link
q1.link(videoparse)
videoparse.link(q2)
# Add Ghost Pads
self.add_pad(
Gst.GhostPad.new('sink', q1.get_static_pad('sink'))
)
self.add_pad(
Gst.GhostPad.new('src', q2.get_static_pad('src'))
)
class AudioDec(Gst.Bin):
def __init__(self):
super().__init__()
# elements
q1 = Gst.ElementFactory.make('queue', None)
audioparse = Gst.ElementFactory.make('audioparse', None)
q2 = Gst.ElementFactory.make('queue', None)
#sink = Gst.ElementFactory.make('autoaudiosink', None)
self.add(q1)
self.add(audioparse)
self.add(q2)
#self.add(sink)
# link
q1.link(audioparse)
audioparse.link(q2)
#audioparse.link(sink)
# Add Ghost Pads
self.add_pad(
Gst.GhostPad.new('sink', q1.get_static_pad('sink'))
)
self.add_pad(
Gst.GhostPad.new('src', q2.get_static_pad('src'))
)
class Player(object):
def __init__(self):
self.fps = 25
self.window = Gtk.Window()
self.window.connect('destroy', self.quit)
self.window.set_default_size(800, 600)
self.drawingarea = Gtk.DrawingArea()
#hbox
self.hbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.window.add(self.hbox)
Gtk.Box.pack_start(self.hbox, self.drawingarea, True, True, 0)
self.setPipeline()
self.setGUI()
self.setShortcuts()
self.playing = False
def setPipeline(self):
self.pipe = Gst.Pipeline.new('player')
# Create bus to get events from GStreamer pipeline
self.bus = self.pipe.get_bus()
self.bus.add_signal_watch()
self.bus.connect('message::eos', self.on_eos)
self.bus.connect('message::error', self.on_error)
# This is needed to make the video output in our DrawingArea:
self.bus.enable_sync_message_emission()
self.bus.connect('sync-message::element', self.on_sync_message)
self.src = Gst.ElementFactory.make('filesrc', None)
self.src.set_property("location", testGrab)
self.dec = Gst.ElementFactory.make('decodebin', None)
self.video = VideoDec()
self.audio = AudioDec()
self.glimagesink = Gst.ElementFactory.make('glimagesink', None)
self.audiosink = Gst.ElementFactory.make('autoaudiosink', None)
self.pipe.add(self.src)
self.pipe.add(self.dec)
self.pipe.add(self.video)
self.pipe.add(self.audio)
self.pipe.add(self.glimagesink)
self.pipe.add(self.audiosink)
#self.pipe.add(self.autovideosink)
# Connect signal handlers
self.dec.connect('pad-added', self.on_pad_added)
# link
self.src.link(self.dec)
self.video.link(self.glimagesink)
self.audio.link(self.audiosink)
def on_pad_added(self, element, pad):
string = pad.query_caps(None).to_string()
print('on_pad_added():', string)
if string.startswith('audio/'):
pad.link(self.audio.get_static_pad('sink'))
elif string.startswith('video/'):
pad.link(self.video.get_static_pad('sink'))
def setGUI(self):
vbox = Gtk.Box(Gtk.Orientation.HORIZONTAL, 0)
vbox.set_margin_top(3)
vbox.set_margin_bottom(3)
Gtk.Box.pack_start(self.hbox, vbox, False, False, 0)
self.playButtonImage = Gtk.Image()
self.playButtonImage.set_from_stock("gtk-media-play", Gtk.IconSize.BUTTON)
self.playButton = Gtk.Button.new()
self.playButton.add(self.playButtonImage)
self.playButton.connect("clicked", self.playToggled)
Gtk.Box.pack_start(vbox, self.playButton, False, False, 0)
self.slider = Gtk.HScale()
self.slider.set_margin_left(6)
self.slider.set_margin_right(6)
self.slider.set_draw_value(False)
self.slider.set_range(0, 100)
self.slider.set_increments(1, 10)
Gtk.Box.pack_start(vbox, self.slider, True, True, 0)
self.label = Gtk.Label(label='0:00')
self.label.set_margin_left(6)
self.label.set_margin_right(6)
Gtk.Box.pack_start(vbox, self.label, False, False, 0)
def setShortcuts(self):
accel = Gtk.AccelGroup()
accel.connect(Gdk.KEY_space, Gdk.ModifierType.CONTROL_MASK, 0, self.playToggled)
accel.connect(Gdk.KEY_Right, Gdk.ModifierType.CONTROL_MASK, 0, self.seekFW0)
accel.connect(Gdk.KEY_Right, Gdk.ModifierType.CONTROL_MASK|Gdk.ModifierType.SHIFT_MASK, 0, self.seekFW10s)
accel.connect(Gdk.KEY_Right, Gdk.ModifierType.SHIFT_MASK, 0, self.seekFW2)
accel.connect(Gdk.KEY_Right, Gdk.ModifierType.MOD1_MASK, 0, self.seekFW10) # alt key
self.window.add_accel_group(accel)
def seekFW0(self, *args):
self.seekTime = 2 * Gst.SECOND // self.fps
self.seekFW()
def seekFW10s(self, *args):
self.seekTime = Gst.SECOND * 10
self.seekFW()
def seekFW2(self, *args):
self.seekTime = Gst.SECOND * 60 * 2
self.seekFW()
def seekFW10(self, *args):
self.seekTime = Gst.SECOND * 60 * 10
self.seekFW()
def seekFW(self, *args):
nanosecs = self.pipe.query_position(Gst.Format.TIME)[1]
destSeek = nanosecs + self.seekTime
self.pipe.seek_simple(
Gst.Format.TIME,
Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
destSeek
)
def play(self):
self.pipe.set_state(Gst.State.PLAYING)
GObject.timeout_add(1000, self.updateSlider)
def stop(self):
self.pipe.set_state(Gst.State.PAUSED)
def playToggled(self, *w):
if(self.playing == False):
self.play()
else:
self.stop()
self.playing=not(self.playing)
self.updateButtons()
def updateSlider(self):
try:
nanosecs = self.pipe.query_position(Gst.Format.TIME)[1]
duration_nanosecs = self.pipe.query_duration(Gst.Format.TIME)[1]
# block seek handler so we don't seek when we set_value()
# self.slider.handler_block_by_func(self.on_slider_change)
duration = float(duration_nanosecs) / Gst.SECOND
position = float(nanosecs) / Gst.SECOND
self.slider.set_range(0, duration)
self.slider.set_value(position)
self.label.set_text ("%d" % (position / 60) + ":%02d" % (position % 60))
#self.slider.handler_unblock_by_func(self.on_slider_change)
except Exception as e:
# pipeline must not be ready and does not know position
print(e)
pass
return True
def updateButtons(self):
if(self.playing == False):
self.playButtonImage.set_from_stock("gtk-media-play", Gtk.IconSize.BUTTON)
else:
self.playButtonImage.set_from_stock("gtk-media-pause", Gtk.IconSize.BUTTON)
def run(self):
self.window.show_all()
# You need to get the XID after window.show_all(). You shouldn't get it
# in the on_sync_message() handler because threading issues will cause
# segfaults there.
self.xid = self.drawingarea.get_property('window').get_xid()
#self.pipeline.set_state(Gst.State.PLAYING)
Gtk.main()
def quit(self, window):
self.pipe.set_state(Gst.State.NULL)
Gtk.main_quit()
def on_sync_message(self, bus, msg):
if msg.get_structure().get_name() == 'prepare-window-handle':
print('prepare-window-handle')
msg.src.set_window_handle(self.xid)
def on_eos(self, bus, msg):
#print('on_eos(): seeking to start of video')
print('on_eos(): pausing video')
self.stop()
#self.pipeline.seek_simple(
# Gst.Format.TIME,
# Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
# 0
#)
#self.playing = False
#self.slider.set_value(0)
#self.label.set_text("0:00")
#self.updateButtons()
def on_error(self, bus, msg):
print('on_error():', msg.parse_error())
p = Player()
p.run()
If somebody could share wisdom or documentation/example links I'd be happy.
I no longer remember quite why I gave up using seek_simple but I do recall that it caused me no end of grief.
Use instead a standard seek:
Gplayer.seek(self.rate, Gst.Format.TIME,
(Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE),
Gst.SeekType.SET, seek_time , Gst.SeekType.NONE, -1)
Gstreamer1.0 seek
Late Edit:
I believe the issue was getting accurate and consistent timestamps, when I was using the scaletempo element. The scaletempo element caters for adjusting the speed at which playback occurs and I was getting inconsistent times from the player if I varied the rate of play. I moved from seek_simple to seek to resolve the issue.
Note: self.rate above is a user defined variable, which for normal playback would be 1.00, dropping below 1.00 for slower playback and above 1.00 for faster playback.

How to implement a thread in a wxPython GUI application

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])
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.
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.
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.
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.

Resources