How to find specific pixel in tkinter window - python

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

Related

getting event late in tkinter listbox [duplicate]

This question already has an answer here:
Python tkinter listbox bind on <Button-1> only works on second click
(1 answer)
Closed 1 year ago.
I was creating simple listbox containing numbers from 0 to 9. I wanted to print number when it get clicked so i bind list box with Button-1. Im facing problem that is when ever i select any number and try to get its location using list_box.curselection() it does not print any thing(return empty tuple), if i click on on any other number then it print previous selected number. I want to get current selected number.
from tkinter import *
root = Tk()
root.title("test listbox")
list_box = Listbox(root)
list_box.pack()
for i in range(0,10):
list_box.insert("end",i)
def def_fun(event):
print(list_box.curselection())
list_box.bind("<Button-1>",def_fun)
root.mainloop()
You don't have to bind to <Button-1> or anything, there is a virtual event with Listbox that you can use here:
def def_fun(event):
print(event.widget.curselection()) # The widget that triggers the event is event.widget
list_box.bind("<<ListboxSelect>>",def_fun) # Gets triggered each time something is selected
Just in case you are wondering why the Button-1 did not work, it is because there is a delay, the delay might be due to binding order, you can read more about it here but here is a gist:
In the default case, your binding on <Key> happens before the class binding, and it is the class binding where the text is actually inserted into the widget. That is why your binding always seems to be one character behind.
Change the binding to releasing mouse button, this will also be more user friendly (for example if they accidentally clicked on a selection they didn't want to select, they can move their mouse to a one they want and only releasing will call the function):
from tkinter import Tk, Listbox
def def_fun(event=None):
print(list_box.curselection())
root = Tk()
root.title("test listbox")
list_box = Listbox(root)
list_box.pack()
for i in range(0, 10):
list_box.insert("end", i)
list_box.bind("<ButtonRelease-1>", def_fun)
root.mainloop()
Another option if you want to call the function on select is either use #CoolCloud answer or you can also set a delay like this (although it will most certainly work in 99.9% of cases, there might be a case where it doesn't):
list_box.bind("<Button-1>", lambda e: root.after(10, def_fun))
The reason is that .curselection() gets the current selection but Button-1 is triggered before anything gets selected so it will print the previous selection because that is what was selected before and where the current selection is now, and then immediately after this, it will move the current selection to the item you clicked.
Important (because it may cause hard-to-debug issues):
I strongly advise against using wildcard (*) when importing something, You should either import what You need, e.g. from module import Class1, func_1, var_2 and so on or import the whole module: import module then You can also use an alias: import module as md or sth like that, the point is that don't import everything unless You actually know what You are doing; name clashes are the issue.
Also:
I strongly suggest following PEP 8 - Style Guide for Python Code. Function and variable names should be in snake_case, class names in CapitalCase. Don't have space around = if it is used as a part of keyword argument (func(arg='value')) but use if it is used for assigning a value (variable = 'some value'). Have two blank lines around function and class declarations.

How to detect if my Tkinter windows has focus?

I'm making a tkinter app and I need to know if my windows had focus, because I will send notifications only if the windows don't have focus. I check the root protocols but I didn't find something suitable.
This question has already been answered, but the accepted answer is more complicated than it should be.
I think the cleanest solution to this problem would be the following:
def has_focus(window):
return window.focus_displayof() # returns None if the window does not have focus
if not has_focus(root):
# do something
There can be several methods of doing this depending upon how you what the function to trigger.
Let's say you want the notification to go when the window loses focus then with the help of <FocusOut> bind we can do so
...
def send_notification(*args):
"""triggers when the window loses focus."""
...
root = tk.Tk()
root.bind('<FocusOut>', send_notification)
...
Or Let us the notification function trigger different times even if the window has focus or not then we can check in the function like so
def send_notification(*args):
"""triggers when the window loses focus."""
if not focus_check.get():
...
root = tk.Tk()
focus_check = tk.BooleanVar()
root.bind('<FocusIn>', lambda _: focus_check.set(True))
root.bind('<FocusOut>', lambda _: focus_check.set(False))
I found a StackOverflow question that seems fairly similar to yours. In essence, you need to add a binding to your Tkinter application. This will only function if your application has focus. You can also use the focus_get command to figure out if your obejct has focus if you apply it to the root object.
Here's the code the answer from 2009 used: e1.bind("<Return>", handleReturn)

Reuse an existing standard binding for a different key combination

I'd like to assign a key combination to do the same as an existing standard binding.
Specifically, for a tkinter.Listbox, make Ctrl-A select all (same as Ctrl-/) and also do the same for Ctrl+<the keys corresponding to those two in my national keyboard layout>.
Since the subroutines doing that exist at Tk level and not on Python level, I cannot just .bind() to them as I would do for a Python function.
If you want to duplicate any existing binding, the first thing you need to do is understand what the original sequence is bound to. In this case, the binding <Control-/> is bound to the binding tag "Listbox" (the name of the internal widget class)
The first step is to get the existing binding by making a raw call to the Tcl interpreter:
func = root.call("bind", "Listbox", "<Control-/>")
The second step is to associate that original function to the new key combination:
root.call("bind", "Listbox", "<Control-A>", func)
Note: the above will associate the binding with all Listbox widgets rather than just a specific listbox.
You could find out from the source code what tcl command is used for the specific binding.
For Listbox's specific case, Control-/ event first activates a virtual event, <<SelectAll>>. Which then calls the Tcl command for listbox'es Tcl procedure tk::ListboxSelectAll.
Let's assign Control-A to mimic Control-/.
Generate <<SelectAll>> event, so that it calls whatever it's supposed to call:
lb.bind('<Control-Key-a>', lambda event: lb.event_generate('<<SelectAll>>'))
Or you could go directly call what <<SelectAll>> eventually calls:
lb.bind('<Control-Key-a>', lambda event: lb.tk.call('tk::ListboxSelectAll', lb))
You may want to bind for all Listbox objects:
lb.bind_class(lb.winfo_class(), '<Control-Key-a>',
lambda event: lb.tk.call('tk::ListboxSelectAll', lb))
A complete example:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
if __name__ == '__main__':
root = tk.Tk()
lb = tk.Listbox(root)
for i in range(4):
lb.insert('end', i)
lb.bind_class(lb.winfo_class(), '<Control-Key-a>',
lambda event: lb.tk.call('tk::ListboxSelectAll', lb))
# assign anything but "single" or "browse"
# so that the effect is obvious
lb['selectmode'] = 'asd'
lb.pack()
tk.mainloop()
Finally, note that you may want to bind to <Control-Key-A>, too, so the binding works even with Caps Lock on.
This will effectively bind Ctrl-Shift-A, too, which you may or may not want. Conversely, with Caps Lock on, Tk would interpret Ctrl-Shift-A as Ctrl-A.
I've asked a very similar question myself.
You can simply generate the event that you want to mimic.
Generating Control-/ event so that it handles everything from that point onward:
lb.bind('<Control-Key-a>',
lambda event:lb.event_generate('<Control-Key-slash>'))
A complete example:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
except ImportError:
import Tkinter as tk
if __name__ == '__main__':
root = tk.Tk()
lb = tk.Listbox(root)
for i in range(4):
lb.insert('end', i)
lb.bind('<Control-Key-a>',
lambda event:lb.event_generate('<Control-Key-slash>'))
# assign anything but "single" or "browse"
# so that the effect is obvious
lb['selectmode'] = 'asd'
lb.pack()
tk.mainloop()

Tkinter overrideredirect no longer receiving event bindings

I have a tinter Toplevel window that I want to come up without a frame or a titlebar and slightly transparent, and then solid when the mouse moves over the window. To do this I am using both Toplevel.overrideredirect(True) and Toplevel.attributes('-alpha', 0.75). I am binding the <Enter> and <Leave> events to a function for this.
These all work when tried separately, but when I have the overrideredirect set to True, the bindings for the mouse entering and leaving no longer works. The binding calls when I click on the window, and then when I move the mouse, but not when the curser enter or leave the window.
I have also tried binding these to a Frame, but with no further luck.
toplevel = Toplevel(root)
toplevel.overrideredirect(True)
toplevel.attributes('-alpha', 0.75)
toplevel.bind('<Enter>', lambda x: mouseMovement(command='enter'))
toplevel.bind('<Leave>', lambda x: mouseMovement(command='leave'))
def mouseMovement(command):
print('Callback: ' + command)
if command == 'enter':
toplevel.attributes('-alpha', 1)
elif command == 'leave':
toplevel.attributes('-alpha', 0.75)
I have tried using the answer to the similar question here, but this results in a window that has all the standard OS decorations, but the close, minimise, and enlarge buttons are simply disabled. Is there a way where I can get rid of the titlebar, but still keep my bindings?
On X Windows this can be handled using appropriate Extended Window Manager Hints to request the window manager to decorate the toplevel the way desired. This sounds like a splash screen window so 'splash' is likely to be appropriate here. For this use the wm_attributes -type parameter eg:
toplevel.wm_attributes('-type', 'splash')
will have the toplevel decorated as for a splash screen dialog which usually means no title bar. If you apply this to an already mapped window, you will need to withdraw and re-map (call wm_deiconify) to get the window manager to apply its settings for the new hint type.

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

Categories