Avoiding Closing a GTK Dialog - dialog

Is there any way to run code after the OK button is pressed but before the dialog is closed in a GTK dialog? I want to be able to syntax check some code entered into the dialog after the OK button is pressed, with the option to keep the dialog open if the code doesn't compile. After a bit of googling I was able to find How to avoid closing of Gtk.Dialog in Python?, but the answer was regrettably short of details, so I couldn't figure out how to implement this. How does one go about doing this?
EDIT: Although the linked question asks about Python specifically, I don't actually care about any particular language. I'm using the Haskell bindings, but I'm fine with answers in any language with GTK+ bindings.
EDIT: If you find this question trying to figure out how to do validation, but don't have the complex requirements I have, I highly recommend looking at #AlexanderDmitriev's answer below.

I'm adding another answer in order to leave previous answer valid.
I see 2 ways to achieve desired behaviour.
Use deprecated gtk_dialog_get_action_area and pack a button there
Stop signal emission to prevent GtkDialog from "seeing" that the response button was pressed.
Both ways can be found in code below. Find deprecated for 1st approach and awesome for 2nd
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class DialogExample(Gtk.Dialog):
button_state = True
def awesome_cb (button, de):
if de.button_state:
print("Awesome ok")
else:
print("Awesome Not allowed")
button.stop_emission_by_name ("clicked")
def deprecated_cb (button, de):
if de.button_state:
print("Deprecated ok")
de.response(11)
else:
print("Deprecated Not allowed");
def switch_state(button, de):
de.button_state = not de.button_state
de.dialog_ok_btn.set_sensitive (de.button_state)
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self.set_default_size(150, 100)
label = Gtk.Label("This is a dialog to display additional information")
box = self.get_content_area()
state_switcher_btn = Gtk.Button ("Switch")
state_switcher_btn.connect ("clicked", DialogExample.switch_state, self)
box.add(label)
box.add(state_switcher_btn)
hard_work_button = Gtk.Button ("deprec")
hard_work_button.connect ("clicked", DialogExample.deprecated_cb, self)
carea = self.get_action_area()
carea.add (hard_work_button)
tfb = Gtk.Button ("awesome");
tfb.connect("clicked", DialogExample.awesome_cb, self)
self.add_action_widget(tfb, 12)
self.dialog_ok_btn = self.get_widget_for_response (Gtk.ResponseType.OK)
self.show_all()
def do_response (self, response_id):
print ("Response! ID is ", response_id)
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
button = Gtk.Button("Open dialog")
button.connect("clicked", self.on_button_clicked)
self.add(button)
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
print("The OK button was clicked")
elif response == Gtk.ResponseType.CANCEL:
print("The Cancel button was clicked")
dialog.destroy()
win = DialogWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()

Looks like GtkDialog itself doesn't allow to cancel button press (which is OK from user's point of view). However, every time user changes something, you can check it and make buttons sensitive or not. I've extended code from answer to mentioned question
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class DialogExample(Gtk.Dialog):
#this variable controls, whether OK is sensitive
button_state = True
def switch_state(button, de):
print ("switcher")
de.button_state = not de.button_state
de.set_response_sensitive (Gtk.ResponseType.OK, de.button_state)
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self.set_default_size(150, 100)
label = Gtk.Label("This is a dialog to display additional information")
box = self.get_content_area()
# a button to switch OK's sensitivity
state_switcher_btn = Gtk.Button ("Switch")
state_switcher_btn.connect ("clicked", DialogExample.switch_state, self)
box.add(label)
box.add(state_switcher_btn)
self.show_all()
def do_response (self, response_id):
print ("Override! ID is ", response_id)
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
button = Gtk.Button("Open dialog")
button.connect("clicked", self.on_button_clicked)
self.add(button)
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
print("The OK button was clicked")
elif response == Gtk.ResponseType.CANCEL:
print("The Cancel button was clicked")
dialog.destroy()
win = DialogWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()

Based on the Alexander Dmitriev's hint to use button.stop_emission_by_name, I came up with this solution which is probably what you were asking for:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class MyDialog(Gtk.Dialog):
def __init__(self, *args, **kwargs):
super(MyDialog, self).__init__(*args, **kwargs)
self.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK)
self.connect("response", self._cb_response)
def _cb_response(self, widget, response_id):
if response_id == Gtk.ResponseType.OK and self._check_invalid():
msg = Gtk.MessageDialog(
parent=self,
text="There are errors in what you entered.\n\n"
"Are you sure you want to continue?",
message_type=Gtk.MessageType.QUESTION,
buttons=Gtk.ButtonsType.YES_NO,
)
response = msg.run()
msg.destroy()
if response == Gtk.ResponseType.NO:
widget.stop_emission_by_name("response")
return True
return False
def _check_invalid(self):
"""Placeholder for checking for problems"""
return True
dialog = MyDialog()
dialog.run()

I once had this as well. I decided to catch the response signal. I had a function that would handle the validation. However, the function that handles the response signal always returns True to show GTK that the signal has already been handled and the dialog doesn't close. If the dialog needed closing, I did so manually.
myDialogWindow.connect("response", validate_response)
def validate_response(dialog, response_id):
# validate
if correct:
dialog.destroy()
else:
print("Something went wrong")
return True
Though this gets the job done, I'm not certain this the most GTK'ish solution.

Related

QMessageBox Output

Have a good day to all,
I'm trying to create a exit button in menuBar(). My point is, when user click the close button, QMessageBox() will be pop up to ask QMessageBox.Yes | QMessageBox.No. According to signal, I want to close the program.
To test the code, I just use print(). However results is &No or &Yes, rather than only No or Yes. What is the reason of that ? I couldn't figure out.
Here is my code,
from PyQt5.QtWidgets import *
import sys
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.ui()
self.menu()
self.show()
def ui(self):
self.setWindowTitle("Basic")
self.setGeometry(100, 50, 1080, 640)
def menu(self):
mainmenu = self.menuBar()
filemenu = mainmenu.addMenu("File")
file_close = QAction("Close", self)
file_close.setShortcut("Ctrl+Q")
file_close.triggered.connect(self.close_func1)
filemenu.addAction(file_close)
def close_func1(self): # Ask Yes | No Question
msg = QMessageBox()
msg.setWindowTitle("Warning!")
msg.setText("Would you like to exit ?")
msg.setIcon(QMessageBox.Question)
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
msg.setDefaultButton(QMessageBox.No)
msg.buttonClicked.connect(self.close_func2)
x = msg.exec()
def close_func2(self, i): # In this section code decide to close it or not with if statement
print(i.text())
app = QApplication(sys.argv)
w = Window()
sys.exit(app.exec_())
If you want to decide the outcome of the program based on the result of the button clicked, the simplest solution is to check the result of the exec() method, which returns a StandardButton enumeration (and not a DialogCode as a QDialog normally does).
def close_func1(self): # Ask Yes | No Question
# ...
x = msg.exec()
if x == msg.Yes:
QApplication.quit()

How to allow single mouse click in Gtk.FileChooserDialog?

Below is a simplified example code for a Gtk.FileChooserDialog widget that I had adapted from the Gtk documentation. To select a file or a folder or activate any in this widget, presently I have to place the mouse pointer over the item and double click it. What I would like instead to happen is to use a single mouse click to do the selection and activation. How do I set this up for this widget?
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class FileChooserWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="FileChooser Example")
box = Gtk.Box(spacing=6)
self.add(box)
button1 = Gtk.Button("Choose File")
button1.connect("clicked", self.on_file_clicked)
box.add(button1)
def on_file_clicked(self, widget):
dialog = Gtk.FileChooserDialog("Please choose a file", self,
Gtk.FileChooserAction.OPEN,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
response = dialog.run()
if response == Gtk.ResponseType.OK:
print("Open clicked")
print("File selected: " + dialog.get_filename())
elif response == Gtk.ResponseType.CANCEL:
print("Cancel clicked")
dialog.destroy()
win = FileChooserWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()
You can use the "selection-changed" signal like this:
def selection_changed (filechooser, udata):
print ("selected ", filechooser.get_filename()) # GtkFileChooser method
if True: # some selection checking
filechooser.response(Gtk.ResponseType.OK) # GtkDialog method
dialog = Gtk.FileChooserDialog(...)
dialog.connect ("selection-changed", selection_changed, None)
response = dialog.run()
if response == Gtk.ResponseType.OK:
pass
elif response == Gtk.ResponseType.CANCEL:
pass
dialog.destroy()
But this signal is emitted every time user interacts with filechooser, even when user changes folder with breadcrumb-buttons. It will be up to you to determine, whether it's time to respond with Ok, Cancel or don't respond at all.
Futhermore, the user may be confused, that a dialog which requires pressing Ok or Enter in literally every other app behaves otherwise in your app.

App crashes when using QWidgets.QMessageBox

So I've been trying my luck with PyQT5 to give a GUI to an app I've been working on.
I've encountered an issue with QMessageBox feature.
I've been trying to create an "Exit" Action on the MenuBar of the app.
And at first I only made it exit when clicked and it worked.
Now I want to make it give a pop up message of "Are you sure?", which is exactly what the QMessageBox does. So this is my code now:
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.ui = uic.loadUi('rent_creation.ui', self)
self.home()
def home(self):
self.ui.actionExit.triggered.connect(self.close_application)
self.show()
def close_application(self):
choice = QMessageBox.question(self, 'Quit?',
"Are you sure you want to quit?",
QMessageBox.Yes | QMessageBox.No)
if choice == QMessageBox.Yes:
sys.exit()
else:
pass
Now every time I click on the Exit button when I run this code, The Python crashes.
I'm not sure what I'm doing wrong... I've been looking around the internet and it all look well.... I've tried all the variation possible of passing QmessageBox (for example I tried adding QWidgets.QMessageBox.Yes/No and it didn't fix this issue).
I've been following a tutorial on the internet where This code is practically the same as his, and it works for him in the tutorial somehow.
caveat: I am on linux, so things are likely a bit different.
However I wouldn't be surprised if the problem is related with the fact that you use sys.exit to quit the GUI. You probably should cleanly close the window, the QApplication and then exit the program.
The following example might solve your issue. Since I don't have you ui file, I just added a menu action to close the the window and connect it with the QMainWindow.close slot and then override the closeEvent method. See the comments in the code:
import sys
from PyQt5 import QtWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.home()
def home(self):
# add a menu bar with a File menu and a Close action
menu_bar = QtWidgets.QMenuBar(self)
menu = QtWidgets.QMenu('File', menu_bar)
menu_bar.addMenu(menu)
action = menu.addAction('Close')
# connect the Close action with the QMainWindow.close slot
action.triggered.connect(self.close)
self.setMenuBar(menu_bar)
def closeEvent(self, event):
"""override the QMainWindow.closeEvent method to:
* fire up a QMessageBox with a question
* accept the close event if the user click yes
* ignore it otherwise.
Parameters
----------
event : QtCloseEvent
emitted when someone or something asks to close the window
"""
if self.ask_quit():
event.accept()
else:
event.ignore()
def ask_quit(self):
choice = QtWidgets.QMessageBox.question(self, 'Quit?',
"Are you sure you want to quit?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
return choice == QtWidgets.QMessageBox.Yes
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = Window()
w.resize(250, 150)
w.move(300, 300)
w.setWindowTitle('Simple')
w.show()
sys.exit(app.exec_())
The above way of closing the window, i.e. using the closeEvent and connect the menu action to close, has the advantage that the confirmation box is opened every time someone asks to close the window, independently of the method: you get the message box also clicking on the window X button or with alt+F4
Edit: example of how to cleanly close the QApplication only from the Close menu. This should be more in line with the original behavior of the app in the question (see comment).
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.home()
def home(self):
menu_bar = QtWidgets.QMenuBar(self)
menu = QtWidgets.QMenu('File', menu_bar)
menu_bar.addMenu(menu)
action = menu.addAction('Close')
# connect the Close menu to the ``ask_quit`` slot to ask and exit the
# application on "yes"
action.triggered.connect(self.ask_quit)
self.setMenuBar(menu_bar)
def closeEvent(self, event):
"""Ignore all ways of closing"""
event.ignore()
#QtCore.pyqtSlot()
def ask_quit(self):
choice = QtWidgets.QMessageBox.question(self, 'Quit?',
"Are you sure you want to quit?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
if choice == QtWidgets.QMessageBox.Yes:
QtWidgets.QApplication.quit()

PyQt5 - How to return application to initial state after error handling with QMessageBox

Just starting out with Python3 and PyQt5 and I'm kinda stuck here.
My main window takes two ticker codes as input and, after the user presses the Show Me! button, outputs ratio averages for each of them. I created a QMessageBox with an OK button that pops up when the user enters invalid ticker codes.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import good_morning as gm
import MainUI
class MainWindow(QMainWindow, MainUI.Ui_MyStockratios):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.home()
def home(self):
#If Show Me! button is clicked, go grab_user_input()
self.show_me_btn.clicked.connect(self.grab_user_input)
def grab_user_input(self):
#Grab user input for QLineEdits
self.ticker1_value = self.ticker1_label.text()
self.ticker2_value = self.ticker2_label.text()
#Fetch the ratios and place them in a dataframe
self.kr = gm.KeyRatiosDownloader()
try:
self.kr_frame1 = self.kr.download(self.ticker1_value)
self.kr_frame2 = self.kr.download(self.ticker2_value)
#Error handling
except ValueError:
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Invalid ticker code")
msg.setInformativeText("Please verify the data you entered and try again.")
msg.setWindowTitle("Error")
msg.setStandardButtons(QMessageBox.Ok)
reply = msg.exec_()
if reply:
self.ticker2_label.clear()
self.ticker1_label.clear()
self.home()
[...]
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()
Here's my problem: I want the application to return to its' initial state after the user presses the QMessageBox's OK button, which means the QLineEdits must be cleared and the application must wait for the user to input new data and press the Show Me! button again. I cleared the QLineEdits with the clear() function, but can't seem to make the application wait for new user input.
Thanks in advance !
For future reference you're posted code is a bit incomplete. I took some liberties to get a working example. You can ignore most of the changes except for the button handler part. You only need to connect the button once. Your home() method is not needed.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
# import good_morning as gm
# import MainUI
class MainWindow(QMainWindow):#, MainUI.Ui_MyStockratios):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# self.setupUi(self)
layout = QVBoxLayout()
widget = QWidget(self)
self.setCentralWidget(widget)
widget.setLayout(layout)
self.show_me_btn = QPushButton('Show Me!', self)
layout.addWidget(self.show_me_btn)
self.ticker1_label = QLineEdit(self)
layout.addWidget(self.ticker1_label)
self.ticker2_label = QLineEdit(self)
layout.addWidget(self.ticker2_label)
# self.home()
self.show_me_btn.clicked.connect(self.grab_user_input)
# def home(self):
# #If Show Me! button is clicked, go grab_user_input()
# self.show_me_btn.clicked.connect(self.grab_user_input)
def grab_user_input(self):
#Grab user input for QLineEdits
self.ticker1_value = self.ticker1_label.text()
self.ticker2_value = self.ticker2_label.text()
# #Fetch the ratios and place them in a dataframe
# self.kr = gm.KeyRatiosDownloader()
#
# try:
# self.kr_frame1 = self.kr.download(self.ticker1_value)
# self.kr_frame2 = self.kr.download(self.ticker2_value)
#
# #Error handling
# except ValueError:
if 1:
msg = QMessageBox()
msg.setIcon(QMessageBox.Information)
msg.setText("Invalid ticker code")
msg.setInformativeText("Please verify the data you entered and try again.")
msg.setWindowTitle("Error")
msg.setStandardButtons(QMessageBox.Ok)
reply = msg.exec_()
if reply:
self.ticker2_label.clear()
self.ticker1_label.clear()
# self.home()
# [...]
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()

Pause execution until button press

I have a QStackedWidget. In the logic (not the UI) I am trying to change pages and wait there until a button on that page is pressed (basically an OK/Cancel). I pass the UI to the function in the class.
Something like this:
def func1(self, window):
window.stackedWidget.setCurrentIndex(4)
while True:
window.btn_OK.clicked.connect(self.OK_func)
window.btn_Cancel.clicked.connect(self.Can_func)
def OK_func(self, window):
do_something
window.stackedWidget.setCurrentIndex(3)
break
def Can_func(self, window):
window.stackedWidget.setCurrentIndex(3)
break
for i in range(5):
#stuff
func1(window) #this is where I want to pause
#other stuff
Now I know that I can't break with the function like that or pass the window variable through connect, but I hope that makes my point clearly enough.
A simple way to do this is to process pending events inside the loop (so the UI remains responsive), and set/unset an internal flag to control starting and stopping of the loop.
The following demo script shows a basic implementation of this idea:
import time
from PyQt4 import QtCore, QtGui
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
layout = QtGui.QVBoxLayout(self)
self.label = QtGui.QLabel(self)
layout.addWidget(self.label)
self.buttonStart = QtGui.QPushButton('Start', self)
self.buttonStart.clicked.connect(self.handleStart)
layout.addWidget(self.buttonStart)
self.buttonStop = QtGui.QPushButton('Stop', self)
self.buttonStop.clicked.connect(self.handleStop)
layout.addWidget(self.buttonStop)
self._running = False
def handleStart(self):
self.buttonStart.setDisabled(True)
self._running = True
while self._running:
self.label.setText(str(time.clock()))
QtGui.qApp.processEvents()
time.sleep(0.05)
self.buttonStart.setDisabled(False)
def handleStop(self):
self._running = False
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 200, 100)
window.show()
sys.exit(app.exec_())
Just remove while and break.
def func1(self, window):
window.stackedWidget.setCurrentIndex(4)
window.btn_OK.clicked.connect(self.OK_func)
window.btn_Cancel.clicked.connect(self.Can_func)
def OK_func(self, window):
# do_something
window.stackedWidget.setCurrentIndex(3)
def Can_func(self, window):
window.stackedWidget.setCurrentIndex(3)

Resources