Handle COM Ports in another class - python

I am creating a Python project with PyQT5 which combines a class written by me and one written by someone else. The second class is opened in a new window to display data read from serial communication. I now have the problem, that when I close my program before the second window appears, the COM Port opened by the second class while instantiation is not closed properly, so that I can't run my program again. How can I handle this COM port so that it is not opened too soon or that it is closed properly?
My code looks like this:
class SecondWindow(SecondWindowClass):
def closeThis(self):
self.ser.close()
self.close()
self.first.show()
def __init__(self, first):
#SecondWindowClass.__init__(self)
super(SecondWindow, self).__init__()
self.first = first
self.ui = WindowTemplate()
self.ui.setupUi(self)
self.show()
print("Now opening COM port")
self.ser = serial.Serial('COM1', 9600, timeout=1)
print("COM1 opened")
#line = self.ser.readline() # first line is trash
self.ser.flush()
print("flushed COM11")
self.ui.pushButton.clicked.connect(lambda x:self.closeThis())
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def showSecond(self):
self.second.show()
self.hide()
self.ui.end_button.clicked.connect(lambda x:self.nextPicture())
def __init__(self):
QtGui.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setWindowTitle('pyqtgraph example: Qt Designer')
self.ui=uic.loadUi(uiFile, self)
self.setupUi(self)
self.show()
self.second = SecondWindow(self)
self.second.hide()
self.ui.end_button.clicked.connect(lambda x:self.showSecond())
win = MainWindow()
if __name__ == '__main__':
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()

I found a solution using closeEvent:
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def closeEvent(self, evnt): # Use the closing event
if isinstance(self.second, SecondWindow): # Check if instance already exists
try: # Close port if possible
self.second.ser.close()
print("Port closed")
except(RuntimeError, TypeError, NameError):
print("no serial port")
[...]

Related

Cancel long running action with multiple dialogs in PyQt6

I'm having an personal issue with threads in PyQt6. I have used the great example in the solution of How to stop a QThread from the GUI and rebuilt it with a separate dialog and a finished event.
In theory, what I am trying to create is a Connection Dialog which opens a database connection with oracledb. Although, if I click on the connection in the connection dialog, it should not connect silently, but it rather should display a Cancel Dialog, where if I want, I can choose to cancel the current connection attempt.
This functionality should later then be used for every query against the database in my program, the connection dialog was just an example.
My current issue is, that the button on the Cancel Dialog is not displayed. Somehow the Cancel Dialog is frozen even though I use threads for the worker to connect.
For this I have created the following reduced code example:
import time
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
class ConnectionDialog(QDialog):
def __init__(self):
super().__init__()
self.database = -1
self.setFixedSize(QSize(200, 100))
# create CancelDialog but do not display yet
self.cancel_dialog = CancelDialog(self)
# creat a thread
self.thread = QThread()
self.thread.start()
# creat a worker and put it on thread
self.worker = Worker()
self.worker.moveToThread(self.thread)
# create button which triggers the cancel_dialog and therefore the connection attempt
self.btnStart = QPushButton("Connect")
self.btnStart.clicked.connect(self.start_worker)
# if the worker emits finished call helper function end_thread
self.worker.finished.connect(self.end_thread)
# create dummy layout to display the button
self.layout = QHBoxLayout()
self.layout.addWidget(self.btnStart)
self.setLayout(self.layout)
# helper function to quit and wait for the thread
def end_thread(self, connection):
self.worker.stop()
self.thread.quit()
self.thread.wait()
print(f"Connected to {connection}")
self.close()
# push connection to self.database for further use
self.database = connection
# helper function to start worker in thread
def start_worker(self):
self.cancel_dialog.show()
self.worker.task()
class CancelDialog(QDialog):
def __init__(self, parent):
super().__init__(parent)
self.setModal(True)
# create button for the cancel operation for the thread of the parent
self.btnStop = QPushButton("Cancel")
self.btnStop.clicked.connect(lambda: self.cancel_thread())
# create dummy layout to display the button
self.layout = QHBoxLayout()
self.layout.addWidget(self.btnStop)
self.setLayout(self.layout)
# helper function when pressing cancel button
def cancel_thread(self):
# stop worker
self.parent().worker.stop()
# quit thread, this time we dont want to wait for the thread
self.parent().thread.quit()
print("Canceled")
self.close()
# push error value to self.database
self.parent().database = -1
class Worker(QObject):
"Object managing the simulation"
finished = pyqtSignal(int)
def __init__(self):
super().__init__()
self._isRunning = True
self.connection = -1
def task(self):
if not self._isRunning:
self._isRunning = True
# this simulates a database connection
print("connecting...")
print("fake delay before connection")
time.sleep(3)
print("really connecting now")
self.connection = 123
print("fake delay after connection")
time.sleep(3)
print("really connecting now")
self.finished.emit(self.connection)
print("finished connecting")
def stop(self):
self._isRunning = False
if self.connection:
self.connection = 0
print("Canceled")
if __name__ == "__main__":
app = QApplication(sys.argv)
simul = ConnectionDialog()
simul.show()
sys.exit(app.exec())
I did some more research because I had some more errors, the comment of ekhumoro was also VERY helpful. I removed workers altogether and only used a class that inherits from QThread. i added another signal "started" to determine when to show the cancel dialog. maybe this is use to somebody someday :)
this is the final reduced code example:
import sys
import time
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *
class ConnectionDialog(QDialog):
def __init__(self):
super().__init__()
self.database = None
self.setFixedSize(QSize(200, 100))
# create CancelDialog but do not display yet
self.cancel_dialog = CancelDialog(self)
# creat a thread
self.thread = BackgroundThread()
# create button which triggers the cancel_dialog and therefore the connection attempt
self.btnStart = QPushButton("Connect")
self.btnStart.clicked.connect(self.thread.start)
# if started signals, show cancel_dialog
self.thread.started.connect(self.cancel_dialog.show)
# if finished signals, end thread and close cancel_dialog
self.thread.finished.connect(self.end_thread)
self.thread.finished.connect(self.cancel_dialog.close)
# create dummy layout to display the button
self.layout = QHBoxLayout()
self.layout.addWidget(self.btnStart)
self.setLayout(self.layout)
# helper function to quit and wait for the thread
def end_thread(self, connection):
self.thread.quit()
self.thread.wait()
print(f"Connected to {connection}")
# push connection to self.database for further use
self.database = connection
self.close()
class CancelDialog(QDialog):
def __init__(self, parent):
super().__init__(parent)
self.setModal(True)
# create button for the cancel operation for the thread of the parent
self.btnStop = QPushButton("Cancel")
self.btnStop.clicked.connect(lambda: self.cancel_thread())
# create dummy layout to display the button
self.layout = QHBoxLayout()
self.layout.addWidget(self.btnStop)
self.setLayout(self.layout)
# helper function when pressing cancel button
def cancel_thread(self):
# terminate thread (Warning, see documentation)
self.parent().thread.terminate()
print("Canceled thread")
# push error value to self.database
self.parent().database = None
self.close()
class BackgroundThread(QThread):
finished = pyqtSignal(int)
started = pyqtSignal()
def __init__(self, parent=None):
super().__init__(parent)
def run(self):
self.started.emit()
print("connecting...")
print("fake delay before connection")
time.sleep(3)
print("really connecting now")
self.connection = 123
print("fake delay after connection")
time.sleep(3)
print("done")
self.finished.emit(self.connection)
print("finished connecting")
if __name__ == "__main__":
app = QApplication(sys.argv)
simul = ConnectionDialog()
simul.show()
ret = app.exec()
if simul.database:
print("Accepted")
else:
print("Rejected")
print(simul.database)

python launch editor externally and get texts

I'm writing a PyQt programe where I'd like to allow the user to launch their preferred editor to fill in a TextEdit field.
So the goal is to launch an editor (say vim) externally on a tmp file, and upon editor closing, get its contexts into a python variable.
I've found a few similar questions like Opening vi from Python, call up an EDITOR (vim) from a python script, invoke an editor ( vim ) in python. But they are all in a "blocking" manner that works like the git commit command. What I am after is a "non-blocking" manner (because it is a GUI), something like the "Edit Source" function in zimwiki.
My current attempt:
import os
import tempfile
import threading
import subprocess
def popenAndCall(onExit, popenArgs):
def runInThread(onExit, popenArgs):
tmppath=popenArgs[-1]
proc = subprocess.Popen(popenArgs)
# this immediately finishes OPENING vim.
rec=proc.wait()
print('# <runInThread>: rec=', rec)
onExit(tmppath)
os.remove(tmppath)
return
thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
thread.start()
return thread
def openEditor():
fd, filepath=tempfile.mkstemp()
print('filepath=',filepath)
def cb(tmppath):
print('# <cb>: cb tmppath=',tmppath)
with open(tmppath, 'r') as tmp:
lines=tmp.readlines()
for ii in lines:
print('# <cb>: ii',ii)
return
with os.fdopen(fd, 'w') as tmp:
cmdflag='--'
editor_cmd='vim'
cmd=[os.environ['TERMCMD'], cmdflag, editor_cmd, filepath]
print('#cmd = ',cmd)
popenAndCall(cb, cmd)
print('done')
return
if __name__=='__main__':
openEditor()
I think it failed because the Popen.wait() only waits until the editor is opened, not until its closing. So it captures nothing from the editor.
Any idea how to solve this? Thanks!
EDIT:
I found this answer which I guess is related. I'm messing around trying to let os wait for the process group, but it's still not working. Code below:
def popenAndCall(onExit, popenArgs):
def runInThread(onExit, popenArgs):
tmppath=popenArgs[-1]
proc = subprocess.Popen(popenArgs, preexec_fn=os.setsid)
pid=proc.pid
gid=os.getpgid(pid)
#rec=proc.wait()
rec=os.waitid(os.P_PGID, gid, os.WEXITED | os.WSTOPPED)
print('# <runInThread>: rec=', rec, 'pid=',pid, 'gid=',gid)
onExit(tmppath)
os.remove(tmppath)
return
thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
thread.start()
return thread
I assume this gid=os.getpgid(pid) gives me the id of the group, and os.waitid() wait for the group. I also tried os.waitpid(gid, 0), didn't work either.
I'm on the right track?
UPDATE:
It seems that for some editors that works, like xed. vim and gvim both fails.
With QProcess you can launch a process without blocking the Qt event loop.
In this case I use xterm since I do not know which terminal is established in TERMCMD.
from PyQt5 import QtCore, QtGui, QtWidgets
class EditorWorker(QtCore.QObject):
finished = QtCore.pyqtSignal()
def __init__(self, command, parent=None):
super(EditorWorker, self).__init__(parent)
self._temp_file = QtCore.QTemporaryFile(self)
self._process = QtCore.QProcess(self)
self._process.finished.connect(self.on_finished)
self._text = ""
if self._temp_file.open():
program, *arguments = command
self._process.start(
program, arguments + [self._temp_file.fileName()]
)
#QtCore.pyqtSlot()
def on_finished(self):
if self._temp_file.isOpen():
self._text = self._temp_file.readAll().data().decode()
self.finished.emit()
#property
def text(self):
return self._text
def __del__(self):
self._process.kill()
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._button = QtWidgets.QPushButton(
"Launch VIM", clicked=self.on_clicked
)
self._text_edit = QtWidgets.QTextEdit(readOnly=True)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self._button)
lay.addWidget(self._text_edit)
#QtCore.pyqtSlot()
def on_clicked(self):
worker = EditorWorker("xterm -e vim".split(), self)
worker.finished.connect(self.on_finished)
#QtCore.pyqtSlot()
def on_finished(self):
worker = self.sender()
prev_cursor = self._text_edit.textCursor()
self._text_edit.moveCursor(QtGui.QTextCursor.End)
self._text_edit.insertPlainText(worker.text)
self._text_edit.setTextCursor(prev_cursor)
worker.deleteLater()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
I guess in your case you should change
"xterm -e vim".split()
to
[os.environ['TERMCMD'], "--", "vim"]
Possible commands:
- xterm -e vim
- xfce4-terminal --disable-server -x vim
Update:
Implementing the same logic that you use with pyinotify that is to monitor the file, but in this case using QFileSystemWatcher which is a multiplatform solution:
from PyQt5 import QtCore, QtGui, QtWidgets
class EditorWorker(QtCore.QObject):
finished = QtCore.pyqtSignal()
def __init__(self, command, parent=None):
super(EditorWorker, self).__init__(parent)
self._temp_file = QtCore.QTemporaryFile(self)
self._process = QtCore.QProcess(self)
self._text = ""
self._watcher = QtCore.QFileSystemWatcher(self)
self._watcher.fileChanged.connect(self.on_fileChanged)
if self._temp_file.open():
self._watcher.addPath(self._temp_file.fileName())
program, *arguments = command
self._process.start(
program, arguments + [self._temp_file.fileName()]
)
#QtCore.pyqtSlot()
def on_fileChanged(self):
if self._temp_file.isOpen():
self._text = self._temp_file.readAll().data().decode()
self.finished.emit()
#property
def text(self):
return self._text
def __del__(self):
self._process.kill()
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._button = QtWidgets.QPushButton(
"Launch VIM", clicked=self.on_clicked
)
self._text_edit = QtWidgets.QTextEdit(readOnly=True)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self._button)
lay.addWidget(self._text_edit)
#QtCore.pyqtSlot()
def on_clicked(self):
worker = EditorWorker("gnome-terminal -- vim".split(), self)
worker.finished.connect(self.on_finished)
#QtCore.pyqtSlot()
def on_finished(self):
worker = self.sender()
prev_cursor = self._text_edit.textCursor()
self._text_edit.moveCursor(QtGui.QTextCursor.End)
self._text_edit.insertPlainText(worker.text)
self._text_edit.setTextCursor(prev_cursor)
worker.deleteLater()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
The issue I reproduced is that proc is the gnome-terminal process and not the vim process.
Here are the two options that work for me.
1) Find the process of your text editor and not that of your terminal. With the right process ID, the code can wait for the process of your text editor to finish.
With psutil (portable)
Finds the latest editor process in the list of all running processes.
import psutil
def popenAndCall(onExit, popenArgs):
def runInThread(onExit, popenArgs):
tmppath=popenArgs[-1]
editor_cmd=popenArgs[-2] # vim
proc = subprocess.Popen(popenArgs)
proc.wait()
# Find the latest editor process in the list of all running processes
editor_processes = []
for p in psutil.process_iter():
try:
process_name = p.name()
if editor_cmd in process_name:
editor_processes.append((process_name, p.pid))
except:
pass
editor_proc = psutil.Process(editor_processes[-1][1])
rec=editor_proc.wait()
print('# <runInThread>: rec=', rec)
onExit(tmppath)
os.remove(tmppath)
return
thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
thread.start()
return thread
Without psutil (works on Linux, but not portable to Mac OS or Windows)
Draws from https://stackoverflow.com/a/2704947/241866 and the source code of psutil.
def popenAndCall(onExit, popenArgs):
def runInThread(onExit, popenArgs):
tmppath=popenArgs[-1]
editor_cmd=popenArgs[-2] # vim
proc = subprocess.Popen(popenArgs)
proc.wait()
# Find the latest editor process in the list of all running processes
pids = [pid for pid in os.listdir('/proc') if pid.isdigit()]
editor_processes = []
for pid in pids:
try:
process_name = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read().split('\0')[0]
if editor_cmd in process_name:
editor_processes.append((process_name, int(pid)))
except IOError:
continue
editor_proc_pid = editor_processes[-1][1]
def pid_exists(pid):
try:
os.kill(pid, 0)
return True
except:
return
while True:
if pid_exists(editor_proc_pid):
import time
time.sleep(1)
else:
break
onExit(tmppath)
os.remove(tmppath)
return
thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
thread.start()
return thread
2) As a last resort, you can catch a UI event before updating the text:
def popenAndCall(onExit, popenArgs):
def runInThread(onExit, popenArgs):
tmppath=popenArgs[-1]
proc = subprocess.Popen(popenArgs)
# this immediately finishes OPENING vim.
rec=proc.wait()
raw_input("Press Enter") # replace this with UI event
print('# <runInThread>: rec=', rec)
onExit(tmppath)
os.remove(tmppath)
return
thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
thread.start()
return thread
I think #eyllanesc's solution is very close to what zim is doing (zim is using GObject.spawn_async() and GObject.child_watch_add(), I've no experiences with GObject, I guess that's the equivalent to QProcess.start()). But we run into some problems regarding how some terminals (like gnome-terminal) handles new terminal session launching.
I tried to monitor the temporary file opened by the editor, and on writing/saving the temp file I could call my callback. The monitoring is done using pyinotify. I've tried gnome-terminal, xterm, urxvt and plain gvim, all seem to work.
Code below:
import threading
from PyQt5 import QtCore, QtGui, QtWidgets
import pyinotify
class EditorWorker(QtCore.QObject):
file_close_sig = QtCore.pyqtSignal()
edit_done_sig = QtCore.pyqtSignal()
def __init__(self, command, parent=None):
super(EditorWorker, self).__init__(parent)
self._temp_file = QtCore.QTemporaryFile(self)
self._process = QtCore.QProcess(self)
#self._process.finished.connect(self.on_file_close)
self.file_close_sig.connect(self.on_file_close)
self._text = ""
if self._temp_file.open():
program, *arguments = command
self._process.start(
program, arguments + [self._temp_file.fileName()]
)
tmpfile=self._temp_file.fileName()
# start a thread to monitor file saving/closing
self.monitor_thread = threading.Thread(target=self.monitorFile,
args=(tmpfile, self.file_close_sig))
self.monitor_thread.start()
#QtCore.pyqtSlot()
def on_file_close(self):
if self._temp_file.isOpen():
print('open')
self._text = self._temp_file.readAll().data().decode()
self.edit_done_sig.emit()
else:
print('not open')
#property
def text(self):
return self._text
def __del__(self):
try:
self._process.kill()
except:
pass
def monitorFile(self, path, sig):
class PClose(pyinotify.ProcessEvent):
def my_init(self):
self.sig=sig
self.done=False
def process_IN_CLOSE(self, event):
f = event.name and os.path.join(event.path, event.name) or event.path
self.sig.emit()
self.done=True
wm = pyinotify.WatchManager()
eventHandler=PClose()
notifier = pyinotify.Notifier(wm, eventHandler)
wm.add_watch(path, pyinotify.IN_CLOSE_WRITE)
try:
while not eventHandler.done:
notifier.process_events()
if notifier.check_events():
notifier.read_events()
except KeyboardInterrupt:
notifier.stop()
return
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self._button = QtWidgets.QPushButton(
"Launch VIM", clicked=self.on_clicked
)
self._text_edit = QtWidgets.QTextEdit(readOnly=True)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self._button)
lay.addWidget(self._text_edit)
#QtCore.pyqtSlot()
def on_clicked(self):
worker = EditorWorker(["gnome-terminal", '--', "vim"], self)
worker.edit_done_sig.connect(self.on_edit_done)
#QtCore.pyqtSlot()
def on_edit_done(self):
worker = self.sender()
prev_cursor = self._text_edit.textCursor()
self._text_edit.moveCursor(QtGui.QTextCursor.End)
self._text_edit.insertPlainText(worker.text)
self._text_edit.setTextCursor(prev_cursor)
worker.deleteLater()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
BUT pyinotify only works in Linux. If you could find a cross-platform solution (at least on Mac) please let me know.
UPDATE: this doesn't seem to be robust. pyinotify reports file writing instead of just file closing. I'm depressed.

What is the correct way to read strings or send strings to a qtextbrowser from another class using python and pyqt gui?

I'm trying to send a string from one class to a qtextbrowser located in another class, my gui is built in pyqt and the code i wrote it in python 2.7.
Here is my test code:
class print_text():
def __init__(self):
text1 = "Acesta este un text de proba"
self.classMyWindow = MyWindow()
self.classMyWindow.statusText_updater("Merge ok ")
class systemValues(QThread):
def __init__(self):
QThread.__init__(self)
def __del__(self):
self.wait()
def cpuRunValue(self):
text1 = "Acesta este un text de proba"
self.classMyWindow = MyWindow()
self.classMyWindow.statusText_updater("Merge ok ")
def run(self):
self.cpuRunValue()
class MyWindow(QtGui.QMainWindow):
def __init__(self):
#QtGui.QDialog.__init__(self)
super(MyWindow, self).__init__()
file_path = os.path.abspath("im-manager.ui")
uic.loadUi(file_path, self)
self.myThread = systemValues()
self.myThread.start()
def statusText_updater(self,text):
time_print = time.strftime("%d/%m/%Y-%H:%M:%S")
read1 = self.status.toPlainText()
self.status.setText(text+" >>> "+time_print+" \n"+ read1+" ")
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.show()
# app.exec_()
sys.exit(app.exec_())
I get this error:
QPixmap: It is not safe to use pixmaps outside the GUI thread
What is the correct way to read strings or send strings to a qtextbrowser from another class?
I need this because my app needs to read some cpu and ram values on a different thread to keep my app from freezing and display a text message when the job is done.
UI File
To show data from a QThread in a window it is not necessary to create an instance, since the one you are creating will be different from the original window.
To display data from the thread to the GUI you must do it through signals and connect it to a slot that updates the data each time the signal is emitted.
In this case we will create the newMessage signal and emit it:
newMessage = QtCore.pyqtSignal(str)
#emit signal
self.newMessage.emit("Merge ok ")
Then we connect it to the statusText_updater slot
self.myThread.newMessage.connect(self.statusText_updater)
Complete code:
class systemValues(QtCore.QThread):
newMessage = QtCore.pyqtSignal(str)
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def __del__(self):
self.wait()
def cpuRunValue(self):
text1 = "Acesta este un text de proba"
self.newMessage.emit("Merge ok ")
def run(self):
self.cpuRunValue()
class MyWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent=parent)
file_path = os.path.abspath("im-manager.ui")
uic.loadUi(file_path, self)
self.myThread = systemValues()
self.myThread.newMessage.connect(self.statusText_updater)
self.myThread.start()
def statusText_updater(self,text):
time = datetime.datetime.now()
time_print = time.strftime("%d/%m/%Y-%H:%M:%S")
read1 = self.status.toPlainText()
self.status.setText(text+" >>> "+time_print+" \n"+ read1+" ")
As this is now your QThread class will only display once the message, if you want the message to be shown from time to time you must modify the run method:
def run(self):
while True:
self.cpuRunValue()
time.sleep(1)
This error is occurring because of your threading, not just because you have multiple classes. Whenever threading, you should always use signals and slots to communicate changes within the GUI thread that you would like to trigger. Check out this link for comprehensive examples:
http://pythoncentral.io/pysidepyqt-tutorial-creating-your-own-signals-and-slots/

Calling QMainWindow From Second QDialog

My PyQt application starts with Login screen. If password OK, a module-screen (with icons) appears. When user click some button, a QMainWindow will appears. But I can't do this because of qmainwindow object has no attribute '_exec' error. This is my code:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
...
...
class Login(QDialog):
def __init__(self, parent=None):
super(Login, self).__init__(parent)
...
...
uyg=QApplication(sys.argv)
class icons(QDialog):
def __init__(self, parent=None):
super(icons, self).__init__(parent)
...
self.buton = QPushButton()
self.buton.pressed.connect(self.open)
...
def open(self):
dialogmain = Main()
dialogmain._exec() #or dialogmain.show() ???
self.accept()
self.close()
uyg.exec_()
if Login().exec_() == QDialog.Accepted:
dialog = icons()
dialog.exec_()
else:
uyg.quit()
What am I doing wrong? Thank you.
Lately i have done the similar work:I have a loging window and a main window ,and I used something like a FSM to switch between the loging and main window.
Let's say we have 3 state:loging,main,quit.
STATE_LOGING = 0
STATE_MAIN = 1
STATE_QUIT = 2
STATE_DESTROY = 3 #this is a flag
class CState():
sigSwitchState = pyqtSignal(int)
def __init__(self):
super(CState,self).__init__()
def start(self):
pass
def sendQuit(self,nextstate):
self.sigSwitch.emit(nextstate)
class CLoginState(CState):
def __init__(self):
super(CLoginState,self).__init__()
def start(self):
w = Loging()
w.show()
def whenPasswdOk(self):
self.sendQuit(STATE_MAIN)
class CMainState(CState):
def __init__(self):
super(CMainState,self).__init__()
def start(self):
w = MainWindow()
w.show()
def whenMainWindowQuit(self):
self.sendQuit(STATE_QUIT)
class CQuitState(CState):
def __init__(self):
super(CQuitState,self).__init__()
def start(self):
#do some clean stuff...
pass
def whenCleanDone(self):
self.sendQuit(STATE_DESTROY)
class CMainApp():
def __init__(self):
self.app = QApplication(sys.argv)
def __CreateState(state):
if state == STATE_LOGING:
s = CLoginState()
if state == STATE_MAIN:
s = CMainState()
#... same as other state
s.sigSwitchState.connect(self.procNextState)
def procNextState(self,state):
if state == STATE_DESTROY:
QApplication().exit()
s = self.__CreateState(state)
s.start()
def run(self):
self.procNextState(STATE_LOGING)
sys.exit(self.app.exec_())
if __name__ == "__main__":
app = CMainApp()
app.run()
Apart from the application object and QDrag, please pretend that exec() doesn't exist. It is an utterly confusing method that essentially never has to be used. Especially not by anyone new to Qt.
If you want to display any widget, simply show() it. If you want to be notified when a dialog was accepted, connect some code to its accepted() signal. That's all.

Can't show window from thread

I have several threads that need to work with window. Here's thread definition:
class MyThread(QtCore.QThread):
def __init__(self, id, window, mutex):
super(MyThread, self).__init__()
self.id = id
self.window = window
self.mutex = mutex
self.connect(self, QtCore.SIGNAL("load_message_input()"), self.window, QtCore.SLOT("show_input()"))
def run(self):
self.mutex.lock()
self.emit(QtCore.SIGNAL("load_message_input()"))
self.connect(self.window, QtCore.SIGNAL("got_message(QString)"), self.print_message)
self.window.input_finished.wait(self.mutex)
self.mutex.unlock()
def print_message(self, str):
print "Thread %d: %s" % (self.id, str)
And here's window definition:
class MyDialog(QtGui.QDialog):
def __init__(self, *args, **kwargs):
super(MyDialog, self).__init__(*args, **kwargs)
self.last_message = None
self.setModal(True)
self.message_label = QtGui.QLabel(u"Message")
self.message_input = QtGui.QLineEdit()
self.dialog_buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
self.dialog_buttons.accepted.connect(self.accept)
self.dialog_buttons.rejected.connect(self.reject)
self.hbox = QtGui.QHBoxLayout()
self.hbox.addWidget(self.message_label)
self.hbox.addWidget(self.message_input)
self.vbox = QtGui.QVBoxLayout()
self.vbox.addLayout(self.hbox)
self.vbox.addWidget(self.dialog_buttons)
self.setLayout(self.vbox)
self.input_finished = QtCore.QWaitCondition()
#QtCore.pyqtSlot()
def show_input(self):
self.exec_()
def on_accepted(self):
self.emit(QtCore.SIGNAL("got_message(QString)"), self.message_input.text())
self.input_finished.wakeOne()
And here's main:
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
mutex = QtCore.QMutex()
threads = []
window = test_qdialog.MyDialog()
for i in range(5):
thread = MyThread(i, window, mutex)
thread.start()
threads.append(thread)
for t in threads:
t.wait()
sys.exit(app.exec_())
I can't figure out why window isn't shown when executing the script.
Update:
For some reason other threads don't stop on line with self.mutex.lock(). Can't figure out why.
You have several problems in your code:
If you want a QThread to use slots you need to create an event loop for it (which is easy, just call QThread.exec_), but QThreads with event loops needs to be coded differently (next I'll post you an example)
You need to connect on_accepted to accepted if you want to emit the messages, unless you use the auto-connect features of Qt.
If you want to use QThread first you need to start a QApplication so for t in threads: t.wait() can't be executed before the call to QApplication.exec_ (in my example just removed it).
The last but not less important issue: If you want your threads to consume resources exclusively you should think of a consumer-producer approach (the problem is that when you emit a signal every slot will get a copy of the data and if you try to block a thread with an event loop the application just freezes, to solve the problem of consumer-producer I pass an extra mutex to the signal of the message and try to lock it [never blocking!] to know if the thread con consume the event)
As promised there is an example of how to use event loops on QThreads:
from PyQt4 import QtCore, QtGui
class MyThread(QtCore.QThread):
load_message_input = QtCore.pyqtSignal()
def __init__(self, id, window):
super(MyThread, self).__init__()
self.id = id
self.window = window
self.load_message_input.connect(self.window.show_input)
self.window.got_message.connect(self.print_message)
self.started.connect(self.do_stuff)
def run(self):
print "Thread %d: %s" % (self.id,"running")
self.exec_()
#QtCore.pyqtSlot()
def do_stuff(self):
print "Thread %d: %s" % (self.id,"emit load_message_input")
self.load_message_input.emit()
#QtCore.pyqtSlot("QString","QMutex")
def print_message(self, msg, mutex):
if mutex.tryLock():
print "Thread %d: %s" % (self.id, msg)
self.do_stuff()
class MyDialog(QtGui.QDialog):
got_message = QtCore.pyqtSignal("QString","QMutex")
def __init__(self, *args, **kwargs):
super(MyDialog, self).__init__(*args, **kwargs)
self.last_message = None
self.setModal(True)
self.message_label = QtGui.QLabel(u"Message")
self.message_input = QtGui.QLineEdit()
self.dialog_buttons = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel)
self.dialog_buttons.accepted.connect(self.accept)
self.dialog_buttons.accepted.connect(self.on_accepted)
self.dialog_buttons.rejected.connect(self.reject)
self.hbox = QtGui.QHBoxLayout()
self.hbox.addWidget(self.message_label)
self.hbox.addWidget(self.message_input)
self.vbox = QtGui.QVBoxLayout()
self.vbox.addLayout(self.hbox)
self.vbox.addWidget(self.dialog_buttons)
self.setLayout(self.vbox)
self.input_finished = QtCore.QWaitCondition()
#QtCore.pyqtSlot()
def show_input(self):
print "showing input"
window.show()
window.setModal(True)
#QtCore.pyqtSlot()
def on_accepted(self):
print "emit: ", self.message_input.text()
self.got_message.emit(self.message_input.text(), QtCore.QMutex())
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
mutex = QtCore.QMutex()
threads = []
window = MyDialog()
for i in range(5):
thread = MyThread(i, window)
thread.start()
threads.append(thread)
print "start app"
sys.exit(app.exec_())
Note: almost always the thread who receives the signal first will be the one with id 1.
My recommendation, do not use slots in your threads (which will make safe the use of mutex and wait-conditions) and implement a consumer-producer approach for the messages.
You are waiting for the threads to exit, before calling app.exec_(). You should probably monitor the threads in a GUI idle loop or connect to the thread's finished() signal.

Categories