Per the following Stackoverflow wisdom
Python/Tkinter: Trap text selection via keyboard/mouse as an event?
I tried to detect the creation of a user selection in a Text object as follows
import tkinter as tk
def handle_selection(e=None):
print('here')
try:
sel_first = input_text.index(tk.SEL_FIRST)
sel_last = input_text.index(tk.SEL_LAST)
print(sel_first, sel_last)
except:
return
def main():
window = tk.Tk()
window.title("Simple Text Editor")
input_text = tk.Text(window)
input_text.bind("<<Selection>>", handle_selection)
input_text.pack()
window.mainloop()
if __name__ == "__main__":
main()
However the event is triggered (apparently) for each horizontal pixel selected rather than when the selection is completed, which was my intention. I also tried the following
input_text.bind("<<Selection-ButtonRelease>>", handle_selection)
which wasn't triggered at all.
Is there a way to capture the event when the user releases the button after making the selection?
The following running code gives a window with a button on it . The tooltip displays when the mouse enters the button.
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtTest import QTest
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.button = QPushButton("MyButton")
global i
i = 0
self.button.setToolTip(str(i) + " seconds has passed since you move your mouse onto MyButton")
self.button.leaveEvent = self.clear()
self.layout.addWidget(self.button)
timer = QTimer(self)
timer.timeout.connect(self.start_counting)
timer.start(1000)
self.widget = QWidget()
self.widget.setLayout(self.layout)
self.setCentralWidget(self.widget)
def clear(self):
global i
i = 0
def start_counting(self):
if self.button.underMouse() == True:
global i
i = i + 1
self.button.setToolTip(str(i) + " seconds has passed since you move your mouse onto MyButton")
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit( app.exec_() )
My goal is to count the number of seconds the mouse is inside the button and live display it using the tooltip. More precisely, I need to make sure all of the following is happening:
Every time the mouse enters the button, the count starts at 0 seconds and the tooltip shows up.
While the mouse stays inside the button (stationary or moving), the tooltip stays shown with the number of seconds (the text displayed inside the tooltip) updated over time.
When the mouse leaves the button, the count is cleared to zero.
As seen in the code, I have attempted to use underMouse to achieve my goals. My attempt is a partial success as the tooltip does update itself when the mouse moves inside the button. However, the tooltip does not update itself when the mouse stays stationary inside the button. Also, the count does not seem to be cleared when the mouse moves outside of the button .
What am I missing ?
One solution is to use an event-filter to monitor the enter and leave events, and also use an elapsed-timer to get an accurate measure of how long the mouse has been over the target widget. Below is a basic demo based on your example that implements the above. It also tries to match the normal behaviour of tooltips, but if necessary you can easily adjust the code to suit your own needs:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.button = QPushButton("MyButton")
self.layout.addWidget(self.button)
self.widget = QWidget()
self.widget.setLayout(self.layout)
self.setCentralWidget(self.widget)
self.position = QPoint()
self.counter = QElapsedTimer()
self.timer = QTimer()
self.timer.timeout.connect(self.start_counting)
self.button.installEventFilter(self)
def eventFilter(self, source, event):
if event.type() == QEvent.Enter and source is self.button:
self.counter.start()
self.timer.start(1000)
QTimer.singleShot(500, self.start_counting)
elif event.type() == QEvent.Leave and source is self.button:
self.timer.stop()
QToolTip.hideText()
return super().eventFilter(source, event)
def start_counting(self):
if self.button.underMouse():
if not QToolTip.isVisible():
self.position = QCursor.pos()
count = int(self.counter.elapsed() / 1000)
QToolTip.showText(self.position, (
f'{count} seconds have passed since '
'you moved your mouse onto MyButton'
))
if __name__ == "__main__":
app = QApplication(sys.argv)
mainWin = MainWindow()
mainWin.show()
sys.exit( app.exec_() )
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()
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()
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.