Destroyed about dialog doesn't reappear properly - python

I'm working on a GUI application in Python / Glade, and have the following issue.
I am trying to get an About dialog properly working...however when I click 'Close' (in the About dialog) and then attempt to open it again, this is all I see:
So, just a tiny little snippet of the window, and a non-functioning close button.
This is my class for my Glade window:
# glade object
class MainWindow(object):
builder_ = None
# load main window
def __init__(self):
handler = {
"sigWindowDestroy" : gtk.main_quit,
"sigShowAbout" : self.show_about
}
projfile = "proj.glade"
self.builder_ = gtk.Builder()
self.builder_.add_from_file(projfile)
self.builder_.connect_signals(handler)
window = self.builder_.get_object("main_window")
window.show()
# show about dialog
def show_about(self, *args):
dAbout = self.builder_.get_object("dAbout")
dAbout.run()
dAbout.destroy()
And in my main function:
# load glade gui
app = MainWindow()
gtk.main()
On the second click, I see the following output in my terminal window (using Mac OS X).
GtkWarning: gtk_widget_show: assertion `GTK_IS_WIDGET (widget)' failed
dAbout.run()
GtkWarning: gtk_label_set_markup: assertion `GTK_IS_LABEL (label)' failed
dAbout.run()
Edit: sorry, must reopen for general unfamiliarity with PyGTK.
I've used the show()/hide() methods instead of run()/destroy() as proposed. Now, I was following along with another SO post, which highlighted this tutorial (who said to use run()/destroy()), and am seeing this behavior.
First, the Close button does nothing. I had thought for some reason its behavior was pre-defined.
Second, closing the dialog with the corner close button still provides the same behavior that I see with run()/destroy() as above.
Edit 2: Solved by adding the following:
dAbout.connect("response", lambda d, r: d.hide())

Don't try to (deep-)copy a widget. It doesn't work, as you found out.
Instead, hide() the dialog instead of destroy()ing it.

You could have even used run(). You just shouldn't use destroy(). What made you think, you shouldn't use run() and hide() together? See, when you destroy a widget, that means removing it from memory as if it never had been build. If you hide it, you can reuse it later, but take care of changes a user might have done to it, as the window will re-appear in the state it was in before being hidden. You can manipulate a widgets properties from code while hidden.
The "predefined" action of your close button was caused by run(). The solution you posted, using the lambda function is little more than what run() does for you. Basically it does the following:
Connect the "response" signal of your DialogWindow
Connect the "delete-event" signal of your DialogWindow
Start a new Gtk main loop to block the application
Show your widget
Disconnect the signals
Return the response
You just need to hide() it afterwards and are able to run() it again.

Related

PyQt5: Using DeleteOnClose when switching to new window

I am currently creating a GUI in Python 3.7, using PyQt5 and Qt Designer in the Spyder environment. The GUI has many different windows. Basically I am starting with the UI_Start window and then open the next window when a button is pressed. The GUI is working kind of fine, however after approximately 50 windows the program suddenly doesn't show the next window anymore but also doesn't stop the execution. The weird thing about this issue is that:
the exact same window class has been called a lot of times beforehand and there have never been any issues
the problem does not only occur for one window but it can also occur for another window class (but after the same amount of windows being shown)
I tried to figure out why the .show() command is suddenly not working anymore. I used print statements to see where the program "breaks down". I saw that even the print statements after the .show() command are working but then as the window isn't shown I can't press any button to trigger the next event. So basically the program is hanging.
I am relatively new to programming in Python and creating GUIs but I thought that maybe the problem occurs due to memory leak. This is why I am now trying to open memory space when closing a window by using self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True). However, now I am facing the problem that the next window doesn't show up anymore. So how can I use DeleteOnClose if I want to show a new window afterwards?
Also if anyone has a suggestion for the original problem, please let me know. I am trying to figure out the problem since like a week but have not come any further.
Thank you already!
Some part of my code to work with:
class UI_Start(QtWidgets.QMainWindow):
def __init__(self):
super(UI_Start, self).__init__() # Call the inherited classes __init__ method
uic.loadUi('Screen_Start.ui', self) # Load the .ui file
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) # added newly
self.Start_pushButton_Start.clicked.connect(self.openKommiScreen)
def openKommiScreen(self):
self.close()
self.KommiScreen = UI_Kommi(self)
class UI_Kommi(QtWidgets.QMainWindow):
def __init__(self, parent = None):
super(UI_Kommi, self).__init__(parent)
uic.loadUi('Screen_Kommi.ui', self)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
global sheetNo
sheetNo = 1
self.WeiterButton = self.findChild(QtWidgets.QPushButton,'pushButton_Weiter')
self.WeiterButton.clicked.connect(self.openScanScreen)
self.show()
def openScanScreen(self):
self.close()
self.ScanScreen = UI_Scan(self)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = UI_Start()
window.show()
sys.exit(app.exec_())
At first I would guess it's a garbage collection problem. The only reference to your new window is stored in your previous one. Which is deleted, so there is no more reference to your window object and python may delete it automatically.
In these cases I often goes for a global variable to store the current windows references.

How to handle modal dialog in pytest-qt without mocking the dialog

I am using pytest-qt to automate the testing of a PyQt GUI. The dialogs need to be handled as a part of the testing(dialogs should not be mocked).
For example, file dialog that comes after a button-click has to be handled. There are 2 problems
After the button click command, the program control goes to the event handler and not to the next line where I can try to send mouseclick/keystrokes to the dialog.
Since the QDialog is not added to the main widget, it is not being listed among the children of the main widget. So how to get the reference of the QDialog?
I tried multi-threading but that didn't work, later I found that QObjects are not thread-safe.
def test_filedialog(qtbot, window):
qtbot.mouseClick(window.browseButton, QtCore.Qt.LeftButton, delay=1)
print("After mouse click")
#This is where I need to get the reference of QDialog and handle it
It can be done using QTimer.
def test_filedialog(qtbot, window):
def handle_dialog():
# get a reference to the dialog and handle it here
QTimer.singleShot(500, handle_dialog)
qtbot.mouseClick(window.browseButton, QtCore.Qt.LeftButton, delay=1)
Refer this link for more details

PyQt - QDialogButtonBox signals and tool tip

I got a couple of questions regarding qDialogButtonBox. While my code still works, I believed that there are a few parts that can be better refined/ I am not finding much info online
class testDialog(QtGui.QDialog):
def __init_(self, parent=None):
...
self.init_ui()
self.signals_connection()
def init_ui(self):
...
self.buttonBox = QtGui.QDialogButtonBox()
self.buttonBox.addButton("Help", QtGui.QDialogButtonBox.HelpRole)
self.buttonBox.addButton("Apply", QtGui.QDialogButtonBox.AcceptRole)
self.buttonBox.addButton("Cancel", QtGui.QDialogButtonBox.RejectRole)
#
def signals_connection(self):
self.test_random.clicked.connect(self.test_rand)
# Is this the latest/correct way to write it?
self.buttonBox.accepted.connect(self.test_apply)
self.buttonBox.rejected.connect(self.test_cancel)
self.buttonBox.helpRequested.connect(self.test_help)
def test_apply(self):
print "I am clicking on Apply"
def test_cancel(self):
print "I am clicking on Cancel"
self.close()
def test_help(self):
print "I am clicking for Help!"
My questions are as follows:
Under my function - signals_connection(), the lines that I wrote for
the buttonBox (though the code still works) are quite different
for the signal I have wrote for the self.test_random and I am
unable to find any similar online for the qdialogbuttonbox.. There
is another style that I have found - self.connect(self.buttonBox,
QtCore.SIGNAL("accepted()"), self, QtCore.SLOT("accept()")) but I
think that is the old style?? Otherwise what should be the right way
to write it?
In my test_cancel() function, is writing self.close() the best
way to close the application? The way that I run my program is as
follows:
dialog = testDialog();dialog.show()
Lastly, is it possible to add 3 different tool tips to the 3 buttons I have created? I saw that there is a command for it - self.buttonBox.setToolTip("Buttons for life!"), but this will results in all 3 buttons to have the same tool tip. Can I make it as individual?
Yes, that is the correct way to write signal connections (the other syntax you found is indeed the old way of doing it). You can find all the signals in the pyqt documentation for QDialogButtonBox. Different widgets and objects have different signals. QPushButton's and QDialogButtonBox's have different signals.
Yes, close() will close the dialog. The QApplication will exit by default if there are no other windows open. However, if this is a modal dialog, you typically want to close a dialog with either the accept or reject command. This will alert the calling function as to whether the dialog was closed with the Ok/Yes/Apply button or closed with the No/Cancel button.
You can set different tooltips for different buttons in the QDialogButtonBox. You just need to get a reference to the specific button you want to set the tooltip for.
For example
self.buttonBox.button(QDialogButtonBox.Help).setToolTip('Help Tooltip')
self.buttonBox.button(QDialogButtonBox.Ok).setToolTip('Apply Tooltip')
Or you could loop through all the buttons
for button in self.buttonBox.buttons():
if button.text() == 'Help':
button.setToolTip('Help Tooltip')
elif button.text() == 'Apply':
button.setToolTip('Apply Tooltip')
Also, you could connect the accepted and rejected signals from the QDialogButtonBox to the accept and reject slots on the QDialog
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
That way, you won't have to manually connect the Ok and Cancel buttons to your callbacks for closing the dialog.

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()

Block and hide QDialog: Alternative to exec_()?

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_().

Categories