I have some small PyQt program that I want to convert to Tkinter(because of commercial license). And I wonder how easy was working with multi-threading on Qt:
class DownloadFileThread(QtCore.QThread):
download_error = QtCore.pyqtSignal(str, str)
download_progress = QtCore.pyqtSignal(int)
download_finished = QtCore.pyqtSignal()
def run(self):
# some code here ...
if error:
self.download_error.emit(
f'Error',
f'error'
)
elif progress:
self.download_progress.emit(progress)
else:
self.download_finished.emit()
f.close()
# and then somewhere in GUI class:
def set_progress_bar_value(self, val):
self.progress_bar.setValue(val)
self.download_file_thread.download_progress.connect(self.set_progress_bar_value)
So I have fully independent class that allows me to connect multiple widgets and control them based on SIGNALS
But I read a lot of articles and examples and still can't figure out how to implement something like this on Tkinter. How to make fully independent class that should post events to main thread and then GUI class should handle this events and update UI.
I use the standard python thread module in python for Tkinter and find it works great..
Here is an excerpt from a program used for streaming data from an API..
Hopefully it can give you a head start but it is pure functional based..
In my case it worked a charm.. The program crashes without..
def get_data():
"""Parse and dump all data."""
fnt = font.Font(family="Courier New", size=14)
textPad = tkinter.Frame(root)
textPad.pack(expand=True, fill='both')
T = tkinter.Text(textPad, font=fnt, height=29)
scrollbar = tkinter.Scrollbar(textPad)
.....
def thread_stuff():
t = threading.Thread(target=get_data)
t.daemon = True
t.start()
Related
I have a Qt Python program that logs data over a serial port. I'd like this program to always log data while running even when the application is not visible. Currently, when the application is not visible, the logging will pause after about ~45 seconds. Once the application window becomes visible again, logging resumes. The logging portion of the code is in a second thread using QRunnable and QThreadPool.
I've tried searching for the cause (or solution), but have not had much luck. Part of my problem is that I'm not sure if this issue is related to the OS, IDE, language, etc.
High-level details:
OS: macOS 12.4
IDE: vscode
Language/frameworks: Python3 / Qt (pyside6)
Does anyone have any ideas on why this application/thread might be pausing? Is it possible to have the application to continue to log data even when it is not visible? My hope is that once I'm pointed in the right direction I'll be able to address the issue.
UPDATE
Example code
class LogSignals(QObject):
result = Signal(dict)
class LogWorker(QRunnable):
def __init__():
super().__init__()
self.signals = LogSignals()
def run(self):
try:
for i in range(N_LOG_SAMPLES):
result = self.getSerialData()
self.signals.result.emit(result)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.threadpool = QThreadPool()
def startLog(self):
log_worker = LogWorker()
log_worker.signals.result.connect(self.updateLogData)
self.threadpool.start(log_worker)
#Slot()
def updateLogData(self, result: dict):
#save data
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
You would need to check if your application is active, and start or drop the logging accordingly. You can get this by using:
QWidget *QApplication::activeWindow()
This would return a nullptr if your application does not have an active window. So, you would write something like this:
if (application->activeWindow())
; // Start / Keep logging
else
; // Stop logging
I am a writing a GUI based application using the PyQT framework which connects to a device, sends commands, reads the corresponding data and displays this in real time to a table, graph widget and to a file.
Once the run button is clicked it starts a thread which sends the external device commands according to a procedure table and emits signals with the data to various methods to change the GUI.
When the run button is clicked it executes the following lines:
worker = Worker(self.runProcedure)
worker.signals.updateResults.connect(self.updateResultsTable)
worker.signals.writeResults.connect(self.writeResultsFile)
worker.signals.finished.connect(self.procedure_complete)
self.threadpool.start(worker)
within the runProcedure commands are sent to the device from the procedure table and the data read from the device is put into a list 'hfData' using code similar to that listed below:
while float(currentForceReading) <= float(target) and stopAlarm == 0:
ts = dt.now().timestamp()
hfData = (connection.readline()).split()
updateResults_callback.emit(ts, hfData) #method to update results table and graphs
writeResults_callback.emit(ts, hfData) #method to write results to a file
One of the options in the application is to hold for a period of time where it continues collecting data from the device without sending new commands.
I am looking for a way to continue taking measurements whilst in this hold time and continue updating the GUI.
I have tried to implement the following code, however this while loop blocks the GUI from updating:
stepHoldTime = float(procedureModel.data(procedureModel.index(row,4), Qt.DisplayRole))
if(stepHoldTime != 0):
endTime = time.monotonic() + stepHoldTime
while(time.monotonic() < endTime):
ts = dt.now().timestamp()
hfData = (connection.readline()).split()
updateResults_callback.emit(ts,hfData)
Is there a correct way to implement this functionality?
Instead of while-loop you could run QTimer which will execute code every few milliseconds.
It is minimal example which shows how to run function every 1000ms and update time in label.
from PyQt5.QtWidgets import QApplication, QLabel
from PyQt5.QtCore import QTimer, QDateTime
class Window(QLabel):
def __init__(self, parent=None):
super().__init__(parent)
self.showTime()
help(QTimer)
self.timer = QTimer()
self.timer.timeout.connect(self.showTime)
self.timer.start(1000)
def showTime(self):
text = QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')
self.setText(text)
if __name__ == '__main__':
app = QApplication([])
win = Window()
win.show()
app.exec()
But your problem can be more complex and it may need more complex solution. You may have to show minimal working code which we could run and see problem - and test some ideas to resolve problem.
You can start your "reader" from a separate thread
class YourUi(QtWidgets.QMainWindow):
update_data = QtCore.pyqtSignal(str)
def __init__(self):
super(YourUi, self).__init__()
self.update_data.connect(self.do_update_data)
t = threading.Thread(target=self.update_worker, args=(self.update_data,), daemon=True)
t.start()
#staticmethod
def update_worker(signal):
connection = create_conncetion()
while True:
hfData = (connection.readline()).split()
signal.emit(hfData)
time.sleep(0.1)
def do_update_data(self, hf_data):
# access GUI elemetns from main thread to prevent freezing
self.some_qt_label.setText(str(hf_data))
I was having trouble formatting the title of this question, because I wasn't sure I'm going about this the right way, so let me explain.
I want to try and add a right-click context menu to an existing program for which I don't have the source code. wxPython is generally my framework of choice. I figured there was a couple ways of doing this:
1) Create a transparent wx.Frame which is tied to and sits on top of the existing program, intercepting mouse events. If I did this, I wasn't sure if the mouse events could then be passed to the underlying window. I like this option, because it would allow adding more useful information in the overlay.
2) Create a headless program which globally intercepts right-click events, and spawns the context menu at the pointer location when certain conditions are met. Based on the research I've done so far, this didn't seem possible without continuously polling for mouse position.
What am I missing? Is there a more elegant solution for this? Is this even possible using Python?
edit: I have a partial proof-of-concept working which looks like this:
import wx
import win32gui
import win32api
import win32con
class POC_Frame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, id=wx.ID_ANY, title='POC', pos=(0,0), size=wx.Size(500, 500), style=wx.DEFAULT_FRAME_STYLE)
self.ToggleWindowStyle(wx.STAY_ON_TOP)
extendedStyleSettings = win32gui.GetWindowLong(self.GetHandle(), win32con.GWL_EXSTYLE)
win32gui.SetWindowLong(self.GetHandle(), win32con.GWL_EXSTYLE,
extendedStyleSettings | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT)
win32gui.SetLayeredWindowAttributes(self.GetHandle(), win32api.RGB(0,0,0), 100, win32con.LWA_ALPHA)
self.Bind(wx.EVT_RIGHT_DOWN, self.onRightDown)
self.Bind(wx.EVT_RIGHT_UP, self.onRightUp)
self.CaptureMouse()
def onRightDown(self, event):
print(event)
def onRightUp(self, event):
print(event)
app = wx.App(False)
MainFrame = POC_Frame(None)
MainFrame.Show()
app.MainLoop()
This seems to work OK, as it passes the right click events to the underlying window, while still recognizing them, but it only does it exactly once. As soon as it loses focus, it stops working and nothing I've tried to return focus to it seems to work.
I've always had better luck hooking global mouse and keyboard events with pyHook rather than wx. Here is a simple example:
import pyHook
import pyHook.cpyHook # ensure its included by cx-freeze
class ClickCatcher:
def __init__(self):
self.hm = None
self._is_running = True
self._is_cleaned_up = False
self._is_quitting = False
self.set_hooks()
# this is only necessary when not using wx
# def send_quit_message(self):
# import ctypes
# win32con_WM_QUIT = 18
# ctypes.windll.user32.PostThreadMessageW(self.pump_message_thread.ident, win32con_WM_QUIT, 0, 0)
def __del__(self):
self.quit()
def quit(self):
if not self._is_running:
return
self._is_quitting = True
self._is_running = False
if self.hm:
# self.hm.UnhookKeyboard()
self.hm.UnhookMouse()
# self.send_quit_message()
self._is_cleaned_up = True
def set_hooks(self):
self._is_running = True
self._is_cleaned_up = False
self.hm = pyHook.HookManager()
self.hm.MouseRightUp = self.on_right_click
# self.hm.HookKeyboard()
self.hm.HookMouse()
def on_right_click(self):
# create your menu here
pass
If you weren't using wx, you'd have to use pythoncom.PumpMessages to push mouse and keyboard events to you program, but App.Mainloop() accomplishes the same thing (if you use PumpMessages and Mainloop together about half of the events won't be push to your program).
Creating a wx.Menu is easy enough. You can find the mouse coordinates using wx.GetMousePosition()
I'm trying to create a simple Python GUI (with Tkinter) with start button, running a while loop in a thread, and a stop button to stop the while loop.
I'm having issue with the stop button, which doesn't stop anything and frozen GUI once the start button is clicked.
See code below:
import threading
import Tkinter
class MyJob(threading.Thread):
def __init__(self):
super(MyJob, self).__init__()
self._stop = threading.Event()
def stop(self):
self._stop.set()
def run(self):
while not self._stop.isSet():
print "-"
if __name__ == "__main__":
top = Tkinter.Tk()
myJob = MyJob()
def startCallBack():
myJob.run()
start_button = Tkinter.Button(top,text="start", command=startCallBack)
start_button.pack()
def stopCallBack():
myJob.stop()
stop_button = Tkinter.Button(top,text="stop", command=stopCallBack)
stop_button.pack()
top.mainloop()
Any idea how to solve this? I'm sure this is trivial and must have be done thousands of times but I cannot find a solution myself.
Thanks
David
The code is calling run method directly. It will call the method in the main thread. To run it in a separated thread you should use threading.Thread.start method.
def startCallBack():
myJob.start()
I am trying to write a custom software (in python) to grab frames from a frame grabber card (Hauppauge WinTV-HVR-1900), and I cannot get it to work. The bundled WinTV software works perfectly, and I am able to grab frames from usb webcams, so I know the hardware works. I manage to capture something though, since my screen is completely black but changes to the right size and resolution, so my hunch is it is some sort of decoding problem.
Could anyone help me with an indication or provide a code example for frame extraction from such hardware, please? Should I write an entire custom driver? Or maybe use the VLC python bindings (since VLC does succeed in reading the video stream)?
EDIT:
Basically, my question comes down to this: How is my frame grabber different from a webcam, since the integrated hardware encoder yields an MPEG-2 stream? Shouldn't is behave just as my webcam?
The logic file of my attempt (QT framework using pyQt4 with python 2.7 on windows 7):
#-*- coding: utf-8 -*-
import sys
from VideoCapture import Device
#import Gui files and classes
import FloGui
import deviceDialog
# PyQT4 imports
from PyQt4 import QtGui, QtCore
class devicedialog(QtGui.QDialog, deviceDialog.Ui_streamDialog):
#the logic of the streaming device choice dialog
def __init__(self,parent=None):
super(devicedialog,self).__init__(parent)
self.setupUi(self)
self.retranslateUi(self)
self.index = None
i=0
self.camlist=list()
while True:
try:
cam = Device(i,0)
name = cam.getDisplayName()
dev = [name,i]
self.camlist.append(dev)
i+=1
del cam
except:
break
for j in xrange(len(self.camlist)):
item = QtGui.QListWidgetItem(self.camlist[j][0])
self.deviceListBox.addItem(item)
self.exec_()
def accept(self):
selected = self.deviceListBox.currentItem().text()
for k in xrange(len(self.camlist)):
if self.camlist[k][0]==selected:
self.index = k
QtGui.QDialog.accept(self)
class Flolog(QtGui.QMainWindow,FloGui.Ui_MainWindow):
"""
Flolog is inherited from both QtGui.QDialog and FloGui.Ui_MainWindow
"""
def __init__(self, parent=None):
"""
Initialization of the class. Call the __init__ for the super classes
"""
super(Flolog,self).__init__(parent)
self.setupUi(self)
self.retranslateUi(self)
self.connectActions()
def streamchoice(self):
streamdialog = devicedialog()
self.VideoWidget.stop()
self.VideoWidget.path = streamdialog.index
self.VideoWidget.load()
self.playButton.setEnabled(True)
def menuloadfile(self):
filename = QtGui.QFileDialog.getOpenFileName(self, 'Open File', '.')
self.VideoWidget.stop()
self.VideoWidget.path = str(filename)
self.VideoWidget.load()
self.playButton.setEnabled(True)
def playbutton(self):
self.VideoWidget.play()
self.playButton.setEnabled(False)
def main(self):
self.show()
def quit_(self):
sys.exit(0)
def connectActions(self):
"""
Connect the user interface controls to the logic
"""
self.actionFile.triggered.connect(self.menuloadfile)
self.actionStream.triggered.connect(self.streamchoice)
self.actionQuit.triggered.connect(self.quit_)
self.playButton.clicked.connect(self.playbutton)
self.stopButton.clicked.connect(self.VideoWidget.stop)
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
Flologob = Flolog()
Flologob.main()
sys.exit(app.exec_())
I actually finally did solve this. The problem stemmed from the fact that one should first specify which video device should be used by the graphics driver, either manually or in the code. I solved this on Linux Ubuntu by switching devices via the v4l2 graphics driver. I know there is an equivalent maneuver to be performed on Windows.