The protocol attribute in Tkinter allows one to run functions when the exit button of a window has been clicked (the button with the x on it, it's top right in Windows).
I'd like to run a function when the user try's to exit my application. Is there a wxPython equivalent?
snippet:
self.protocol("WM_DELETE_WINDOW", self.do_something)
When you click on the close button you are producing an EVT_CLOSE event so if you bind this event to an onClose method then you can execute whatever you want before actually closing the application. A simple example:
class ChildFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.Bind(wx.EVT_CLOSE, self.on_close)
def on_close(self, evt):
process_whatever_you_want()
self.Destroy()
Related
I know I can intercept pressing the X button with protocol("WM_DELETE_WINDOW", do_something) however I am having a hard time figuring out how to activate this button or at least the protocol that triggers when this button is pressed.
Here is the situation. I have 2 classes. My main Tk class and my Menu class. When I am setting up the command to close the program with an exit button from the menu I want this button to do exactly the same thing as the X button on the Tk class.
Now I know I could simply call the controller that was passed to the menu class and then call the method I built to handle the close event however I am trying to build this menu class in such a way that I do not need to do this from the menu class. This will allow me to use the menu class on any app I build with little to no editing.
I have not been able to find a post or some documentation that tells me how I can programmatically activate the "WM_DELETE_WINDOW" protocol.
Here is an image if it is unclear what I want. Simply I want the exit button to do exactly what the X button does.
Main class:
import tkinter as tk
import PIP_MENU
class PIP(tk.Tk):
def __init__(self):
super().__init__()
PIP_MENU.start(self)
self.protocol("WM_DELETE_WINDOW", self.handle_close)
def handle_close(self):
print("Closing")
self.quit()
if __name__ == '__main__':
PIP().mainloop()
Menu class on separate .py file:
import tkinter as tk
class Menu(tk.Menu):
def __init__(self, controller):
super().__init__()
self.controller = controller
controller.config(menu=self)
file_menu = tk.Menu(self, tearoff=0)
self.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Exit", command=self.handle_exit)
def handle_exit(self):
# What can I do here that will be handled by
# protocol "WM_DELETE_WINDOW" of the main class?
# All I can find is destroy() and quit()
# But both of these do not get handled by "WM_DELETE_WINDOW".
def start(controller):
Menu(controller)
I have not been able to find a post or some documentation that tells me how I can programmatically active the "WM_DELETE_WINDOW" protocol.
You can't. By definition, the WM_DELETE_WINDOW protocol comes from the window manager.
Catching the protocol handler is designed to give you an opportunity to override its behavior. It is not designed to be a way to trigger some code no matter how the application is destroyed. If you want to run some code when the window is destroyed, whether that is by the user clicking the control on the window frame or through some other way, the correct way to do that is to bind to the <Destroy> event on the root window.
You have to be careful, in that any binding on the root window will be triggered for every widget. Therefore, your binding should only run when event.widget is the same as the root window.
The following example illustrates the technique. There is a method handle_close which is called whenever the window is destroyed. Whether you close the window by clicking on the control on the window frame, or whether you click on the "Close me!" button, the code still runs.
import tkinter as tk
class Example(tk.Tk):
def __init__(self):
super().__init__()
self.bind("<Destroy>", self.handle_close)
button = tk.Button(self, text="Close me!", command=self.destroy)
button.pack()
def handle_close(self, event):
if event.widget == self:
print("Closing")
self.quit()
example = Example()
example.mainloop()
I don't believe there's a method that invokes a specific protocol, since protocol seems to be a specific event watch. Here's a snippet from the module's class Tk:
class Tk(Misc, Wm):
"""Toplevel widget of Tk which represents mostly the main window
of an application. It has an associated Tcl interpreter."""
def _loadtk(self):
...
self.protocol("WM_DELETE_WINDOW", self.destroy)
As you can see, by default the module itself sets the protocol to destroy(). The protocol() method only seeks to replace the default function (at the absence of a function, it just removes the function):
def wm_protocol(self, name=None, func=None):
"""Bind function FUNC to command NAME for this widget.
Return the function bound to NAME if None is given. NAME could be
e.g. "WM_SAVE_YOURSELF" or "WM_DELETE_WINDOW"."""
if callable(func):
command = self._register(func)
else:
command = func
return self.tk.call(
'wm', 'protocol', self._w, name, command)
protocol = wm_protocol
but to achieve what you want you should be able to reference back to the same handling method with this:
def handle_exit(self):
self.controller.handle_close()
Of course, this is not as versatile since you must explicitly know the handler in your main window.
Thought I have accepted Bryan's answer I did manage to come to a workaround I think is fine here.
If I pass the method that is being used to deal with window closing to my menu class and then check if something has been passed I can then decide on weather or not to use the exit method I made or self.controller.destroy() with an if statement.
Here is my solution.
Main file:
import tkinter as tk
from tkinter import messagebox
import PIP_MENU
class PIP(tk.Tk):
def __init__(self):
super().__init__()
PIP_MENU.start(self, self.handle_close)
self.protocol("WM_DELETE_WINDOW", self.handle_close)
def handle_close(self):
x = messagebox.askquestion("DERP", "Do you want to close without saving?")
if x == "yes":
self.destroy()
if __name__ == '__main__':
PIP().mainloop()
Menu file:
import tkinter as tk
class Menu(tk.Menu):
def __init__(self, controller, exit_handler=None):
super().__init__()
self.controller = controller
self.exit_handler = exit_handler
controller.config(menu=self)
file_menu = tk.Menu(self, tearoff=0)
self.add_cascade(label="File", menu=file_menu)
file_menu.add_command(label="Exit", command=self.handle_exit)
def handle_exit(self):
if self.exit_handler != None:
self.exit_handler()
else:
self.controller.quit()
def start(controller, exit_handler=None):
return Menu(controller, exit_handler)
I'm trying to launch a dialog window inside of an active application window. The difficulty I am facing is being able to interact with the active application window once the dialog window is launched.
Here is an example of my python script:
class select_output_UI(QtGui.QDialog):
def __init__(self, *args, **kwargs):
super(select_output_UI, self).__init__(*args, **kwargs)
# BUILD UI FROM FILE
ui_file = QtCore.QFile("./select_output.ui")
ui_file.open(QtCore.QFile.ReadOnly)
self.myWidget = QtUiTools.QUiLoader().load(ui_file, self)
ui_file.close()
# SIGNALS
self.myWidget.cancel_button.clicked.connect(self.cancel_button_pressed)
def cancel_button_pressed(self):
self.button_pressed = "CANCEL"
self.close()
dialog = select_output_UI(QtGui.QApplication.activeWindow())
There are 2 options I am familiar with to launch this dialog window:
dialog.show()
This option allow's me to interact with the active application window, but this option will not wait for the dialog window to close before continuing to run whatever code is underneath.
dialog.exec_()
This option does not allow me to interact with the active application window. But what it does do is wait for the dialog window to close before continuing with the rest of the code.
Is there a way to interact with the application window while the dialog window has launch and have python wait till the dialog window is closed before continuing to read the rest of my code?
Sounds like you want to connect your dialog's "OK" (or "proceed", "continue", etc.) button to a method or function containing the rest of the code you want to run. Chances are you'll want it to be a method, since I imagine the rest of the code will need access to some of the widget values on the dialog.
For example:
class select_output_UI(QtGui.QDialog):
def __init__(self, *args, **kwargs):
super(select_output_UI, self).__init__(*args, **kwargs)
# Load .ui file, etc...
self.myWidget.ok_button.clicked.connect(self.do_work)
self.myWidget.cancel_button.clicked.connect(self.reject)
def do_work(self):
print "I'm doing work!"
# Do the work...
self.accept()
dialog = select_output_UI(QtGui.QApplication.activeWindow())
dialog.show()
Alternatively, you could hook your "OK" and "Cancel" buttons up to .accept() and .reject(), respectively, and then attach your do_work() function/method to the dialog's accepted signal. However, if you approach it that way, your code will execute after the dialog is closed, rather than allowing you to close it when you see fit (or, say, leave it open if something goes wrong in the rest of your code).
I'm using python and PyGObjects (the introspection lib) for Gtk 3 here.
Consider the following code:
from gi.repository import Gtk
class InternalWidget(Gtk.Button):
def __init__(self):
super(InternalWidget, self).__init__()
self.set_size_request(100,100)
self.connect("button-press-event", self.on_press)
def on_press(self, *args):
print "The Internal Widget was clicked."
class ExternalEventBox(Gtk.EventBox):
def __init__(self):
super(ExternalEventBox, self).__init__()
self.fixed = Gtk.Fixed()
self.add(self.fixed)
self.internal_widget = InternalWidget()
self.set_size_request(200, 200)
self.connect("button-press-event", self.on_press)
self.connect("enter-notify-event", self.on_enter)
self.connect("leave-notify-event", self.on_leave)
def on_enter(self, *args):
self.fixed.put(self.internal_widget, 50,50)
self.show_all()
def on_leave(self, *args):
self.fixed.remove(self.internal_widget)
def on_press(self,*args):
print "The External Event Box was clicked."
w = Gtk.Window(Gtk.WindowType.TOPLEVEL)
w.connect("delete-event", Gtk.main_quit)
w.add(ExternalEventBox())
w.show_all()
Gtk.main()
Above, whenever the mouse enters the ExternalEventBox, a button (InternalWidget) is added to it as a child. When the mouse leaves the ExternalEventBox, the button is removed as a child of the ExternalEventBox.
Now, if you run the code (which you can), the button appears and disappears properly. However, clicking on the button, contrary to what is expected, only sends a signal to the containing ExternalEventBox, whereas the button receives no signal.
Interestingly, the expected behavior (clicking on the button actually clicks it) happens when the button, rather than being dynamically added and removed, is added once in the constructor of the event box, and never removed.
Is this a bug, or am I just missing something?
Edit: In a nutshell, I only get "The External Event Box was clicked.", but never "The Internal Widget was clicked.".
Update: I filed a bug report.
You need to set the EventBox event window to be below it's children using .set_above_child(false)
Here's the docs for it: GtkEventBox
If the window is above, all events inside the event box will go to the event box. If the window is below, events in windows of child widgets will first got to that widget, and then to its parents.
I have a CLI application, which is digging some data, in case of need, fires up a thread which creates GTK window with some information. However the CLI (main thread) still analyzes the data in the background, so there could be numerous windows created. In case I close the window, the destroy event is actually fired up, I got a debug line in CLI, but the window locks up.
Some magical command that I have to use ?
I create window like this in the main thread:
gtk.gdk.threads_init()
notifyWindow = NotifyWindow()
notifyWindow.start()
This is NotifyWindow(Thread).destroy
def destroy(self, widget, data=None):
print "destroy signal occurred"
gtk.main_quit()
This is NotifyWindow(Thread).run
def run(self):
self.window = gtk.glade.XML( "hadinfo.glade" )
self.window_main = self.window.get_widget("window_main")
if (self.window_main):
self.window_main.connect("destroy", self.destroy)
self.window_main.connect("delete_event", self.delete_event)
self.button_cancel = self.window.get_widget("button_cancel")
self.button_cancel.connect("clicked", self.destroy)
self.window.get_widget("window_main").show()
gtk.main()
using a gtk.threads_enter() and leave around your main call should help.
Take a look at the PyGtk Faq : PyGtk FAQ
I'm working on an application that will need to use a variety of Dialogs. I'm having trouble getting events bound in a way that ensures that my Dialogs are destroyed properly if someone closes the application before dismissing the dialogs. I would expect to use something like this:
class Form(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(None, -1, "Dialog")
self.Bind(wx.EVT_CLOSE, self.onClose)
self.Bind(wx.EVT_CLOSE, self.onClose, MAIN_WINDOW)
...
def onClose(self, evt):
self.Destroy()
The behavior I'm currently encountering is that if someone opens a Dialog, then closes the Application before dismissing the Dialog the Application does not exit fully. MAIN_WINDOW is a reference to the Frame that's registered as my Top Level Window. Thanks in advance!
I was attempting to use event bubbling incorrectly. The solution is to make sure the Dialogs are children of the Top Level Window so that the Application exiting forces the Dialogs to destroy as well.
class Form(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(MAIN_WINDOW, -1, "Dialog")
self.Bind(wx.EVT_CLOSE, self.onClose)
...
def onClose(self, evt):
self.Destroy()