Tkinter how to bind to shift+tab - python

I'm trying to bind the SHIFT+TAB keys, but I can't seem to get it to work. The widget I'm binding to is an Entry widget.
I've tried binding the keys with widget.bind('<Shift_Tab>', func), but I get an error message saying:
File "/usr/lib64/python3.8/tkinter/init.py", line 1337, in
_bind self.tk.call(what + (sequence, cmd))
_tkinter.TclError: bad event type or keysym "Shift_Tab"
Update
I'm still having a problem detecting SHIFT+TAB. Here is my test code. My OS is Linux. The tab key works, just not SHIFT+TAB. Seems like a simple problem to solve, so I must be going about it wrong?
I'm trying to tab between columns in a Treeview that I have overlaid widgets on a row to simulate inline editing. There can be only one active widget on a line. I keep track of what column I'm in and when the user presses SHIFT+TAB or TAB, I remove the current widget and display a new widget in the corresponding column.
Here is a link to the complete project:
The project is in one file and has no imports.
The code below is my attempt and it doesn't work.
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.title('Default Demo')
self.geometry('420x200')
wdg = ttk.Entry(self)
wdg.grid()
def tab(_):
print('Tab pressed.')
def shift_tab(_):
print('Shift tab pressed.')
wdg.bind('<Tab>', tab)
wdg.bind('<Control-ISO_Left_Tab>', shift_tab)
def main():
app = App()
app.mainloop()
if __name__ == '__main__':
main()

The following code (in python 2.7.6) should make it clear:
I hope this reference works for you
from Tkinter import *
def key(event=None):
print 'You pressed Ctrl+Shift+Tab'
root = Tk()
frame = Frame(root, width=100, height=100)
frame.focus_set()
frame.bind('<Control-Shift-KeyPress-Tab>', key)
frame.pack()
root.mainloop()
EDIT: The above works well for Windows and Mac. For Linux, use
'<Control-ISO_Left_Tab>'.

Not a direct answer and too long for a comment.
You can solve your question by yourself with a simple trick, bind <Key> to a function, and print the key event argument passed to the bind function where you can see which key is pressed or not. Try multiple combinations of keys to see what is the state and what is their keysym or keycode.
import tkinter as tk
def key_press(evt):
print(evt)
root = tk.Tk()
root.bind("<Key>", key_press)
root.mainloop()
It will output the following for pressing SHIFT + TAB combo on macOS.
<KeyPress event state=Shift keysym=Tab keycode=3145753 char='\x19' x=-5 y=-50>
Where,
state=Shift means a key event's state is on SHIFT.
keysym=Tab means tab key is pressed. If we just press SHIFT, the keysym will be Shift_L or Shift_R (shows Shift_L for both shift keys on mac).
keycode is an unique code for each key even for different key combos for example keycode for Left Shift is 131330 and keycode for TAB is 3145737 but when SHIFT + TAB is pressed the code is not the same to either, it is 3145753. (I'm not sure if these are the same code for every os but one can figure it out by printing them to the console)
Also, see all the event attributes.
Though bind '<Shift-Tab>' key combination works well on Mac, it can also be used like so...
def key_press(evt):
if evt.keycode==3145753:
print('Shift+Tab is pressed')

Here is my solution for Linux users that want to bind Shift+Tab.
def press(e):
print('Shift+Tab is pressed')
widget.bind('<ISO_Left_Tab>',press)

Related

Add event for 'windows' button in tkinter?

My Code:
from tkinter import*
root = Tk()
root.geometry("500x500")
def moti(event):
root.destroy()
root.bind("<Window>",moti)
root.mainloop()
I want to bind this key
So,how can I bind This key in windows?Thank you!
From a practical example, I was able to find the windows key is called as <Win_L>(for the left key) <Win_R> for the right one), you can find that out yourself by using this code:
import tkinter as tk
root = tk.Tk()
def event(e):
print(e.keysym)
root.bind('<Key>',event)
root.mainloop()
This will print the key name once the window has focus and you press on it.
So TL;DR: The key name for the windows key is <Win_L>. Also for reference read keysyms manual page - Tk-built-in-commands
w.bind('<Win_L>',callback)
Note: While on Windows systems you can use <Win_L>, on a ubuntu system it would be <Super_L>. So a safe method would be:
from tkinter import *
root = Tk()
def event(e):
print(f'You just clicked: {e.keysym}')
try:
root.bind('<Win_L>',event)
except TclError:
root.bind('<Super_L>',event)
root.mainloop()

Create multiple windows using one tkinter button in python

I am opening other windows from a single tkinter button as shown here:
https://www.pythontutorial.net/tkinter/tkinter-toplevel/
The code shown there is
import tkinter as tk
from tkinter import ttk
class Window(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.geometry('300x100')
self.title('Toplevel Window')
ttk.Button(self,
text='Close',
command=self.destroy).pack(expand=True)
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry('300x200')
self.title('Main Window')
# place a button on the root window
ttk.Button(self,
text='Open a window',
command=self.open_window).pack(expand=True)
def open_window(self):
window = Window(self)
window.grab_set()
if __name__ == "__main__":
app = App()
app.mainloop()
If I run this program, it is not possible to hit the "Open a window" button twice to get two Toplevel instances. I would like to get as many instances as I like to with only one button. Is this possible somehow?
Consider this line of code:
window.grab_set()
This is setting a grab on the first window that is created. That means that all events from both the keyboard and the mouse are funneled to the first window that is created. That means you can no longer click on the button in the root window until the grab has been removed. Note that if the window is destroyed, the grab is automatically removed.
Grabs are typically used when creating a modal dialog -- a dialog which requires user input before the program can continue. By doing a grab, you insure that the user can't interact with the main program until they've interacted with the dialog.
The solution is simple: remove the call to window.grab_set() if your goal is to be able to open multiple windows which can all be used at the same time.
Simply remove window.grab_set(). The grab_set() method routes all events for this application to this widget. Whatever events generated like button-click or keypress is directed to another window.
def open_window(self):
window = Window(self)

Python tk entry box not active until window deselected

I have a small Tk-based application that uses a standard layout of a window, defined in init. For one of the submenu items, I need to temporarily create a small form, which I remove after it is successfully submitted. I do this on the fly with the code in start_make_canvas in the mcve below:
import random
import tkinter
from tkinter import *
from tkinter import messagebox
from PIL import ImageTk, Image
NONE=0
TAGGING=1
MAKECANVAS=4
TAGS=["some text","some more text"]
PICS=["c:/users/rob/desktop/camera.jpg","c:/users/rob/desktop/fridge.jpg"]
class Window(Frame):
def __init__(self,master):
Frame.__init__(self, master)
self.master=master
self.mode=NONE
self.init_window()
self.start_tagging()
def start_tagging(self):
if self.photo is not None:
self.photo.destroy()
self.photo=None
messagebox.showinfo("Start tagging")
self.mode=TAGGING
self.configure_buttons()
self.show_pic()
def init_window(self):
menubar=Menu(self.master)
menu=Menu(menubar,tearoff=0)
menu.add_command(label="Start tagging",command=self.start_tagging)
menu.add_command(label="Make canvas",command=self.start_make_canvas)
menubar.add_cascade(label="Tag",menu=menu)
self.master.config(menu=menubar)
self.pack(fill=BOTH,expand=1) #take full space of root window
self.photo=None
self.tag_trk={}
row=1
for tag in TAGS:
self.tag_trk[tag]=IntVar()
Checkbutton(self,text=tag,variable=self.tag_trk[tag]).place(x=500,y=10+20*row)
row+=1
self.tag_count=StringVar()
self.button1_label=StringVar()
self.btn1=Button(self,textvariable=self.button1_label,command=self.button1_click)
self.btn1.place(x=10,y=495)
self.max_score=StringVar()
def configure_buttons(self):
if self.mode==NONE:
self.button1_label.set("Tag")
elif self.mode==TAGGING:
self.button1_label.set("Next")
elif self.mode==MAKECANVAS:
self.button1_label.set("Make")
def button1_click(self):
if self.mode==TAGGING:
self.show_pic()
elif self.mode==MAKECANVAS:
# do some things here
for e in self.form: e.destroy()
self.mode=NONE
self.configure_buttons()
elif self.mode==NONE:
self.start_tagging()
def show_pic(self):
if self.photo is not None:
self.photo.destroy()
img=ImageTk.PhotoImage(Image.open(random.choice(PICS)))
self.photo=tkinter.Label(self,image=img,borderwidth=0)
self.photo.image=img
self.photo.place(x=15,y=5)
def start_make_canvas(self):
if self.photo is not None:
self.photo.destroy()
self.photo=None
self.mode=MAKECANVAS
self.form=[]
e=Label(self,text='Max score')
e.place(x=80,y=200)
self.form.append(e)
e=Entry(self,textvariable=self.max_score,width=20)
e.place(x=180,y=200)
self.form.append(e)
self.form[1].focus_set()
self.configure_buttons()
def target_tags():
global root
root=tkinter.Tk()
root.geometry("700x570")
root.protocol("WM_DELETE_WINDOW", on_closing)
app=Window(root)
root.mainloop()
def on_closing():
global root
root.destroy()
if __name__ == "__main__":
target_tags()
The problem occurs after selecting "Make Canvas" from the menu - the form creation works just fine, except that the newly created Entry elements are not active when first created: I cannot see an insertion cursor, and typed text does not go into the entry. When I select a different window and the reselect my application window, all is fine. Is there a method I need to call after I create the form for mainloop to recognize that there are new bits to be looking after?
Note: in creating the mcve, I found that the messagebox in start_tagging was necessary to recreate the problem. Without it, everything works from the get-go. With it, the checkboxes that are created initially work fine, but the new entry box doesn't (until the window is unselected/reselected).
For some reason, the Entry control looks as if it hasn't been fully initialized yet: I cannot even click on the control and input something into it, it doesn't react. It goes to normal if I Alt-Tab out of the app and back in. Using focus_force() helps as a workaround (it's justified in this case since it's done as a response to user's affirmative action).
This could as well be a bug in Tk, or some additional step is required after .place() that place manual page "forgot" to mention. (With ttk.Entry, it's just the same, so not a case of obsolete code.) You can ask # tcl-core#lists.sourceforge.net about this.

How to bind keyboard event to Python tkMessageBox?

I'm developing a small GUI application which is required to work with a wireless presenter pointer, which has only two keys: left arrow and right arrow. I can bind keyboard event "Left" and "Right" to the root (the main window) and call a function, so most part of my application works fine.
But when I need to pop up a message box with tkMessageBox to show some information, the only way to click "OK" button with keyboard is to press "space", which is not there on my presenter pointer. It means when such a message box is popped up, the presenter have to go to the computer to either click the "OK" button with mouse, or "space" key with keyboard.
Is there any way allowing me to temporarily bind "left arrow" or "right arrow" to the "OK" button when such a message box is popped up and then restore the binding of both the keys back to there original on_click function?
As the tkMessageBox is not an object but a tcl call, you cannot overload bindings that easy. Just subclass Tkinter.Frame to get an object where keys can be bound.
Subclassing could nevertheless follow the look and feel of a MessageBox.
e.g.
#!/usr/bin/python
import Tkinter
class MyBox(Tkinter.Toplevel):
def __init__(self, *args, **kwargs):
Tkinter.Toplevel.__init__(self, *args, **kwargs)
self.__text = Tkinter.StringVar()
self.__text.set("Initialized Text")
Tkinter.Label(self, textvariable = self.__text).grid(row=0, column=0, columnspan=3, sticky=Tkinter.NW+Tkinter.SE)
Tkinter.Button(self, text="OK", command=self.release_func).grid(row=1, column=1, sticky=Tkinter.NW+Tkinter.SE)
self.bind_all("&ltKeyRelease&gt", self.release_func)
self.grid()
self.focus_set()
def set_text(self, text="NoText"):
self.__text.set(text)
self.focus_set()
def release_func(self, event=None):
# event=None necessary as we also use button binding.
self.destroy()
root = Tkinter.Tk()
messagebox = MyBox()
messagebox.set_text("Show this message")
root.mainloop()

Automatically Activate Main Window in TkInter

Is it possible to automatically activate the main window of a tkinter app? I am using Yosemite on a Mac. When the window comes up, the title bar is grayed out, and I have to click on the window before it will respond to events. The Tk manual says that event generate, "Generates a window event and arranges for it to be processed just as if it had come from the window system." I tried generating a <Button-1> event, but it had no effect. The manual goes on to say, "Certain events, such as key events, require that the window has focus to receive the event properly." I tried focus_force, but it didn't work either.
Is it possible to do what I want? Is this a Mac peculiarity? In the code below, the text changes as the mouse cursor enters and leaves the label, but the app is unresponsive until you click on the window.
import tkinter as tk
root = tk.Tk()
def visit(event):
kilroy['text'] = 'Kilroy was here.'
def gone(event):
kilroy['text'] = 'Kilroy has left the building'
def startup():
root.focus_force()
root.event_generate('<Button-1>')
frame = tk.Frame(root, width=500,height=100)
kilroy = tk.Label(frame, text="Kilroy hasn't been here.", width = 50)
kilroy.grid(row=0,column=0)
frame.grid(row=0,column=0)
kilroy.grid_propagate(0)
frame.grid_propagate(0)
kilroy.bind('<Enter>', visit)
kilroy.bind('<Leave>', gone)
root.after(100,startup)
root.mainloop()

Categories

Resources