Detecting `<Enter>` and `<Leave>` events for a frame in tkinter - python

I have a menu which when the user moves their mouse off I want to disappear. The menu consists of a Frame packed with several Label/Button widgets. I can detect the user moving their mouse off a Label/Button with simply binding to the <Enter>/<Leave events for those. But binding to the same events for the frame never triggers - I guess because the widgets on top cover up the frame so the mouse never enters it?
Is their a way to make the events propagate down to the containing Frame?
window=tkinter.Tk()
menu_frm = tkinter.Frame(window, name="menu_frm")
lbl_1 = tkinter.Label(menu_frm, text="Label", name="lbl_1")
lbl_1.pack()
lbl_2 = tkinter.Label(menu_frm, text="Label", name="lbl_2")
lbl_2.pack()
menu_frm.pack()
# These two (per label and the number of labels is likely to grow) events fire
lbl_1.bind("<Enter>", lambda _: stayopenfunction())
lbl_1.bind("<Leave>", lambda _: closefunction())
lbl_2.bind("<Enter>", lambda _: stayopenfunction())
lbl_2.bind("<Leave>", lambda _: closefunction())
# These two events do not fire
menu_frm.bind("<Enter>", lambda _: stayopenfunction())
menu_frm.bind("<Leave>", lambda _: closefunction())

I found an easy fix for what your trying to do, I was trying to do the same!
def function2(event):
-revert event-
Widget.bind("<Enter>", function1)
def function1(event):
-hover event-
Widget.bind("<Leave>", function2)
Widget.bind("<Enter>", function1)
Its a bit stupid, its not the best, but it just means that when you hover it does what its meant to and then listens for the event that reverts it, in this case ''.

If you're just trying to simplify maintenance of the callbacks on the widgets, then I don't think binding events to the containing frame is the best approach. (I guess it's possible to generate virtual events in the callbacks on the buttons and labels, and bind the virtual event to the frame, but it seems rather complicated. Maybe that's just because I have no experience with virtual events.)
Why not just subclass the label and button widgets? Look at the "Instance and Class Bindings" heading at http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm Then you'd just have two class binding to maintain, one for the labels and one for the buttons. This seems cleaner to me; it's just vanilla OOP.
Hope this helps.

Related

How to find specific pixel in tkinter window

I am working on my gui with tkinter, and I have to place stuff in precise places, i can't just use pack. Currently i write almost random numbers for width and height in place(), then run, adjust the numbers, repeat the program and check the position until the button or label fits just perfectly.
Isn't there a simpler way? Is there an option of the python launcher that allows me to point somewhere in the window and find out the coordinates?
Or maybe an extention for vcscode or a setting for pycharm?
This is about the simplest way of getting mouse coordinates
from tkinter import Tk
root = Tk()
root.bind('<Button-1>', lambda e: print(e.x, e.y))
root.mainloop()
basically .bind() binds an event (as the name suggests) to the widget and when the event is triggered .bind() method calls the given function while also passing an event argument so that has to be dealt with but it can also be used as in this case (and in most cases actually)
P.S. e is just a preference of mine when using lambda in this case and if I were to define a function as usually I would use event as argument name
As #Matiiss stated in his answer, using can use mouse coordinates.
Also, there is another event called <Motion> which you can use.
from tkinter import *
root=Tk()
lbl=Label(root,text='')
lbl.pack()
lbl1=Label(root,text='')
lbl1.pack()
root.bind("<Motion>",lambda e: [lbl.config(text=f"X pixel: {e.x}"),lbl1.config(text=f"Y pixel: {e.y}")])
root.mainloop()

Tkinter, Entry widget, is detecting input text possible?

I have an Entry widget on a simple calculator. The user can choose to enter an equation via the keypad. I was wondering if there was a way to detect a character(from the keypad in my case) being typed into the Entry widget. So, focus is on the widget, user presses '4', it comes up on the widget... can I detect this act, for basic purposes of logging the input?
Every time you press a key inside a Tkinter window, a Tkinter.Event instance is created. All you need to do is access that instance. Here is a simple script that demonstrates just how:
from Tkinter import Tk, Entry
root = Tk()
def click(key):
# print the key that was pressed
print key.char
entry = Entry()
entry.grid()
# Bind entry to any keypress
entry.bind("<Key>", click)
root.mainloop()
key (being a Tkinter.Event instance) contains many different attributes that can be used to get almost any type of data you want on the key that was pressed. I chose to use the .char attribute here, which will have the script print what each keypress is.
Yes. There are a few different ways to do this, in fact.
You can create a StringVar, attach it to the Entry, and trace it for changes; you can bind all of the relevant events; or you can add a validation command that fires at any of several different points in the sequence. They all do slightly different things.
When a user types 4, there's a key event with just the 4 in it (which doesn't let you distinguish whether the user was adding 4 to the end, or in the middle, or replacing a whole selected word, or…), and then a modification event is fired with the old text,* and then the "key" or "all" validation function is called with the (proposed) new text, and the variable is updated with the (accepted) new text (unless the validation function returned false, in which case the invalidcommand is called instead).
I don't know which one of those you want, so let's show all of them, and you can play around with them and pick the one you want.
import Tkinter as tk
root = tk.Tk()
def validate(newtext):
print('validate: {}'.format(newtext))
return True
vcmd = root.register(validate)
def key(event):
print('key: {}'.format(event.char))
def var(*args):
print('var: {} (args {})'.format(svar.get(), args))
svar = tk.StringVar()
svar.trace('w', var)
entry = tk.Entry(root,
textvariable=svar,
validate="key", validatecommand=(vcmd, '%P'))
entry.bind('<Key>', key)
entry.pack()
root.mainloop()
The syntax for variable trace callbacks is a bit complicated, and not that well documented in Tkinter; if you want to know what the first two arguments mean, you need to read the Tcl/Tk docs, and understand how Tkinter maps your particular StringVar to the Tcl name 'PY_VAR0'… Really, it's a lot easier to just build a separate function for each variable and mode you want to trace, and ignore the args.
The syntax for validation functions is even more complicated, and a lot more flexible than I've shown. For example, you can get the inserted text (which can be more than one character, in case of a paste operation), its position, and all kinds of other things… but none of this is described anywhere in the Tkinter docs, so you will need to go the Tcl/Tk docs. The most common thing you want is the proposed new text as the argument, and for that, use (vcmd, '%P').
Anyway, you should definitely play with doing a variety of different things and see what each mechanism gives you. Move the cursor around or select part of the string before typing, paste with the keyboard and with the mouse, drag and drop the selection, hit a variety of special keys, etc.
* I'm going to ignore this step, because it's different in different versions of Tk, and not very useful anyway. In cases where you really need a modified event, it's probably better to use a Text widget and bind <<Modified>>.
If you just need to do simple things without using trace module you can try
def objchangetext(self, textwidget):
print(textwidget.get()) #print text out to terminal
text1 = tk.Entry(tk.Tk())
text1.bind("<KeyRelease>", lambda event, arg=(0): objchangetext(text1))

Create a python tkinter window with no X (close) button

I'm writing a 'wizard' type Python Tkinter GUI that collects information from the user and then performs several actions based on the user's entries: file copying, DB updates, etc. The processing normally takes 30-60 seconds and during that time, I want to:
Provide the user with text updates on the activity and progress
Prevent the user from closing the app until it's finished what it's doing
I started on the route of having the text updates appear in a child window that's configured to be trainsient and using wait_window to pause the main loop until the activities are done. This worked fine for other custom dialog boxes I created which have OK/cancel buttons that call the window's destroy method. The basic approach is:
def myCustomDialog(parent,*args):
winCDLG = _cdlgWin(parent,*args)
winCDLG.showWin()
winCDLG.dlgWin.focus_set()
winCDLG.dlgWin.grab_set()
winCDLG.dlgWin.transient(parent)
winCDLG.dlgWin.wait_window(winCDLG.dlgWin)
return winCDLG.userResponse
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.userResponse = ''
def showWin(self):
#Tkinter widgets and geometry defined here
def _btnOKClick(self):
#self.userResponse assigned from user entry/entries on dialog
self.dlgWin.destroy()
def _btnCancelClick(self):
self.dlgWin.destroy()
However this approach isn't working for the new monitor-and-update dialog I want to create.
First, because there's no user-initiated action to trigger the copy/update activities and then the destroy, I have to put them either in showWin, or in another method. I've tried both ways but I'm stuck between a race condition (the code completes the copy/update stuff but then tries to destroy the window before it's there), and never executing the copy/update stuff in the first place because it hits the wait_window before I can activate the other method.
If I could figure out a way past that, then the secondary problem (preventing the user from closing the child window before the work's done) is covered by the answers below.
So... is there any kind of bandaid I could apply to make this approach work the way I want? Or do I need to just scrap this because it can't work? (And if it's the latter, is there any way I can accomplish the original goal?)
self.dlgWin.overrideredirect(1) will remove all of the buttons (make a borderless window). Is that what you're looking for?
As far as I know, window control buttons are implemented by the window manager, so I think it is not possible to just remove one of them with Tkinter (I am not 100% sure though). The common solution for this problem is to set a callback to the protocol WM_DELETE_WINDOW and use it to control the behaviour of the window:
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.dlgWin.protocol('WM_DELETE_WINDOW', self.close)
self.userResponse = ''
def close(self):
tkMessageBox.showwarning('Warning!',
'The pending action has not finished yet')
# ...

tkinter clicks on child do not propagate to parent

Why doesn't clicking on a child element propagate to the parent?
from tkinter import *
root = Tk()
def handler(event):
print('clicked at', event.x, event.y)
frame = Frame(root, width=100, height=100)
label = Label(frame, text="Label")
frame.bind('<Button-1>', handler)
frame.pack()
label.pack(side=TOP)
root.mainloop()
When I run that, clicking on the label doesn't fire the handler. I've understood that events propagate to parents by default and if you didn't want that, you'd have to return "break"
You are incorrect in your original understanding that events propagate to their parent. They do not.
Admittedly, there's an edge case for widgets which are a direct descendant of a toplevel or root window. Even there, it's not that they are propagating to their parent, but rather they are being handled by other bindings as defined by the bind tags, and by default every widget has it's toplevel window as one of it's bind tags.
If you want to set a binding to work everywhere you can use the bind_all method, since each widget has an "all" bindtag by default. Another option is to give several widgets the same bindtag (using the bindtags method), then bind to that bindtag with bind_class. Which choice you make depends on what you are trying to accomplish.
bindtags are extremely powerful -- arguably more powerful than any binding mechanisms from any other toolkit. For example, if you need to have events propagate you can do that by adjusting the bindtags of every widget to include all of its ancestors. In my experience, however, such shenanigans is rarely ever needed.
You're mistaken. "break" causes that event to not propagate to other handlers for the widget that was clicked on.
In other words, if you bound your action to label and then you bound another action to the first button onto label, both callbacks will be called (unless you return "break" from the first one to be called.)
I'm not sure of a workaround though ... (We might need to wait for BryanOakley to show up ;)

How to capture events on tkinter child widgets?

In the following block, clicking on a_frame triggers the event handler on_frame_click, but clicking on a_label which is a child of a_frame does not. Is there a way to force a_frame to trap and handle events which originated on it's children (preferably with out having to add handlers to the children directly)? I am using Python 3.2.3.
import tkinter
def on_frame_click(e):
print("frame clicked")
tk = tkinter.Tk()
a_frame = tkinter.Frame(tk, bg="red", padx=20, pady=20)
a_label = tkinter.Label(a_frame, text="A Label")
a_frame.pack()
a_label.pack()
tk.protocol("WM_DELETE_WINDOW", tk.destroy)
a_frame.bind("<Button>", on_frame_click)
tk.mainloop()
Yes, you can do what you want, but it requires a bit of work. It's not that it's not supported, it's just that it's actually quite rare to need something like this so it's not the default behavior.
TL;DR - research "tkinter bind tags"
The Tkinter event model includes the notion of "bind tags". This is a list of tags associated with each widget. When an event is received on a widget, each bind tag is checked to see if it has a binding for the event. If so, the handler is called. If not, it continues on. If a handler returns "break", the chain is broken and no more tags are considered.
By default, the bind tags for a widget are the widget itself, the widget class, the tag for the toplevel window the widget is in, and finally the special tag "all". However, you can put any tags you want in there, and you can change the order.
The practical upshot of all this? You can add your own unique tag to every widget, then add a single binding to that tag that will be processed by all widgets. Here's an example, using your code as a starting point (I added a button widget, to show this isn't something special just for frames and labels):
import Tkinter as tkinter
def on_frame_click(e):
print("frame clicked")
def retag(tag, *args):
'''Add the given tag as the first bindtag for every widget passed in'''
for widget in args:
widget.bindtags((tag,) + widget.bindtags())
tk = tkinter.Tk()
a_frame = tkinter.Frame(tk, bg="red", padx=20, pady=20)
a_label = tkinter.Label(a_frame, text="A Label")
a_button = tkinter.Button(a_frame, text="click me!")
a_frame.pack()
a_label.pack()
a_button.pack()
tk.protocol("WM_DELETE_WINDOW", tk.destroy)
retag("special", a_frame, a_label, a_button)
tk.bind_class("special", "<Button>", on_frame_click)
tk.mainloop()
For more on bindtags, you might be interested in my answer to the question How to bind self events in Tkinter Text widget after it will binded by Text widget?. The answer addresses a different question than the one here, but it shows another example of using bind tags to solve real world problems.
I can't seem to find a direct method of automatically binding to child widgets (though there are methods of binding to an entire class of widgets and to all widgets in an application), but something like this would be easy enough.
def bind_tree(widget, event, callback, add=''):
"Binds an event to a widget and all its descendants."
widget.bind(event, callback, add)
for child in widget.children.values():
bind_tree(child, event, callback, replace_callback)
Just thought of this, but you could also put a transparent widget the size of a_frame on top of everything as a child of a_frame and bind the <Button> event to that, and then you could refer to a_frame as e.widget.master in the callback in order to make it reusable if necessary. That'd likely do what you want.
Based on what it says in the Levels of Binding section of this online Tkinter reference, it sounds like it's possible because you can bind a handler to three different levels.
To summarize:
Instance Level: Bind an event to a specific widget.
Class Level: Bind an event to all widgets of a specific class.
Application Level: Widget independent -- certain events always invoke a specific handler.
For the details please refer to the first link.
Hope this helps.
Depending on what you're trying to do, you could bind everything
print(a_label.bindtags()) # ('.!frame.!label', 'Label', '.', 'all')
tk.bind_class('.', "<Button>", on_frame_click)

Categories