Block and hide QDialog: Alternative to exec_()? - python

In my Qt-based application (built using PyQt 4.8.6), I have a class that is a subclass of QtGui.QDialog:
class ModelDialog(QtGui.QDialog):
...
When I run the application's user interface, I can display the QDialog like so:
def main():
app = QtGui.QApplication(sys.argv)
dialog = ModelDialog()
dialog.exec_()
According to the Qt docs and the PyQt docs, exec_() is a blocking function for this QDialog, which defaults to a modal window (which by definition prevents the user from interacting with any other windows within the application). This is exactly what happens under normal circumstances.
Recently, however, I've been working on a way to call through the entire QApplication using defaults for all input values, and not asking the user for any input. The application behaves as expected except for one single aspect: calling dialog.exec_() causes the modal dialog to be shown.
The only workaround I've been able to find has been to catch the showEvent function and to promptly hide the window, but this still allows the QDialog object to be shown for a split second:
class ModelDialog(QtGui.QDialog):
...
def showEvent(self, data=None):
self.hide()
Is there a way to prevent the modal window from being shown altogether, while continuing to block the main event loop? I'd love for there to be something like:
def main():
app = QtGui.QApplication(sys.argv)
dialog = ModelDialog()
dialog.setHideNoMatterWhat(True)
dialog.exec_()
(to that end, I tried using QWidget.setVisible(False), but dialog.exec_() sets the dialog to be visible anyways, which is expected according to the Qt docs)

Use app.exec_() instead of dialog.exec_().

Related

PyQt5 QFileDialog closes when filename clicked

I am using PyQt5 QFileDialog.getOpenFileName. I am expecting the box to remain open until the "open" button is clicked. However, when I run the code on my Linux system, the dialog box closes immediately when the file name is clicked. On a Windows system, the box behaves as expected and remains open until the 'Open' button is clicked. The results are the same with or without the QFileDialog.DontUseNativeDialog option set.
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QFileDialog
import sys
class Main(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QFileDialog Test")
button = QPushButton("Click to open file")
button.setCheckable(True)
button.clicked.connect(self.open_file)
# Set the central widget of the Window.
self.setCentralWidget(button)
def open_file(self):
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
file_name, _ = QFileDialog.getOpenFileName(None, "Open File",
"", "Python Files (*.py);;Text Files (*.txt)",options=options)
app = QApplication(sys.argv)
window = Main()
window.show()
app.exec_()
Edit:
I logged out of KDE and started an Openbox session instead, then ran the above code. QFileDialog behaved as I was expecting, and waited for me to click the Open button. This verifies the problem exists with KDE / KWin, and that the code, run under other window managers, will likely work fine.
Still isn't a real solution, but I am more informed now than I was earlier.
2nd Edit:
I found that if I change Workspace Behavior -> General Behavior -> Click Behavior from Single click, to Double click, my QFileDialog issue goes away. How to get around this would be a different topic though.
3rd Edit:
Added 'QFileDialog.DontUseNativeDialog' option to sample code.
It seems like Qt tries to respect the way the OS opens files and folders in its file manager, even when using the native dialog. That depends on the SH_ItemView_ActivateItemOnSingleClick style hint, and the only way to bypass it is to apply a proxy style.
While you could apply the style to the QFileDialog's view within its __init__ (as long as you're using the native dialog), you're using static methods, so you can only do this by setting the style to the whole QApplication.
Note that, unlike stylesheets, palette and font, styles are not propagated to children widgets, and they always use the QApplication style (or the style manually set for them).
class SingleClickWorkaroundProxy(QProxyStyle):
def styleHint(self, hint, option, widget, data):
if hint == self.SH_ItemView_ActivateItemOnSingleClick:
return False
return super().styleHint(hint, option, widget, data)
# ...
app = QApplication(sys.argv)
app.setStyle(SingleClickWorkaroundProxy())
window = Main()
window.show()
app.exec_()

signal clicked works only with static methods

I'm using PyQt5, I'm trying to create an application that involves a QPushButton. Now, when the user clicks this button, a function is supposed to be executed.
When I try to use button.clicked.connect(self.button_clicked_slot) it works as expected only and only if button_clicked_slot() is a static method, i.e. doesn't take self as an argument. In case it is a non-static method, the function isn't executed when the button is clicked.
I've tried various similar answers from StackOver and elsewhere, none of them could solve the problem I'm facing. I'm creating and using this button inside a class, the slot function is a method of this same class. I've tried to make the class inherit from QWidget and QObject, neither of those solutions worked. It always seems to boil down to using a staticmethod.
The entire code would be very big, however, here is an over-simplified code snippet with the exact same
import sys
from PyQt5 import QtWidgets, QtCore
class activity(QtWidgets.QWidget):
def __init__(self):
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
button = QtWidgets.QPushButton('test button')
button.clicked.connect(self.temp_print)
window.setCentralWidget(button)
window.show()
sys.exit(app.exec_())
#staticmethod
def temp_print():
print('Reached here')
activity()
In the above code, the method temp_print() is executed whenever the button is clicked (because it is a staticmethod). However, if I rewrite the function as:
def temp_print(self):
print('Reached here')
And suddenly, this function is never executed regardless of how many times I click the button.
In several other code snippets and the official documentation, I've seen a non-static function being used as a slot and things seem to be going smoothly for them. The code snippet I shared above is an overly-simplified version of the problem that I'm facing.
And yes, in the code that I shared above, I don't need the self parameter inside the function and thus should be able to use a static method too. However, like I mentioned, this is a simplified version of the code I'm using (my actual code is over 500+ lines and thus it would be very stupid to paste the whole thing here), but, in my actual code, I am using the self parameter and thus need a non-static function to be used as the slot for click events.
Static methods can be invoked without the need to have an instance unlike the methods of the class that need the object to be created.
In your example, app.exec_() prevents the constructor from running, so the other methods of the class such as temp_print can not be called.
So there are 2 possible solutions:
Use a lambda method to invoke temp_print:
import sys
from PyQt5 import QtWidgets
class Activity:
def __init__(self):
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
button = QtWidgets.QPushButton("test button")
button.clicked.connect(lambda: self.temp_print())
window.setCentralWidget(button)
window.show()
sys.exit(app.exec_())
def temp_print(self):
print("Reached here")
if __name__ == "__main__":
Activity()
Let the object finish building:
import sys
from PyQt5 import QtWidgets
class Activity:
def __init__(self):
self.m_app = QtWidgets.QApplication(sys.argv)
self.window = QtWidgets.QMainWindow()
button = QtWidgets.QPushButton("test button")
button.clicked.connect(self.temp_print)
self.window.setCentralWidget(button)
self.window.show()
def temp_print(self):
print("Reached here")
def run(self):
return self.m_app.exec_()
if __name__ == "__main__":
a = Activity()
sys.exit(a.run())

How to run a pyqt5 application properly?

I can't get the GUI for my application to run in the manner that I need it to. My question is, given the below criteria, how do I go about setting up and running the GUI properly. The lack of good documentation that I have found is insanely frustrating (maybe I'm looking in the wrong places?).
I have a main window in a file called MainCustomerWindow.py containing a class by the same name. This is where all the code from the qt designer is. I have another class file called GUIController. The GUIController class does just that, controls the multiple GUI windows. It is in this GUIController class that I am trying to instantiate and run the MainCustomerWindow. Here is the code I have been trying.
def setup_window(self):
APP = QtWidgets.QApplication(sys.argv)
Window = MainCustomerWindow()
Window.setupUi(QtWidgets.QMainWindow)
Window.show()
sys.exit(APP.exec_())
Just as a side note, I come from JavaFX and Swing, and don't fully understand the workflow for pyqt5. So if someone could add an explanation for that as well it would be greatly appreciated.
The class generated by Qt Designer is not a widget, it is a class used to fill an existing widget, so you must create an object in the window, assuming you have used the "Main Window" template, then the widget must be QMainWindow (if it is another maybe you should use QDialog or QWidget), then you have to create another class that belongs to the design, and using the method setupUi() you must pass the widget to fill it:
def setup_window(self):
app = QtWidgets.QApplication(sys.argv)
# create window
window = QtWidgets.QMainWindow()
ui = MainCustomerWindow()
# fill window
ui.setupUi(window)
window.show()
sys.exit(app.exec_())
Although a better option is to create a new class and have it inherit from both:
class MainWindow(QtWidgets.QMainWindow, MainCustomerWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
If you want to get detailed information I recommend you read the following:
http://pyqt.sourceforge.net/Docs/PyQt5/designer.html
You can try taking the code you have there and adding it to the main statement at the end of your app's script. You can also have this statement instantiate your class where the init method contains the setupui() call. For example:
if __name__ == '__main__':
app = QWidgets.QApplication(sys.argv)
window = QMainWindow()
main_window = MainCustomerWindow()
window.show()
sys.exit(app.exec())
This code first sets up the PyQt app as an instance of QApplication. Next it instantiates an instance of QMainWindow so that PyQt knows what to display as the main app starts. In my experience, I've put setupui() in the init method of the app class. In your case, in the init method of MainCustomerWindow Finally, window.show() tells PyQt to begin rendering the main window.

Displaying a QtGui.QMainWindow object without quiting the script afterwards

I've wrote an object that inherits from QtGui.QMainWindow (python/pyqt). It displays an image and gives me more controls. I want to use this object as additional way to plot figures in the flow of the script (like plt.show())
The problem is that displaying this object involves a code like this:
app = QtGui.QApplication(sys.argv)
mainWin = ImageViewerWindow(result) #ImageViewerWindow inherits from QtGui.QMainWindow
mainWin.show()
app.exec_()
After the "app" was closed, I can't display additional window. Is there a way to display this window, wait for it to close, and then display another window without explicitly using signals?
(signals can be used behind the scenes but I don't want to complicate the user that want to display the image with minimal number of commands)
Probably the easiet way, another way will be catching the closing event in your window.
app = QtGui.QApplication(sys.argv)
mainWin = ImageViewerWindow(result) #ImageViewerWindow inherits from QtGui.QMainWindow
mainWin.show()
differentWindow = dW() # your other window
app.aboutToQuit.connect(lambda: differentWindow.show())
app.exec_()
Another way would be adding the closeEvent method to your window class
class ImageViewerWindow(...): # or QMainWindow
...
def closeEvent(self, event):
differentWindow = dW() # your other window
differentWindow.show()

PyQt:Why a popup dialog prevents execution of other code?

I am having a little problem with a pop up dialog.I have a combobox,which when the option changes it pops up a dialog with a textedit widget,do some stuff and insert some text in the textedit widget.
This is what i use for the popup:
def function_1(self):
dialog = QDialog()
dialog.ui = Ui_Dialog_popup()
dialog.ui.setupUi(dialog)
dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
dialog.exec_()
I have the pop up gui code made in QtDesignere in a separate py file.
The popup dialog appears,but if the dialog is not closed,nothing else can be executed.Do you know how can I deal with this ? Thanks.
That's exactly what the exec method of QDialog is designed for: modal dialogs. Read the "Modal" and "Modeless dialog" sections.
If you don't the dialog to block your main UI, call show() instead of exec() (and check the modal property documentation).
Elaborating on what Mat said: The show() function immediately returns, and as dialog is local to this function, the object gets deleted as soon as "function_1" returns. You might want to make the dialog a member or global (whichever suits your requirement) so that the object stays in memory.
HTH
Since you're setting the WA_DeleteOnClose window attribute, I'm assuming you want to create a new dialog every time the function_1 method is called (which is probably a good idea).
If so, the simplest way to solve your issue (based on the code you've given), is to give your dialog a parent (so it is kept alive), and then display it modelessly using show():
def function_1(self):
dialog = QDialog(self)
dialog.ui = Ui_Dialog_popup()
dialog.ui.setupUi(dialog)
dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
dialog.show()

Categories