I am building an API that I would like to use in applications using pyqt and other guis.
If a programmer is using a gui other that Qt I don't want her to have to import pyqt etc.
My problem is that I use a task to subscribe to messages and pass them through to the main task.
I accomplish this by passing a parameter (qt) which is either True or False depending on what gui we are implementing.
I then create a class that generates a SubscriberParent class of either a QtCore.QThread or a Thread. The ultimate SubscriberThread
is of type SubscriberThreadParent.
The main thread either polls the interTaskQueue or uses Qt's signals and slots to process the message.
class Gui(object):
def __init__(self, qt):
if qt:
from PyQt5 import QtCore
from PyQt5.QtCore import Qt, pyqtSignal
class SubScriberThreadParent(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
else:
from threading import Thread
class SubScriberThreadParent(Thread):
def __init__(self):
Thread.__init__(self)
class SubscriberThread(Gui.SubScriberThreadParent):
def __init__(self, qt, dsParam, SubScriberThreadParent):
if qt:
from PyQt5 import QtCore
from PyQt5.QtCore import Qt, pyqtSignal
pubIn = pyqtSignal(str, str)
SubScriberThreadParent.__init__(self)
self.interTaskQueue = dsParam.interTaskQueue
.
.
.
.
#
# Pass the message on to the main application
#
def alertMainApp(self, bodyTuple):
if self.qt:
btz = '{0}'.format(bodyTuple)
self.pubIn.emit(btz)
LOGGER.info("Emitted Alert ")
else:
if self.interTaskQueue != None:
self.interTaskQueue.put(bodyTuple) # Pass it to the main thread
LOGGER.info("Queued Alert.")
else:
LOGGER.error("No Inter-task data transfer method available to the subscriber task!")
The error I get with this approach is: "AttributeError: type object 'Gui' has no attribute 'SubScriberThreadParent'"
How can I make this work?
I also would like to know the scope of the conditional imports.
In order for one class to inherit from Gui.SubScriberThreadParent you will have to have created an instance of Gui and assigned something to a class variable.
Consider this code:
class Gui:
def __init__(self, qt):
if qt:
class Foo:
att = 'Foo'
Gui.Parent = Foo
else:
class Bar:
att = 'Bar'
Gui.Parent = Bar
def makeThread():
class Thread(Gui.Parent):
def __init__(self):
self.att = Gui.Parent.att
return Thread()
def main():
Gui(False)
t = makeThread()
print(t.att)
main()
Output:
With Gui(False) as above, the output is:
Bar
When changed to Gui(True), the output is:
Foo
Related
I am using PYQT5 to build a GUI and I am using APScheduler to manage the jobs I would like to run. I have the scheduler items and timer items broken into there own classes and then connecting them in the main file.
The issue I am having is once the timer finishes one cycle, I try to add time to the Timer class and start it over for the next count down before the scheduler is supposed to run again. I am getting two errors or warnings and I am not sure how to fix them. They are:
QObject::killTimer: Timers cannot be stopped from another thread
QObject::startTimer: Timers cannot be started from another thread
Once these are thrown the GUI updates but no longer counts down. I will attach the simplest version that I have found that will reproduce the errors. Any help is greatly appreciated and thank you for your time.
Main.py
import sys
from PyQt5 import QtCore
from PyQt5 import QtWidgets
from Timer import Timer
from Schedule import Scheduler
import datetime
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
vbox = QtWidgets.QVBoxLayout()
central_widget.setLayout(vbox)
self.start_pushButton = QtWidgets.QPushButton()
self.start_pushButton.setText("Start")
self.start_pushButton.clicked.connect(self.start_schedule)
vbox.addWidget(self.start_pushButton)
self.pages_qsw = QtWidgets.QStackedWidget()
vbox.addWidget(self.pages_qsw)
self.time_passed_qll = QtWidgets.QLabel()
vbox.addWidget(self.time_passed_qll)
self.my_timer = Timer()
self.my_timer.get_seconds.connect(self.update_gui)
self.sch = Scheduler()
def start_schedule(self):
self.sch.add(self.hello)
self.sch.start()
self.start_my_timer()
def start_my_timer(self):
next_run = self.sch.next_occurance().replace(tzinfo=None) # This removes the time zone.
a = datetime.datetime.now()
difference = next_run - a
self.my_timer.addSecs(difference.seconds)
self.my_timer.timer_start()
def hello(self):
print("hello world")
self.start_my_timer()
#QtCore.pyqtSlot(str)
def update_gui(self,seconds):
self.time_passed_qll.setText(str(seconds))
app = QtWidgets.QApplication(sys.argv)
main_window = MyMainWindow()
main_window.show()
sys.exit(app.exec_())
Timer.py
from PyQt5.QtCore import QTimer, pyqtSignal
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import datetime
class Timer(QTimer):
get_seconds = pyqtSignal(str)
def __init__(self):
super().__init__()
self.time_left = 1
self.timeout.connect(self.timer_timeout)
def addSecs(self, secs):
self.time_left += secs
def timer_start(self):
self.start(1000)
self.update_gui()
def timer_timeout(self):
self.time_left -= 1
if self.time_left <= 0:
self.stop()
self.update_gui()
def update_gui(self):
self.get_seconds.emit(str(self.time_left))
Schedule.py
from datetime import datetime
from apscheduler.schedulers.qt import QtScheduler
class Scheduler():
def __init__(self):
self.id = 'test_job'
self.sched = QtScheduler()
def add(self,job_function):
self.sched.add_job(job_function, 'cron', day_of_week='mon-fri',hour ='9-18',minute = '2,7,12,17,22,27,32,37,42,47,52,57',second = '5', id=self.id)
def start(self):
self.sched.start()
def next_occurance(self):
for job in self.sched.get_jobs():
if job.id == self.id:
return job.next_run_time
As the error explains, you cannot start and stop a QTimer from another thread, and since APScheduler works on different threads that the reason of your issue: self.hello is called from the APScheduler thread, not the thread from which your Timer is created.
To access objects created in different threads, you need to use signals and slots in order to let Qt manage communications between different threads.
So, the solution could be to subclass your Scheduler by inheriting from QObject (in order to be able to create signals and connect to them), then use a custom signal each time a job is executed and use that signal to restart the timer.
To achieve that, I use a createJob function which actually runs the job and emits a started signal when the job is started, and a completed when completed.
Unfortunately I cannot test the following code as I'm unable to install APScheduler right now, but the logic should be fine.
Main.py
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.sch = Scheduler()
self.sch.completed.connect(self.start_my_timer)
def hello(self):
print("hello world")
# no call to self.start_my_timer here!
Schedule.py
from datetime import datetime
from apscheduler.schedulers.qt import QtScheduler
from PyQt5 import QtCore
class Scheduler(QtCore.QObject):
started = QtCore.pyqtSignal(object)
completed = QtCore.pyqtSignal(object)
def __init__(self):
self.id = 'test_job'
self.sched = QtScheduler()
def add(self, job_function, *args, **kwargs):
self.sched.add_job(self.createJob(job_function), 'cron',
day_of_week='mon-fri', hour='9-18',
minute='2,7,12,17,22,27,32,37,42,47,52,57',
second='5', id=self.id, *args, **kwargs)
def createJob(self, job_function):
def func(*args, **kwargs):
self.started.emit(job_function)
job_function(*args, **kwargs)
self.completed.emit(job_function)
return func
def start(self):
self.sched.start()
def next_occurance(self):
for job in self.sched.get_jobs():
if job.id == self.id:
return job.next_run_time
Note that I'm emitting the started and completed signals with the job_function argument (which is a reference to the job, self.hello in your case), which might be useful to recognize the job in case you want to react differently with multiple jobs. I also added basic support for positional and keyword arguments.
Also note that I'm only giving a very basic implementation (your function only prints a message). If you need to interact to UI elements in the job function, the same problem with QTimer will rise, as no access to UI elements is allowed from threads outside the main Qt thread.
In that case you'll need to find another way. For example, you could add a job (that is not actually run from the scheduler) and emit a signal with that job as argument, and connect to a function that will actually run that job in the main thread.
Main.py
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
# ...
self.sch = Scheduler()
self.sch.startJob.connect(self.startJob)
def startJob(self, job, args, kwargs):
job(*args, **kwargs)
self.start_my_timer()
def hello(self):
self.someLabel.setText("hello world")
Schedule.py
from datetime import datetime
from apscheduler.schedulers.qt import QtScheduler
from PyQt5 import QtCore
class Scheduler(QtCore.QObject):
startJob = QtCore.pyqtSignal(object, object, object)
# ...
def add(self, job_function, *args, **kwargs):
self.sched.add_job(self.createJob(job_function, args, kwargs), 'cron',
day_of_week='mon-fri', hour='9-18',
minute='2,7,12,17,22,27,32,37,42,47,52,57',
second='5', id=self.id)
def createJob(self, job_function, args, kwargs):
def func():
self.starJob.emit(job_function, args, kwargs)
return func
As said, the code above is untested, you'll need to check for possible bugs (maybe I made some mistake with the wildcard arguments).
Finally, some small suggestions:
There are very few and specific cases for which using pyqtSlot decorator is actually necessary; interestingly enough, using them is often source of problems or unexpected behavior.
It's usually better to leave signal arguments as they are without any conversion, so you shouldn't convert the time to a string for the get_seconds signal; also, QLabel can accept numeric values using setNum() (both for float and int numbers).
Be more careful with spacings (I'm referring to self.sched.add_job): for keyword arguments, spaces should only exist after commas (read more on the Style Guide for Python Code); while it actually doesn't represent an issue, it greatly improves readability.
Well I was scripting a software designed via Python which I'll be using signals and slots too much in an PyQt5 application. I got an idea of creating a dictionary where all signals come in and each signal will have its own key in order to access (or basically to connect it to a function). The problem is that I get this error 'AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect' for some reason. I read about this error and found out that I have to declare the signals outside the constructor to get it working but unfortunately that will break my idea so that, I came here so somebody can solve my issue.
Here is the code if you still don't understand:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QRunnable, pyqtSlot, QThreadPool, QObject, pyqtSignal
class WorkerSignals(QObject):
signals = {}
def __init__(self, **kwargs):
QObject.__init__(self)
if (kwargs is not None):
for key, value in kwargs.items():
self.signals[key] = value
class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
#pyqtSlot()
def run(self):
self.fn(*self.args, **self.kwargs)
And an example of creating signals:
worker_signals = WorkerSignals(result=pyqtSignal(str), error=pyqtSignal(str))
worker_signals.signals['result'].connect(self.on_receive_result)
worker_signals.signals['error'].connect(self.on_receive_error)
As indicated in the docs:
A signal (specifically an unbound signal) is a class attribute. When a
signal is referenced as an attribute of an instance of the class then
PyQt5 automatically binds the instance to the signal in order to
create a bound signal. [...]
So it is not only necessary that is it declared outside the constructor but it must be a static attribute since it serves as a prototype to create the signals that belong to the instance. A possible solution is to use type to create dynamic classes:
from PyQt5 import QtCore
d = {
"result": QtCore.pyqtSignal(str),
"error": QtCore.pyqtSignal(str)
}
WorkerSignals = type("WorkerSignals", (QtCore.QObject,), d)
if __name__ == '__main__':
import sys
app = QtCore.QCoreApplication(sys.argv)
worker_signals = WorkerSignals()
def on_result(text):
print("result:", text)
def on_error(text):
print("error:", text)
worker_signals.result.connect(on_result)
worker_signals.error.connect(on_error)
def emit_result():
worker_signals.result.emit(" 1+1=2 ")
def emit_error():
worker_signals.error.emit(" :( ")
QtCore.QTimer.singleShot(1000, emit_result)
QtCore.QTimer.singleShot(2000, emit_error)
QtCore.QTimer.singleShot(3000, app.quit)
sys.exit(app.exec_())
How do i pass variables from one python module(1st module) to another(2nd module) which contains a main module and two Qdialog which appears when the buttons are pressed in the 2nd module
How can i send these values from 1st module to the Qdialog in the 2nd module
I am dealing with multiple modules and data needs to updated here and there and totally confused with Inheritance/ Creating instances of classes and Modules
# mainwindow.py(1st module)
from 2nd_module import window2, dialog1, dialog2
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.win2 =window2()
self.dia1 =dialog1()
if 1:
self.win2.bar()
# pass data
self.dia1.some_function(data)
.
# 2nd_module.py
from mainwindow import mainwindow
class dialog1 (QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
def update(self):
w = window2()
#do something
w.self.ma.foo()
.
.
class dialog2 (QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
.
.
class window2(QtGui.QMainWindow):
def __init__(self):
super(window2, self).__init__()
self.update_request = 0 # initilizing the variable here
self.ma = mainwindow()
self.connect(dialog1_bttn, Qt.SIGNAL("clicked()"), self.open_dialog1 )
def open_dialog1(self): #this is how i create the instance of dialog box
self.open1 =dialog1()
self.open1.show
def foo(self): # this updates the request value from 2 dialog boxes
#do something
self.update_request = 1
def bar(self): # this is called from 1st module to check if there are any update requests
if self.update_request ==1:
#do something
My struggles
so here when i call foo() from dialog1 it works fine and updates the value to 1. But now when i call bar() from 1st module the value self.update_request turns to be 0 all the time
my mainwindow(1st module) runs all the background tasks like exchanging data from serial port,etc. and i need to send that data to the dialog1 in 2nd_module to update the values
Can anyone please advise me on how to create proper instances of Qt Dialogs, windows and to pass data/variables/list from one class to another ..thanks in advance
Following is the sample code structure I am intending to implementing a larger time consuming operation. For doing larger operation, I have used QThread and updating progressbar (from main class) using the emited signal. All works fine un till large time consuming operation is completed. However, I run in to problem when I call a function from main GUI class. Here is the code structure I am trying (read the comments):-
import time
from scripts.gui import Ui_Dialog
from PyQt4 import QtGui
from PyQt4 import QtCore
class AppGui(QtGui.QDialog, Ui_Dialog):
def __init__(self):
QtGui.QDialog.__init__(self)
# Main code here.
# This GUI pops up for user input and opens a main GUI.
def main_function(self):
# Doing some main coding here.
self.work_thread = WorkThread()
self.work_thread.update.connect(self.ui.progressBar.setValue)
self.work_thread.start()
# I wanted to continue more coding here after the thread is finished. But self.work_thread.wait() is blocking main gui.
# Therefore, I moved the continuation code to different function --> sub_function()
def sub_function(self):
# Do the remaining code left over from the main_function()
class WorkThread(QtCore.QThread):
update = QtCore.pyqtSignal(int)
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
self.thread = GenericThread(scripts.function, arg1, arg2) # This "scripts.function()" function does long process.
self.thread.start()
while self.thread.isRunning():
# Do some long process.
time.sleep(1)
self.update.emit(signal)
print "Distro extraction completed..."
if self.thread.isFinished():
self.main_class = AppGui()
self.main_class.sub_function() # <-- Problematic call to main AppGui function.
if self.isFinished():
return
class GenericThread(QtCore.QThread):
def __init__(self, function, *args, **kwargs):
QtCore.QThread.__init__(self)
self.function = function
self.args = args
self.kwargs = kwargs
def __del__(self):
self.wait()
def run(self):
self.function(*self.args, **self.kwargs)
return
This is what I got after running.
What I believe is that I am wrongly calling function of main AppGui() from WorkThread() class.
QPixmap: It is not safe to use pixmaps outside the GUI thread
Larger operation is complete...
QObject::installEventFilter(): Cannot filter events for objects in a different thread.
[xcb] Unknown request in queue while dequeuing
[xcb] Most likely this is a multi-threaded client and XInitThreads has not been called
[xcb] Aborting, sorry about that.
python2.7: ../../src/xcb_io.c:179: dequeue_pending_request: Assertion `!xcb_xlib_unknown_req_in_deq' failed.
Any help to solve this issue is appreciated.
The reason is that worker thread emit a signal, this signal could not directly bind to an UI slot, but you need to bind it to a general slot, then you call the UI slot to upgrade. As I don't have all your code, so I write a similar file like this, it works fine
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import time
class WorkerThread(QThread):
updateSignal = pyqtSignal(int)
def run(self):
count = 0
while True:
time.sleep(0.1)
self.updateSignal.emit(count)
count += 1
class ProgressBar(QProgressBar):
def __init__(self, parent=None):
super(ProgressBar, self).__init__(parent)
self.worker = WorkerThread()
self.worker.updateSignal.connect(self.updateProgress) # here should bind to a general slot
def startWorker(self):
self.worker.start()
def updateProgress(self, progress):
self.setValue(progress)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
p = ProgressBar()
p.startWorker()
p.show()
app.exec_()
I'm having troubles using PyQt4 slots/signals.
I'm using PyLIRC and I'm listening for button presses on a remote. This part I have gotten to work outside of Qt. My problem comes when emitting the signal from the button listening thread and attempting to call a slot in the main thread.
My button listener is a QObject initialized like so:
buttonPressed = pyqtSignal(int)
def __init__(self):
super(ButtonEvent, self).__init__()
self.buttonPressed.connect(self.onButtonPressed)
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
The onButtonPressed slot is internal to the button listener for testing purposes.
To move the button listener to another thread to do the work, I use the following:
event = ButtonEvent()
eventThread = QThread()
event.moveToThread(eventThread)
eventThread.started.connect(event.run)
Then in the main thread, I have my VideoTableController class that contains the slot in the main thread that doesn't get called. Inside of __init__ I have this:
class VideoTableController(QObject):
def __init__(self, buttonEvent):
buttonEvent.buttonPressed.connect(self.onButtonPressed)
Where onButtonPressed in this case is:
#pyqtSlot(int)
def onButtonPressed(self, bid):
print 'handling button press'
if bid not in listenButtons: return
{ ButtonEnum.KEY_LEFT : self.handleBack,
#...
So when I start the event thread, it starts listening properly. When I press a button on the remote, the onButtonPressed slot internal to the ButtonEvent class is properly called, but the slot within VideoTableController, which resides in the main thread, is not called. I started my listening thread after connecting the slot to the signal, and I tested doing it the other way around, but to no avail.
I have looked around, but I haven't been able to find anything. I changed over to using QObject after reading You're doing it wrong. Any help with this is greatly appreciated. Let me know if you need anything else.
EDIT: Thanks for the responses! Here is a big chunk of code for you guys:
ButtonEvent (This class uses singleton pattern, excuse the poor coding because I'm somewhat new to this territory of Python also):
import pylirc
from PyQt4.QtCore import QObject, pyqtSignal, QThread, pyqtSlot
from PyQt4 import QtCore
class ButtonEvent(QObject):
"""
A class used for firing button events
"""
_instance = None
_blocking = 0
_isListening = False
buttonPressed = pyqtSignal(int)
def __new__(cls, configFileName="~/.lircrc", blocking=0, *args, **kwargs):
if not cls._instance:
cls._instance = super(ButtonEvent, cls).__new__(cls, args, kwargs)
cls._blocking = blocking
if not pylirc.init("irexec", configFileName, blocking):
raise RuntimeError("Problem initilizing PyLIRC")
cls._isListening = True
return cls._instance
def __init__(self):
"""
Creates an instance of the ButtonEvent class
"""
super(ButtonEvent, self).__init__()
self.buttonPressed.connect(self.button)
### init
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
def stopListening(self):
print 'stopping'
self._isListening = False
#pyqtSlot(int)
def button(self, bid):
print 'Got ' + str(bid)
def setupAndConnectButtonEvent(configFileName="~/.lircrc", blocking=0):
"""
Initializes the ButtonEvent and puts it on a QThread.
Returns the QThread it is running on.
Does not start the thread
"""
event = ButtonEvent().__new__(ButtonEvent, configFileName, blocking)
eventThread = QThread()
event.moveToThread(eventThread)
eventThread.started.connect(event.run)
return eventThread
Here is the VideoTableController:
from ControllerBase import ControllerBase
from ButtonEnum import ButtonEnum
from ButtonEvent import ButtonEvent
from PyQt4.QtCore import pyqtSlot
from PyQt4 import QtCore
class VideoTableController(ControllerBase):
listenButtons = [ ButtonEnum.KEY_LEFT,
ButtonEnum.KEY_UP,
ButtonEnum.KEY_OK,
ButtonEnum.KEY_RIGHT,
ButtonEnum.KEY_DOWN,
ButtonEnum.KEY_BACK ]
def __init__(self, model, view, parent=None):
super(VideoTableController, self).__init__(model, view, parent)
self._currentRow = 0
buttonEvent = ButtonEvent()
buttonEvent.buttonPressed.connect(self.onButtonPressed)
self.selectRow(self._currentRow)
#pyqtSlot(int)
def onButtonPressed(self, bid):
print 'handling button press'
if bid not in listenButtons: return
{ ButtonEnum.KEY_LEFT : self.handleBack,
ButtonEnum.KEY_UP : self.handleUp,
ButtonEnum.KEY_OK : self.handleOk,
ButtonEnum.KEY_RIGHT : self.handleRight,
ButtonEnum.KEY_DOWN : self.handleDown,
ButtonEnum.KEY_BACK : self.handleBack,
}.get(bid, None)()
And here is my startup script:
import sys
from PyQt4 import QtCore, QtGui
from ui_main import Ui_MainWindow
from VideoTableModel import VideoTableModel
from VideoTableController import VideoTableController
from ButtonEvent import *
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.buttonEvent = ButtonEvent()
self.bEventThread = setupAndConnectButtonEvent()
model = VideoTableModel("/home/user/Videos")
self.ui.videoView.setModel(model)
controller = VideoTableController(model, self.ui.videoView)
self.bEventThread.start()
def closeEvent(self, event):
self.buttonEvent.stopListening()
self.bEventThread.quit()
event.accept()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
buttonEvent = ButtonEvent()
myapp = Main()
myapp.show()
sys.exit(app.exec_())
It turns out I was just making a foolish Python mistake. The signal was being emitted correctly, and the event loop was running properly in all threads. My problem was that in my Main.__init__ function I made a VideoTableController object, but I did not keep a copy in Main, so my controller did not persist, meaning the slot also left. When changing it to
self.controller = VideoTableController(model, self.ui.videoView)
Everything stayed around and the slots were called properly.
Moral of the story: it's not always a misuse of the library, it may be a misuse of the language.
It seems that the quickest workaround would be change your ButtonEvent code here:
...
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
...
to this:
#pyqtSlot()
def run(self):
print 'running'
while(self._isListening):
s = pylirc.nextcode()
if (s):
print 'emitting'
self.buttonPressed.emit(int(s[0]))
The short explanation to this issue is that PyQt uses a proxy internally, and this way you can make sure to avoid that. After all, your method is supposed to be a slot based on the connect statement.
Right... Now, I would encourage you to give some consideration for your current software design though. It seems that you are using a class in a dedicated thread for handling Qt button events. It may be good idea, I am not sure, but I have not seen this before at least.
I think you could get rid of that class altogether in the future with a better approach where you connect from the push button signals directly to your handler slot. That would not be the run "slot" in your dedicated thread, however, but the cannonical handler.
It is not a good design practice to introduce more complexity, especially in multi-threaded applications, than needed. Hope this helps.
I haven't actually tested this (because I don't have access to your compiled UI file), but I'm fairly certain I'm right.
Your run method of your ButtonEvent (which is supposed to be running in a thread) is likely running in the mainthread (you can test this by importing the python threading module and adding the line print threading.current_thread().name. To solve this, decorate your run method with #pyqtSlot()
If that doesn't solve it, add the above print statement to various places until you find something running in the main thread that shouldn't be. The lined SO answer below will likely contain the answer to fix it.
For more details, see this answer: https://stackoverflow.com/a/20818401/1994235