How to run two GUIs in Python in parallel? - python

We are talking about python 2.7 and PyQt4.
I'm working with an open-source program for the eeg analysis called PyCorder, that received data from a system electrodes-amplifier and plot them in a GUI; we can consider it as a black box for the purposes of this question. I implemented a simple interface with Qt designer. My aim is to run both the PyCorder and my interface at the same time in a way that they can exchange data between them.
Here the lines of the code where my interface is launched:
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I guess it's common to any interface done with Qt designer;
Following, the PyCorder launching part:
def main(args):
print "Starting PyCorder, please wait ...\n"
setpriority(priority=4)
app = Qt.QApplication(args)
try:
win = None
win = MainWindow()
win.showMaximized()
if ShowConfirmationDialog:
accept = Qt.QMessageBox.warning(None, "PyCorder Disclaimer", ConfirmationText,
"Accept", "Cancel", "", 1)
if accept == 0:
win.usageConfirmed = True
app.exec_()
else:
win.close()
else:
win.usageConfirmed = True
app.exec_()
except Exception as e:
tb = GetExceptionTraceBack()[0]
Qt.QMessageBox.critical(None, "PyCorder", tb + " -> " + str(e))
if win != None:
win.close()
# show the battery disconnection reminder
if ShowBatteryReminder and win and win.usageConfirmed:
DlgBatteryInfo().exec_()
print "PyCorder terminated\n"
if __name__ == '__main__':
main(sys.argv)
Is it sufficient to know just these two parts of the respective codes to answer my question? Is there any toolbox that can be useful to reach my goal?

The best idea comes to my mind is to use multi processing libraries in order to run QT several times. multiprocessing is one of the libraries you can use.

There are a number of ways to do this, but I prefer to use a central "window manager" to acts as the owner and inter-communication hub of the two windows. Below is a sample of that technique that will need to be adapted to your needs. Note that I use Qt5. I tested on Qt5, and then attempted to translate to Qt4 without testing. It should be right, but might need a little tweeking.
from PyQt4 import QtCore, QtGui
class WindowManager(QtCore.QObject):
"""
Inheriting from QObject has benefits.
For instance, the WindowManager can not have pyQtSignal members, if needed
"""
def __init__(self):
super(WindowManager, self).__init__()
self.firstWindow = FirstWindow(self)
self.secondWindow = SecondWindow(self)
self.firstWindow.show()
self.secondWindow.show()
class FirstWindow(QtGui.QMainWindow):
def __init__(self, manager):
super(FirstWindow, self).__init__()
self.manager = manager
self.mainWidget = QtGui.QWidget()
self.setCentralWidget(self.mainWidget)
self.mainLayout = QtGui.QVBoxLayout(self.mainWidget)
self.clickyButton = QtGui.QPushButton("Click me")
self.mainLayout.addWidget(self.clickyButton)
self.clickyButton.clicked.connect(self.clickyButtonClicked)
def clickyButtonClicked(self, checked=None):
self.manager.secondWindow.setMessage("Clicky button clicked")
class SecondWindow(QtGui.QMainWindow):
def __init__(self, manager):
super(SecondWindow, self).__init__()
self.manager = manager
self.mainWidget = QtGui.QWidget()
self.setCentralWidget(self.mainWidget)
self.mainLayout = QtGui.QVBoxLayout(self.mainWidget)
self.messageBox = QtGui.QLabel()
self.mainLayout.addWidget(self.messageBox)
def setMessage(self, message):
self.messageBox.setText(message)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
manager = WindowManager()
sys.exit(app.exec_())

Related

Getting closeEvent when exiting the application

I'm trying to make a small python programs which is able to have several windows. The issue is when I try to implement a menu entry to quit the programs, closing all the windows at once. I've tried to use qApp.close() and qApp.exit() but if those allow to effectively quit the program, there is no close events generated for the windows still opened, which prevent me to save modified data or to prevent leaving the application. What's the best practice for that? I could understand not being able to cancel the exit process, but being able to propose to save modified data is something I really want.
import sys
from PyQt5.QtWidgets import *
opened_windows = set()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.create_actions()
opened_windows.add(self)
def closeEvent(self, ev):
if QMessageBox.question(self, 'Closing', 'Really close?') == QMessageBox.Yes:
ev.accept()
opened_windows.remove(self)
else:
ev.ignore()
def create_action(self, action_callback, menu, action_name):
action = QAction(action_name, self)
action.triggered.connect(action_callback)
menu.addAction(action)
def create_actions(self):
_file_menu = self.menuBar().addMenu('&File')
self.create_action(self.on_new, _file_menu, '&New')
_file_menu.addSeparator()
self.create_action(self.on_close, _file_menu, '&Close')
self.create_action(self.on_quit, _file_menu, '&Quit')
self.create_action(self.on_exit, _file_menu, '&Exit')
def on_new(self):
win = MainWindow()
win.show()
def on_close(self):
self.close()
def on_quit(self):
qApp.quit()
def on_exit(self):
qApp.exit(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
status = app.exec()
print(len(opened_windows), ' window(s) opened')
print('status = ', status)
sys.exit(status)
Currently I'm modifying on_close and on_exit like this:
def on_exit(self):
for w in opened_windows.copy():
w.on_close()
if len(opened_windows) == 0:
qApp.exit(1)
but I wonder if I'm missing a better way which would not force me to maintain a set of opened windows.
Cause
It is important to understand, that the app and the main window are related, but are not the same thing. So, when you want to close the program, don't bother closing the app. Close the main window instead. From the documentation of QCloseEvent :
Close events are sent to widgets that the user wants to close, usually by choosing "Close" from the window menu, or by clicking the X title bar button. They are also sent when you call QWidget::close() to close a widget programmatically.
Solution
Connect your exit-action's triggered signal to the close slot of your MainWindow. In your case, instead of:
self.create_action(self.on_exit, _file_menu, '&Exit')
write:
self.create_action(self.close, _file_menu, '&Exit').
Define in MainWindow a signal closed and emit it from your implementation of the closedEvent, e.g. in the place of opened_windows.remove(self)
In on_new connect win.closed to self.close
Example
Here is how I suggest you to change your code in order to implement the proposed solution:
import sys
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
closed = pyqtSignal()
def __init__(self):
super().__init__()
self.create_actions()
def closeEvent(self, ev):
if QMessageBox.question(self, 'Closing', 'Really close?') == QMessageBox.Yes:
ev.accept()
self.closed.emit()
else:
ev.ignore()
def create_action(self, action_callback, menu, action_name):
action = QAction(action_name, self)
action.triggered.connect(action_callback)
menu.addAction(action)
def create_actions(self):
_file_menu = self.menuBar().addMenu('&File')
self.create_action(self.on_new, _file_menu, '&New')
_file_menu.addSeparator()
self.create_action(self.close, _file_menu, '&Exit')
def on_new(self):
win = MainWindow()
win.show()
win.closed.connect(self.close)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWindow()
win.show()
status = app.exec()
print('status = ', status)
sys.exit(status)
Edit: I wonder how I missed it before. There is the QApplication::closeAllWindows slot which does exactly what I want and whose example is a binding to exit.
There is a way to propose to save modified data on quit and exit, the signal QCoreApplication::aboutToQuit.
Note that although the Qt documentation says that user interaction is not possible, at least with PyQt5 I could use a QMessageBox without apparent issues.

PyQt5 Interrupt Close Event from Outside the Main Gui Module

I use Qt Designer to build my GUI's and convert them to py files using pyuic5. My end goal here is to interrupt the user from closing the program when a variable == 1 and present them with an 'are you sure you want to close?' type dialog. If said variable == 0 then just close the program normally.
I have seen lots of examples on how to do this, but all of them require editing the code in the GUI module. I import my gui.py file created by pyuic5 into my main script where I do all my connections to buttons, line edits, etc.. I do this so that at anytime I can update the GUI with Qt Designer and not affect the programs functionality.
Is there a way to do this from my main script that has the GUI module from Qt Designer imported?
Example of how my main script is structured:
import philipsControlGui
import sys
def main():
MainWindow.show()
sys.exit(app.exec_())
def test():
print('test')
# Main window setup
app = philipsControlGui.QtWidgets.QApplication(sys.argv)
MainWindow = philipsControlGui.QtWidgets.QMainWindow()
ui = philipsControlGui.Ui_MainWindow()
ui.setupUi(MainWindow)
# Main window bindings
ui.onButton.clicked.connect(test)
### Can I insert something here to do: if user closes the window... do something else instead?
if __name__ == "__main__":
main()
You should create a subclass from your imported gui so you can reimplement the closeEvent method:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from philipsControlGui import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setUpUi(self)
self.ui.onButton.clicked.connect(self.test)
self._check_close = True
def test(self):
print('test')
self._check_close = not self._check_close
def closeEvent(self, event):
if self._check_close:
result = QtWidgets.QMessageBox.question(
self, 'Confirm Close', 'Are you sure you want to close?',
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if result == QtWidgets.QMessageBox.Yes:
event.accept()
else:
event.ignore()
def main():
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If there's a specific 'ExitButton' in your design, you should be able to connect it in the main code and create a pop up dialog. You would have to import the QtCore/QtGui components. I always write my GUI directly (QtDesigner is pain when it comes to these things) so I'm assuming something like this:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
[YOUR CODE]
ui.ExitButton.clicked.connect(Exit)
def Exit():
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Are you sure you want to close this window?")
msg.setWindowTitle("MessageBox demo")
msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
msg.buttonClicked.connect(msgbtn)
retval = msg.exec_()
print "value of pressed message box button:", retval

How to input in console while running Qt Application in python?

I can't input anything from the user in the terminal while running a PyQt Application.
Actually i am creating something but i can't show the whole code here so there is the main of the code, and believe that i need the fix of it only:
from PyQt4.QtGui import *
import sys
def window():
app = QApplication(sys.argv)
window = QWidget()
btn = QPushButton()
btn.setText("Input In Console")
box = QFormLayout()
box.addRow(btn)
btn.clicked.connect(input_txt)
window.setLayout(box)
window.show()
sys.exit(app.exec_())
def input_txt():
input("Enter you Name ")
if __name__ == "__main__":
window()
And while running as i press the button a disaster of loop starts:
I really tried a lot configuring the solution of this problem but all failed. Hope these information helped, if any question regarding the post then please say in comment.
I don't need this anymore but still i am posting answer for if someone else having problem with the same.
Solution : Use Threads
from PyQt4.QtGui import *
import sys,threading
def window():
app = QApplication(sys.argv)
window = QWidget()
btn = QPushButton()
btn.setText("Input In Console")
box = QFormLayout()
box.addRow(btn)
btn.clicked.connect(input_txt)
window.setLayout(box)
window.show()
sys.exit(app.exec_())
def input_txt():
thread = threading.Thread(target=input)
thread.start()
if __name__ == "__main__":
window()

PyQt application freezes if dialog rejected

I have a small application, that requires login before it starts.
But if user rejects login(press cancel button), application won't close, it's just freeze.
Here is the simplified code:
import sys
from PyQt5 import QtWidgets, QtCore
class LoginWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(LoginWindow, self).__init__(parent)
self.resize(250, 150)
self.move(500, 500)
self.setWindowTitle('Login')
self.login_input = QtWidgets.QLineEdit(self)
self.login_input.move(10, 10)
self.password_input = QtWidgets.QLineEdit(self)
self.password_input.move(10, 50)
self.password_input.setEchoMode(QtWidgets.QLineEdit.Password)
self.button_box = QtWidgets.QDialogButtonBox(self)
self.button_box.move(10, 80)
self.button_box.setOrientation(QtCore.Qt.Horizontal)
self.button_box.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel |
QtWidgets.QDialogButtonBox.Ok)
self.button_box.accepted.connect(self.login)
self.button_box.rejected.connect(self.reject)
def login(self):
self.accept()
def cancel(self):
self.reject()
class MainWindow(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.resize(250, 150)
self.move(500, 500)
self.setWindowTitle('Main')
self.input = QtWidgets.QLineEdit(self)
self.input.move(10, 10)
self.show()
def main():
app = QtWidgets.QApplication([])
l = LoginWindow()
l.show()
login_result = l.exec_()
print(login_result)
if login_result == QtWidgets.QDialog.Accepted:
m = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
What am I doing wrong?
I use python 3 and PyQt5
This happens because PySide has not processed any of it's events.
app.exec_()
This starts the main event loop that continually processes every GUI interaction. This should be called before you execute any GUI code, so the events can be processed correctly from the event Queue.
The QDialog.exec_() is a blocking operation preventing the code from continuing until it gets a response.
If you want to see the dialog items then you may be able to get around this.
QtGui.QApplication.processEvents()
This processes all of the events in the event Queue, so you would probably have to keep calling this method.
Also after you initialize your main window you will have to show the main window.
I find a way to avoid this bug:
I've changed main function:
def main():
app = QtWidgets.QApplication([])
if LoginWindow().exec_() == QtWidgets.QDialog.Accepted:
m = MainWindow()
sys.exit(app.exec_())
And it works normal, but is still can't understand, what was the root cause of the problem

What is the proper way of restarting QApplication?

I'm Working with PySide, and seeking a way to restart my Qt app. Does this depends on python, or it has to be controlled by Qt?
BY ROSTYSLAV'S SUGGESTION BELLOW :
class MyAppMainWindow(QMainWindow):
def __init__(self):
self.EXIT_CODE_REBOOT = -15123123
exit_code = self.EXIT_CODE_REBOOT
def slotReboot(self):
print "Performing application reboot.."
qApp.exit( self.EXIT_CODE_REBOOT )
def main():
currentExitCode = 0
app = QApplication(sys.argv)
ex = MyAppMainWindow()
while currentExitCode == ex.EXIT_CODE_REBOOT :
currentExitCode = app.exec_()
return currentExitCode
if __name__ == '__main__':
main()
Obviously I didn't fully understand. Pleas help.
There's a nice way presented by Qt Wiki on how to make your application restartable.
The approach is based on recreation of QApplication instance and not killing current process.
It can be easily adopted to PySide like the next snippet shows:
EXIT_CODE_REBOOT = -15123123 # you can use any unique value here
exit_code = EXIT_CODE_REBOOT # Just for making cycle run for the first time
while exit_code == EXIT_CODE_REBOOT:
exit_code = 0 # error code - no errors happened
app = QApplication(sys.argv)
...
exit_code = app.exec()
You just need to setup proper exit code through the API presenteed by QApplication before finishing your app. The you can hook up new configuration or whatever you need when new application instance is created.
The topic is quite old, but i see there is no proper solution provided. So here it is (based on what Rostyslav Dzinko suggested):
EXIT_CODE_REBOOT = -11231351
from PySide import QtGui, QtCore
import sys
class MyApp(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MyApp, self).__init__(parent)
def restart (self):
#DO stuff before restarting here
return QtCore.QCoreApplication.exit( EXIT_CODE_REBOOT )
def start_app():
exit_code = 0
while True:
try:
app = QtGui.QApplication(sys.argv)
except RuntimeError:
app = QtCore.QCoreApplication.instance()
myap = MyApp()
myap.show()
exit_code = app.exec_()
if exit_code != EXIT_CODE_REBOOT:
break
return exit_code
if __name__ == '__main__':
start_app()

Categories

Resources