I'm trying to build a PyQt app which (among other things) has the ability via a QTextEdit Box to function like a serial terminal program (HyperTerminal, TeraTerm, etc.) I've read through a few examples from the PySerial page and I think I've managed to get the receive data thread working properly but maybe not as efficiently as possible.
My problem is how do I take the last typed character in the QTextEdit box and send that out the serial connection? I've tried using the textChanged signal that QTextEdit emits, but that then sends everything that I type AND that it receives. I've tried setting up an eventFilter in my main GUI class, but I can't figure out how to get that over to the serial function in another file. Do I want to have a separate thread that listens for a signal emitted from the eventFilter? How do I do that? Is there a more elegant way to do this?
I'm sure I've just managed to overthink this and the solution is simple, but I'm somewhat struggling with it. I'll attach the relevant code snippets (not a full code set) and perhaps somebody can point me in the right direction. If anybody also thinks that the threading that I'm doing could be done in a more efficient manner, then please relay that to me as well!
Thanks for any help that anybody can provide!
Main File:
import sys
from PyQt4 import QtGui
from MainGUI import TestGUI
from SerialClasses import *
from SerialMiniterm import *
class StartMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(StartMainWindow, self).__init__(parent)
self.ui = TestGUI()
self.ui.setupUi(self)
self.ui.serialTextEditBox.installEventFilter(self)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.KeyPress and source is self.ui.serialTextEditBox):
# print some debug statements to console
if (event.key() == QtCore.Qt.Key_Tab):
print ('Tab pressed')
print ('key pressed: %s' % event.text())
print ('code pressed: %d' % event.key())
# do i emit a signal here? how do i catch it in thread?
self.emit(QtCore.SIGNAL('transmitSerialData(QString)'), event.key())
return True
return QtGui.QTextEdit.eventFilter(self, source, event)
def serialConnectCallback(self):
self.miniterm = SerialMiniterm(self.ui, self.SerialSettings)
self.miniterm.start()
temp = self.SerialSettings.Port + 1
self.ui.serialLabel.setText("<font color = green>Serial Terminal Connected on COM%d" % temp)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setStyle("Cleanlooks")
myapp = StartMainWindow()
myapp.show()
sys.exit(app.exec_())
SerialMiniterm.py:
import serial
from PyQt4 import QtGui, QtCore
def character(b):
return b
class SerialMiniterm(object):
def __init__(self, ui, SerialSettings):
self.SerialSettings = SerialSettings
self.ui = ui
self.serial = serial.Serial(self.SerialSettings.Port, self.SerialSettings.BaudRate, parity=self.SerialSettings.Parity, rtscts=self.SerialSettings.RTS_CTS, xonxoff=self.SerialSettings.Xon_Xoff, timeout=1)
self.repr_mode = self.SerialSettings.RxMode
self.convert_outgoing = self.SerialSettings.NewlineMode
self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing]
self.dtr_state = True
self.rts_state = True
self.break_state = False
def _start_reader(self):
"""Start reader thread"""
self._reader_alive = True
self.receiver_thread = ReaderThread(self.alive, self._reader_alive, self.repr_mode, self.convert_outgoing, self.serial)
self.receiver_thread.connect(self.receiver_thread, QtCore.SIGNAL("updateSerialTextBox(QString)"), self.updateTextBox)
self.receiver_thread.start()
def _stop_reader(self):
"""Stop reader thread only, wait for clean exit of thread"""
self._reader_alive = False
self.receiver_thread.join()
def updateTextBox(self, q):
self.ui.serialTextEditBox.insertPlainText(q)
self.ui.serialTextEditBox.moveCursor(QtGui.QTextCursor.End)
#print "got here with value %s..." % q
def start(self):
self.alive = True
self._start_reader()
# how do i handle transmitter thread?
def stop(self):
self.alive = False
def join(self, transmit_only=False):
self.transmitter_thread.join()
if not transmit_only:
self.receiver_thread.join()
class ReaderThread(QtCore.QThread):
def __init__(self, alive, _reader_alive, repr_mode, convert_outgoing, serial, parent=None):
QtCore.QThread.__init__(self, parent)
self.alive = alive
self._reader_alive = _reader_alive
self.repr_mode = repr_mode
self.convert_outgoing = convert_outgoing
self.serial = serial
def __del__(self):
self.wait()
def run(self):
"""loop and copy serial->console"""
while self.alive and self._reader_alive:
data = self.serial.read(self.serial.inWaiting())
if data: #check if not timeout
q = data
self.emit(QtCore.SIGNAL('updateSerialTextBox(QString)'), q)
Something like this?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Terminal(QtGui.QPlainTextEdit):
def keyPressEvent(self, event):
print event.text()
return QtGui.QPlainTextEdit.keyPressEvent(self, event)
term = Terminal()
term.show()
Related
I have two Python scripts that i need to communicate with each another. The first is a GUI made in PySide2. The GUI consists of simple controls for controlling a bluetooth audio device (play, pause, next, previous, etc...). These commands operate with a second python script that i found. The second script is a loop that waits for these commands to be entered and responds once those commands are executed. I'm pretty new to programming, i'm guessing this is essentially connecting a front end with a back end, but its something i've never done before.
I've written a simplified version of my GUI to only display the controls i need. The "back-end" is also below but can originally be found here: https://scribles.net/controlling-bluetooth-audio-on-raspberry-pi/
I've previously asked a similar question and was given a solid and working answer by #eyllanesc here: Execute command to a Python script from separate Python script? However, using the QProcess method i could not work out how to get the print outputs from the back-end into the front-end script. The error messages print correctly however. I have tried playing around with sys.stdout in the back-end, variants of process.read, and QByteArrays but can't seem to get anything going.
The other issue i'm having is that the script will only work if a bluetooth device is connected prior to starting the script. If i disconnect while it is running and try to reconnect, it will no longer accept commands. If there is also a way to monitor whether a device is playing/paused so that the play/pause button can update depending on the devices state, that would also be useful, but its not important at this stage.
Theres a number of ways that it can be done, but i feel that ultimately it would be better for me to have both scripts integrated into one, however i'm open to any solution that works. If anyone has any advice or can get me started i'd be very appreciative!
Front-end:
import sys
from PySide2.QtWidgets import *
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.playbtn = QPushButton("Play")
self.nextbtn = QPushButton("Next")
self.prevbtn = QPushButton("Prev")
layout = QVBoxLayout()
layout.addWidget(self.playbtn)
layout.addWidget(self.nextbtn)
layout.addWidget(self.prevbtn)
self.setLayout(layout)
self.playbtn.released.connect(self.btnplay)
self.nextbtn.released.connect(self.btnnext)
self.prevbtn.released.connect(self.btnprev)
def btnplay(self): #play button turns into pause button upon being pressed
status = self.playbtn.text()
if status == "Play":
self.playbtn.setText("Pause")
print("Play Pressed")
elif status == "Pause":
self.playbtn.setText("Play")
print("Pause pressed")
def btnnext(self):
print("Next pressed")
def btnprev(self):
print("Prev pressed")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Back-end:
import dbus, dbus.mainloop.glib, sys
from gi.repository import GLib
def on_property_changed(interface, changed, invalidated):
if interface != 'org.bluez.MediaPlayer1':
return
for prop, value in changed.items():
if prop == 'Status':
print('Playback Status: {}'.format(value))
elif prop == 'Track':
print('Music Info:')
for key in ('Title', 'Artist', 'Album'):
print(' {}: {}'.format(key, value.get(key, '')))
def on_playback_control(fd, condition):
str = fd.readline()
if str.startswith('play'):
player_iface.Play()
elif str.startswith('pause'):
player_iface.Pause()
elif str.startswith('next'):
player_iface.Next()
elif str.startswith('prev'):
player_iface.Previous()
elif str.startswith('vol'):
vol = int(str.split()[1])
if vol not in range(0, 128):
print('Possible Values: 0-127')
return True
transport_prop_iface.Set(
'org.bluez.MediaTransport1',
'Volume',
dbus.UInt16(vol))
return True
if __name__ == '__main__':
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
obj = bus.get_object('org.bluez', "/")
mgr = dbus.Interface(obj, 'org.freedesktop.DBus.ObjectManager')
player_iface = None
transport_prop_iface = None
for path, ifaces in mgr.GetManagedObjects().items():
if 'org.bluez.MediaPlayer1' in ifaces:
player_iface = dbus.Interface(
bus.get_object('org.bluez', path),
'org.bluez.MediaPlayer1')
elif 'org.bluez.MediaTransport1' in ifaces:
transport_prop_iface = dbus.Interface(
bus.get_object('org.bluez', path),
'org.freedesktop.DBus.Properties')
if not player_iface:
sys.exit('Error: Media Player not found.')
if not transport_prop_iface:
sys.exit('Error: DBus.Properties iface not found.')
bus.add_signal_receiver(
on_property_changed,
bus_name='org.bluez',
signal_name='PropertiesChanged',
dbus_interface='org.freedesktop.DBus.Properties')
GLib.io_add_watch(sys.stdin, GLib.IO_IN, on_playback_control)
GLib.MainLoop().run()
UPDATE 31/10/2020:
I've been playing around with the QProcess class suggested in my earlier question linked above. By using it on the button press functions and adding sys.exit after the command has been executed, it eliminates the need for a device to always be connected, but i still can't find a way to recieve the print outputs from back-end script. It also feels like a really dirty way of working. It also retains the issue with the play/pause state not automatically updating. If anyone has any suggestions i would be very grateful!
import sys
import os.path
from PySide2.QtCore import *
from PySide2.QtWidgets import *
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.playbtn = QPushButton("Play")
self.nextbtn = QPushButton("Next")
self.prevbtn = QPushButton("Prev")
layout = QVBoxLayout()
layout.addWidget(self.playbtn)
layout.addWidget(self.nextbtn)
layout.addWidget(self.prevbtn)
self.setLayout(layout)
self.playbtn.released.connect(self.btnplay)
self.nextbtn.released.connect(self.btnnext)
self.prevbtn.released.connect(self.btnprev)
def btnplay(self):
self.process = QProcess()
self.process.readyReadStandardError.connect(self.handle_readyReadStandardError)
self.process.readyReadStandardOutput.connect(self.handle_readyReadStandardOutput)
self.process.setProgram(sys.executable)
script_path = os.path.join(CURRENT_DIR, "test2.py")
self.process.setArguments([script_path])
self.process.start()
status = self.playbtn.text()
if status == "Play":
command = "play"
self.playbtn.setText("Pause")
print("play pressed")
elif status == "Pause":
command = "pause"
self.playbtn.setText("Play")
print("pause pressed")
msg = "{}\n".format(command)
self.process.write(msg.encode())
def btnnext(self):
self.process = QProcess()
self.process.readyReadStandardError.connect(self.handle_readyReadStandardError)
self.process.readyReadStandardOutput.connect(self.handle_readyReadStandardOutput)
self.process.setProgram(sys.executable)
script_path = os.path.join(CURRENT_DIR, "test2.py")
self.process.setArguments([script_path])
self.process.start()
command = "next"
msg = "{}\n".format(command)
self.process.write(msg.encode())
print("next pressed")
def btnprev(self):
self.process = QProcess()
self.process.readyReadStandardError.connect(self.handle_readyReadStandardError)
self.process.readyReadStandardOutput.connect(self.handle_readyReadStandardOutput)
self.process.setProgram(sys.executable)
script_path = os.path.join(CURRENT_DIR, "test2.py")
self.process.setArguments([script_path])
self.process.start()
command = "prev"
msg = "{}\n".format(command)
self.process.write(msg.encode())
print("prev pressed")
def handle_readyReadStandardError(self):
print(self.process.readAllStandardError().data().decode())
def handle_readyReadStandardOutput(self):
print(self.process.readAllStandardOutput().data().decode())
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
IMHO the OP has an XY problem that adds unnecessary complexity to the application since the dbus eventloop can coexist with the Qt one as I show in the following example:
import sys
import dbus
import dbus.mainloop.glib
from PyQt5 import QtCore, QtWidgets
class AudioManager(QtCore.QObject):
statusChanged = QtCore.pyqtSignal(str)
infoChanged = QtCore.pyqtSignal(dict)
def __init__(self, parent=None):
super().__init__(parent)
self._player_iface = None
self._transport_prop_iface = None
def initialize(self):
bus = dbus.SystemBus()
obj = bus.get_object("org.bluez", "/")
mgr = dbus.Interface(obj, "org.freedesktop.DBus.ObjectManager")
player_iface = None
transport_prop_iface = None
for path, ifaces in mgr.GetManagedObjects().items():
if "org.bluez.MediaPlayer1" in ifaces:
player_iface = dbus.Interface(
bus.get_object("org.bluez", path), "org.bluez.MediaPlayer1"
)
elif "org.bluez.MediaTransport1" in ifaces:
transport_prop_iface = dbus.Interface(
bus.get_object("org.bluez", path), "org.freedesktop.DBus.Properties"
)
if not player_iface:
raise Exception("Error: Media Player not found.")
if not transport_prop_iface:
raise Exception("Error: DBus.Properties iface not found.")
self._player_iface = player_iface
self._transport_prop_iface = transport_prop_iface
bus.add_signal_receiver(
self.handle_property_changed,
bus_name="org.bluez",
signal_name="PropertiesChanged",
dbus_interface="org.freedesktop.DBus.Properties",
)
def play(self):
self._player_iface.Play()
def pause(self):
self._player_iface.Pause()
def next(self):
self._player_iface.Next()
def previous(self):
self._player_iface.Previous()
def set_volume(self, Volume):
if Volume not in range(0, 128):
raise ValueError("Possible Values: 0-127")
self._transport_prop_iface.Set(
"org.bluez.MediaTransport1", "Volume", dbus.UInt16(vol)
)
def handle_property_changed(self, interface, changed, invalidated):
if interface != "org.bluez.MediaPlayer1":
return
for prop, value in changed.items():
if prop == "Status":
self.statusChanged.emit(value)
elif prop == "Track":
info = dict()
for key in ("Title", "Artist", "Album"):
info[key] = str(value.get(key, ""))
self.infoChanged.emit(info)
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._manager = AudioManager()
self._manager.infoChanged.connect(self.handle_info_changed)
self._manager.initialize()
self.playbtn = QtWidgets.QPushButton("Play")
self.nextbtn = QtWidgets.QPushButton("Next")
self.prevbtn = QtWidgets.QPushButton("Prev")
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.playbtn)
layout.addWidget(self.nextbtn)
layout.addWidget(self.prevbtn)
self.playbtn.released.connect(self._manager.play)
self.nextbtn.released.connect(self._manager.next)
self.prevbtn.released.connect(self._manager.previous)
def handle_info_changed(self, info):
print(info)
if __name__ == "__main__":
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
im not familiar with what you're using on your backend, but it should be as simple as this:
# in your frontend
import NAME_OF_BACKEND_PY_FILE_HERE as backend
def btnplay(self): #play button turns into pause button upon being pressed
status = self.playbtn.text()
if status == "Play":
self.playbtn.setText("Pause")
print("Play Pressed")
backend.on_playback_control("play")
elif status == "Pause":
self.playbtn.setText("Play")
print("Pause pressed")
backend.on_playback_control("pause")
def btnnext(self):
print("Next pressed")
backend.on_playback_control("next")
def btnprev(self):
print("Prev pressed")
backend.on_playback_control("prev")
in your backend you should also remove this line: if __name__ == '__main__':
and unindent all the code below it. im not sure how that function should be called normally or what the second variable 'condition' is for. but that's what I can come up with
I am useing python 3.7 and pyqt5
What I want to do is run same QObject with different thread many times.
here is my main
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import threading
import sys
from Worker_Starting import Worker_Starting
class Main(QMainWindow):
def __init__(self) :
super().__init__()
self.setupUI()
self.initSignal()
def setupUI(self):
self.resize(400, 400)
self.pushButton = QPushButton("Start", self)
self.pushButton_2 = QPushButton("Stop", self)
self.pushButton.move(0,0)
self.pushButton_2.move(120,0)
def initSignal(self) :
self.pushButton.clicked.connect(self.Start)
self.pushButton_2.clicked.connect(self.Stop)
#pyqtSlot()
def Start(self) :
for i in range(3) :
print('main', threading.get_ident())
input = ["userID", "userNAME"]
self.my_thread = QThread()
self.my_worker = Worker_Starting(passing_list=input)
self.my_worker.moveToThread(self.my_thread)
self.my_thread.started.connect(self.my_worker.my_fn)
self.my_thread.start()
time.sleep(3)
#pyqtSlot()
def Stop(self) :
pass
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Main()
window.show()
app.exec_()
and my Qobject
from PyQt5 import QtCore
from PyQt5.QtCore import *
import time
import threading
class Worker_Starting(QObject):
def __init__(self, passing_list, parent=None):
QtCore.QThread.__init__(self, parent=parent)
self.userid = passing_list[0]
self.username = passing_list[1]
def my_fn(self):
print(self.username, threading.get_ident())
for i in range(10) :
Now = time.strftime("%Y/%m/%d %H:%M:%S")
print(str(i) + " : " + self.username + " running" + " : " + Now)
time.sleep(0.5)
I put for i in range(3): under def start(self):
because in my actual main script other thread emit input to def start(self): several times.
if I emit only one time, def start(self): works fine.
but when I emit several times, it chrashs.
since I have to emit several times and I need my_thread to work simultaneously and continuously
is there anyway to set same Qobject to different thread?
and how to know or set thread id.
and how to stop thread by thread id.
*I have tried QRunnable, I now it works multi thread. but I have to emit signal back continouously to my main. as far as I know QRunnable are not suitable for custom signal.
The problem is that you're constantly creating new threads by overwriting the existing one, which results in the previous being garbage collected before it's completed, hence the crash.
If you want to execute the same "worker" function concurrently, you cannot use a single object for more threads, as you should use moveToThread each time and this is not possible.
The solution is to use QRunnable instead, implement the function within its run() method, and call QThreadPool to run it.
class Worker_Starting(QRunnable):
def __init__(self, passing_list):
QRunnable.__init__(self)
self.setAutoDelete(False)
self.userid = passing_list[0]
self.username = passing_list[1]
def run(self):
print(self.username, threading.get_ident())
for i in range(10) :
Now = time.strftime("%Y/%m/%d %H:%M:%S")
print(str(i) + " : " + self.username + " running" + " : " + Now)
time.sleep(0.5)
class Main(QMainWindow):
def __init__(self) :
super().__init__()
self.setupUI()
self.initSignal()
self.worker = None
# ...
def Start(self) :
if not self.worker:
input = ["userID", "userNAME"]
self.worker = Worker_Starting(passing_list=input)
for i in range(3):
print('main', threading.get_ident())
QThreadPool.globalInstance().start(self.worker)
Remember that you should not use blocking functions within the main Qt thread (that's why I removed time.sleep(3) from Start()). If you want to start the function more than once at regular intervals, use a QTimer:
interval = 3000 # interval is in milliseconds
for i in range(3):
print('main', threading.get_ident())
QTimer.singleShot(interval * i, lambda:
QThreadPool.globalInstance().start(self.worker))
Note that the pyqtSlot() decorator is usually needed only for special cases and advanced implementation, so you should remove it. Also note that only classes and constants should have capitalized names, while functions, variables and attributes should be lower cased (prefer start and stop instead of Start and Stop).
I have a PyQt window that calls multiple executables as QProcess. How can I list the outputs of each process after the last one has finished? (something like process_result = ["result1", "result2",..])
Let us say it looks like this:
for i in list_of_processes:
process = QtCore.QProcess()
process.start(i)
I can read with process.readyReadStandardOutput() somehow but it is quite chaotic because processes run parallel. process.waitForFinished() does not work because the GUI will freeze.
Also, I checked following page about multithreading: Multithreading PyQt applications with QThreadPool. Another question is similar but did not help me either: Pyside: Multiple QProcess output to TextEdit.
A possible solution is to create a class that manages the processes, and that emits a single signal when all the processes finish as you require.
import sys
from functools import partial
from PyQt4 import QtCore, QtGui
class TaskManager(QtCore.QObject):
resultsChanged = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
self.results = []
self.m_processes = []
self.number_process_running = 0
def start_process(self, programs):
for i, program in enumerate(programs):
process = QtCore.QProcess(self)
process.readyReadStandardOutput.connect(partial(self.onReadyReadStandardOutput, i))
process.start(program)
self.m_processes.append(process)
self.results.append("")
self.number_process_running += 1
def onReadyReadStandardOutput(self, i):
process = self.sender()
self.results[i] = process.readAllStandardOutput()
self.number_process_running -= 1
if self.number_process_running <= 0:
self.resultsChanged.emit(self.results)
def on_finished(results):
print(results)
QtCore.QCoreApplication.quit()
if __name__ == '__main__':
app = QtCore.QCoreApplication(sys.argv)
manager = TaskManager()
manager.start_process(["ls", "ls"])
manager.resultsChanged.connect(on_finished)
sys.exit(app.exec_())
I have a thread where I need to execute a heavy function.
First, I have a function that takes two QDateTime values from the GUI and convert them to UNIX timestamps. Secondly, the "heavy" function uses these values to perform a task.
Both functions (function_task, time_converter_to_unix) do not belong to any class, so as far as I know I can use them in the thread.
But not the parameters, since I cannot access the QDateTime values.
Error: AttributeError: 'TaskThread' object has no attribute 'startTime'
How can I access the QDateTime and read the content from the thread? Thank you.
EDIT: Complete Code. You can find also the link to the GUI in the following link: interface.ui
import sys
import datetime
import time
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtCore import *
from PyQt4.QtGui import *
# Link to GUI
qtCreatorFile = "interface.ui"
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
def time_converter_to_unix(start_datetime, end_datetime):
# Convert QTimeEdit to UNIX Timestamp (int, msec included), and then to float
start_datetime_unix_int = start_datetime.toMSecsSinceEpoch ()
start_datetime_unix = (float(start_datetime_unix_int) / 1000)
end_datetime_unix_int = end_datetime.toMSecsSinceEpoch ()
end_datetime_unix = (float(end_datetime_unix_int) / 1000)
return start_datetime_unix, end_datetime_unix
def dummy_function(self, start_datetime_unix, end_datetime_unix):
# Dummy function, just to simulate a task
result = start_datetime_unix + end_datetime_unix
print result
class Tool(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent = None):
# Setting-ip UI
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
# Button Action
self.runButton.clicked.connect(self.onStart)
# Progress Bar and Label. At the begining, the bar is at 0
self.progressBar.setValue(0)
self.progressBar.setRange(0,100)
self.resultLabel.setText("Waiting...")
#Thread
self.myLongTask = TaskThread()
self.myLongTask.taskFinished.connect(self.onFinished)
def onStart(self):
# Before running the thread, we set the progress bar in waiting mode
self.progressBar.setRange(0,0)
self.resultLabel.setText("In progress...")
print "Starting thread..."
self.myLongTask.start()
def onFinished(self):
# Stop the pulsation when the thread has finished
self.progressBar.setRange(0,1)
self.progressBar.setValue(1)
self.resultLabel.setText("Done")
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
# First, we read the times from the QDateTime elements in the interface
print "Getting times..."
start_datetime_unix, end_datetime_unix = time_converter_to_unix(self.startTime.dateTime(), self.endTime.dateTime())
# Then, we put these values in my_function
print "Executing function..."
dummy_function(self, start_datetime_unix, end_datetime_unix)
# To finish, we execute onFinished.
print "Finishing thread..."
self.taskFinished.emit()
def main():
app = QtGui.QApplication(sys.argv)
window = Tool()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If you execute it along with the .ui, you will see, that once we click on "Run", the Progress bar stays in waiting mode, and the error commented above appears.
startTime and endTime belong to the GUI, and not to the thread, that's why you get that error.
On the other hand it is advisable not to access the GUI from the other thread, it is best to obtain the values before starting the thread and set it as property of the thread as shown below:
class Tool(QtGui.QMainWindow, Ui_MainWindow):
...
def onStart(self):
# Before running the thread, we set the progress bar in waiting mode
self.progressBar.setRange(0,0)
self.resultLabel.setText("In progress...")
self.myLongTask.start_dt = self.startTime.dateTime() # <----
self.myLongTask.end_dt = self.endTime.dateTime() # <----
print "Starting thread..."
self.myLongTask.start()
...
class TaskThread(QtCore.QThread):
...
def run(self):
# First, we read the times from the QDateTime elements in the interface
print "Getting times..."
start_datetime_unix, end_datetime_unix = time_converter_to_unix(self.start_dt, self.end_dt) # <----
# Then, we put these values in my_function
print "Executing function..."
dummy_function(self, start_datetime_unix, end_datetime_unix)
# To finish, we execute onFinished.
print "Finishing thread..."
self.taskFinished.emit()
The context:
I'm building a Graphical Interface with Qt creator and the "behaviour" file in python. A test version of my GUI is:
The expected behaviour:
I am running 2 different threads which are referred to the same function with different input arguments. With the SELECTOR button I can assign the value of 1 or 2 to a variable (and display it)
The button Start thread enables the correct thread to start (the first time).
The loop should be turned off by the stop button by modifying the global running variable.
This is my code
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui, uic
import sys
import threading
import time
import Queue
running = False
first_thread = None
second_thread = None
form_class = uic.loadUiType("simple2.ui")[0]
q = Queue.Queue()
select = 0
def action(string, queue): #function called by threads
global running
while(running):
phrase = string
if queue.qsize() < 10:
queue.put(phrase)
#else:
# print queue.qsize()
class MyWindowClass(QtGui.QMainWindow, form_class):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
#buttons
self.startButton.clicked.connect(self.start_clicked)
self.stopButton.clicked.connect(self.stop_clicked)
self.selector.clicked.connect(self.sel_click)
#variables
self.first = False
self.second = False
#queue
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.update_phrase)
self.timer.start(1)
def start_clicked(self): #start button callback
global select
if select > 0:
global running
running = True
print "started"
if (not self.first) & (select == 1):
first_thread.start()
self.first = True
if (not self.second) & (select == 2):
second_thread.start()
self.second = True
self.startButton.setEnabled(False)
self.startButton.setText('Starting...')
def stop_clicked(self): #stop button callback
global running
running = False
print "stopped"
self.startButton.setEnabled(True)
self.startButton.setText('Start Thread')
def sel_click(self): #selector button callback
global select
if select < 2:
select = select + 1
else:
select = 1
self.thread_counter.setText(str(select))
def update_phrase(self): #looping function
global running
if (not q.empty()) & running:
self.startButton.setText('Thread on')
abc = q.get()
print abc
def closeEvent(self, event):
global running
running = False
if __name__ == "__main__":
first_thread = threading.Thread(target=action, args = ("first", q))
second_thread = threading.Thread(target=action, args = ("second", q))
app = QtGui.QApplication(sys.argv)
w = MyWindowClass(None)
w.setWindowTitle('Multiple threads test in python')
w.show()
app.exec_()
For now, each thread should simple print on terminal their arguments ("First" or "Second").
If threads are started for the first time, my code works. But I would like to switch between threads infinite times.
Since threads cannot be stopped, is there a way to "pause" them?
I cannot find a solution, I hope someone will help me also with a piece of code. Thank you in advance
You can use Lock class to do that, a simple example would be:
import threading
lock = threading.Lock()
//here it will be lock
lock.acquire() # will block if lock is already held
...
then in other side do
//this will wake up
lock.release()
you can read more here http://effbot.org/zone/thread-synchronization.htm