I have a class that inherits from ttk combobox. On a user doubleclick, I create this combobox and place it using .place() function
When I alt-tab and remove focus from my tkinter app, the list of combobox values remains displayed and can be interacted with, even though the rest of the gui does not have focus or visibility on my screen.
I am running windows 7.
class ComboBoxPopup(ttk.Combobox):
def __init__(self, gui_parent, item_parent, values, **kw):
''' If relwidth is set, then width is ignored '''
super().__init__(gui_parent, **kw)
self.item_parent = item_parent
self['values'] = values
self['state'] = 'normal'
self['exportselection'] = True
self.focus_force()
self.bind("<Escape>", lambda *ignore: self.destroy())
self.bind("<Return>", lambda *ignore: self.commit_and_exit() )
self.bind("<<ComboboxSelected>>", lambda *ignore: self.commit_and_exit())
EDIT:
I'm experimenting with this more, it seems like none of my events are bound to the dropdown of the combobox, just to the entryfield of the combobox. I think this is my problem.
None of the bindings I placed on my Combobox affected the drop-down listbox (I do not know the class name for that drop-down listbox).
But once I bound handlers to the alt key presses for all tkinter widget classes, I was able to get the drop-down listbox under the program's control and kill the combobox widget popup.
self.bind_all("<Alt_L>", self.configurationAbort)
self.bind_all("<Alt_R>", self.configurationAbort)
Related
What i am trying to do: I am trying to build a GUI using tkinter in Python (i am using version 3.7.7 on Windows) with a main window that has a Listbox and a second window that contains a Combobox.
In the Listbox there are different files that can be opened. When clicking on each entry some additional information are displayed in the main window (That part is not in my shorter example code below). So what i did was binding the Listbox to a function that loads and displays the information (function_1 in the code below) with the <<ListboxSelect>> event.
The second window can be opened with a button and should edit the selected file. In the Combobox in the second window are different options that change other elements of the second window depending on whats selected. Thats why i bind the <<ComboboxSelected>> event to a different function (function_2 in the code below).
My problem with this is: When i select a value in the Combobox of the second window, the first function that is binded to the Listbox of the first window is executed just after the correct function for the Combobox. This only happens the first time a value for the Combobox is selected, for each time the second window is created. Also when looking at the selected value of the Listbox it returns the selected value of the Combobox.
Edit: I just noticed this problem only happens when i previously selected an item in the Listbox. So like i said below it might have something to do that the Listbox is still active or something like that.
My code and an example:
import tkinter as tk
from tkinter import ttk
class MainGUI:
def __init__(self):
self.root = tk.Tk()
items = ['Test 1', 'Test 2', 'Test 3']
self.item_box = tk.Listbox(self.root)
self.item_box.insert('end', *items)
self.item_box.bind('<<ListboxSelect>>', self.function_1)
self.item_box.pack()
tk.Button(self.root, text='Open second window',
command=self.open_window).pack()
self.root.mainloop()
def function_1(self, event):
print('MainGUI function:', event)
print('Text in Listbox:', self.item_box.selection_get())
def open_window(self):
SecondGUI()
class SecondGUI:
def __init__(self):
self.window = tk.Toplevel()
self.window.grab_set()
items = ['A', 'B', 'C']
self.dropdown = ttk.Combobox(self.window, values=items)
self.dropdown.current(0)
self.dropdown.pack()
self.dropdown.bind('<<ComboboxSelected>>', self.function_2)
def function_2(self, event):
print('SecondGUI function:', event)
MainGUI()
Image of the GUI before and after the option B is clicked
The output after selecting Test 1, opening the second window with the button and selecting B looks like this:
MainGUI function: <VirtualEvent event x=0 y=0>
Text in Listbox: Test 1
SecondGUI function: <VirtualEvent event x=0 y=0>
MainGUI function: <VirtualEvent event x=0 y=0>
Text in Listbox: B
My guess: If i had to guess what the problem is, i would say the Combobox maybe had some sort of Listbox in it that sends the event, but as i wasn't able to find more about when and how these events are sent, i couldn't really find anything about it (Edit: Less likely). When looking at the picture, you can see that the entry in the first window is still selected until the entry in the second window is clicked, so my second guess would be that the Listbox is still active and takes the new selected value when it is clicked or something like that (Edit: more likely).
My question: Why does the first function execute when i use a different widget in a different window, how can i fix this, or are there any better way to do this in the first place?
TL;DR: A Listbox recieves an event when a Combobox is selected in a different window. Why?
Thanks in advance!
It may be due to that exportselection=False is not set for both the Listbox and Combobox. So when an item in the Combobox is selected, it will clear the selection of Listbox which triggers the <<ListboxSelect>> event.
Also you have used wrong function, self.item_box.selection_get(), to get the current selected item of Listbox. self.item_box.get(self.item_box.curselection()) should be used instead.
Below changes should fix the issue:
class MainGUI:
def __init__(self):
...
self.item_box = tk.Listbox(self.root, exportselection=False)
...
def function_1(self, event):
print('MainGUI function:', event)
print('Text in Listbox:', self.item_box.get(self.item_box.curselection()))
...
class SecondGUI:
def __init__(self):
...
self.dropdown = ttk.Combobox(self.window, values=items, exportselection=False)
...
...
I'd like to remove focus from a widget manually.
You can focus to another dummy widget.
Edit
from Tkinter import *
def callback():
print master.focus()
master = Tk()
e = Entry(master)
e.pack()
e.focus()
b = Button(master, text="get", width=10, command=callback)
b.pack()
master.mainloop()
Focusing on a non-'focusable' widget will remove focus from another widget.
Set focus to another widget to remove focus from the target widget is a good idea. There are two methods for this: w.focus_set() and w.focus_force(). However, method w.focus_force() is impolite. It's better to wait for the window manager to give you the focus. Setting focus to parent widget or to the root window removes focus from the target widget.
Some widgets have takefocus option. Set takefocus to 0 to take your widget out of focus traversal (when user hits <Tab> key).
My solution is root.focus() it will remove widget focus.
If the dummy widget is Canvas then c.focus() will not work.
use c.focus_set() or c.tk.call('focus',c) to first focus on the canvas window itself.
That's because
c.focus()
... returns the id for the item that currently has the focus, or an empty string if no item has the focus. Reference
c.focus(id_) will focus on the item having id id_ within the canvas.
c.focus("") will remove the focus from any item in the canvas.
Hence (within some callback)
c.config(highlightthickness = 0) # to remove the highlight border on focus
c.foucs_set()
c.focus("") # just to be sure
The reason c.focus() functions differently is that within Tcl/Tk's Commands there's the "Primary" Command focus
as well as the Canvas-specific Widget Command focus
That's not an issue within the Tcl/Tk syntax but in the tkinter module c.focus() will call the underlying canvas-specific foucs.
From tkinter.py within the Canvas class Line 2549
def focus(self, *args):
"""Set focus to the first item specified in ARGS."""
return self.tk.call((self._w, 'focus') + args)
So the question may be a duplicate here, but the answer from #Bryan Oakley works perfectly for me in Python 3.8
root.focus_set()
Too easy...
If you use ttk widgets you can "remove" the focus ring by removing the color; for example on a button:
style = ttk.Style()
style.configure('TButton', focuscolor='')
I have been searching for a way to set the tab order in a tkinter application, that I have been working on. Currently the default order seems to be working from top-down, but it requires using CTRL + Tab to cycle through the controls.
Is there any way to customize the order and, more so, change the CTRL + Tab to just Tab?
Tab order is based on the stacking order, which in turn defaults to the order that widgets are created. You can adjust the stacking order (and thus the tab order) using the methods tkraise (or lift) and lower.
This should be working out of the box for you without the need to press CTRL + Tab. Be aware, however, that tab inserts a literal tab in text widgets rather than moving focus to another control. That default behavior can be changed of course.
Here's an example showing how to reverse the tab order. When running the example, pressing tab in the first entry should take you to the last. Pressing tab again takes you to the second, then the first, lather, rinse, repeat
Note that the native Tk commands are raise and lower, but since raise is a reserved word in Python it had to be renamed in tkinter.
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
e1 = tk.Entry(self)
e2 = tk.Entry(self)
e3 = tk.Entry(self)
e1.insert(0,"1")
e2.insert(0,"2")
e3.insert(0,"3")
e1.pack()
e2.pack()
e3.pack()
# reverse the stacking order to show how
# it affects tab order
new_order = (e3, e2, e1)
for widget in new_order:
widget.lift()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
Since you mention you have to do CTRL + Tab, I'm guessing you're trying to have the tab key change focus from a text widget. Normally a tab key inserts a literal tab. If you want it to change the focus, just add a binding to the <Tab> event.
Tkinter has a function that will return the name of the next widget that should get focus. Unfortunately, for older versions of Tkinter that function is buggy. However, it's easy to work around that. Here's a couple of methods you can add to the above code:
def _focusNext(self, widget):
'''Return the next widget in tab order'''
widget = self.tk.call('tk_focusNext', widget._w)
if not widget: return None
return self.nametowidget(widget.string)
def OnTextTab(self, event):
'''Move focus to next widget'''
widget = event.widget
next = self._focusNext(widget)
next.focus()
return "break"
I've been searching for ways to skip some widgets while tabbing and found in tkinter's tk_focusNext function description the following: "A widget is omitted if it has the takefocus resource set to 0."
you can set takefocus on widget initialization as an argument.
By default, tkinter's Checkbutton widget responds to clicks anywhere in the widget, rather than just in the check box field.
For example, consider the following (Python 2) code:
import Tkinter as tk
main = tk.Tk()
check = tk.Checkbutton(main, text="Click").pack()
main.mainloop()
Running this code results in a small window, with a single check box, accompanied by the word "Click". If you click on the check box, it is toggled.
However, this also happens if you click on the text of the widget, rather than the check box.
Is this preventable? I could make a separate widget to hold the text, but that still results in a small area around the check box that responds to clicks.
Two solutions come to mind:
Do as you suggest and create a separate widget for the checkbutton and for the label.
replace the bindings on the checkbutton with your own, and examine the x/y coordinates of the click and only accept the click if it happens in a small region of the widget.
This program creates a checkbutton and overrides the default event on it by binding a method to the checkbutton. When the button is clicked, the method checks a defined limit to allow the normal operation or to override. OP wanted to make sure that when text of the checkbutton is clicked, no default action is taken. That is essentially what this does.
import tkinter as tk
import tkinter.ttk as ttk
class App(tk.Frame):
def __init__(self, parent, *args, **kwargs):
self.checkvar = IntVar()
check = tk.Checkbutton(parent, text='Click', variable=self.checkvar)
check.bind('<Button-1>', self.checkCheck)
check.pack()
print(dir(check))
def checkCheck(self, event):
# Set limit based on your font and preference
limit = 18
print(event.x, event.y, self.checkvar.get())
if event.x > limit or event.y > limit:
self.checkvar.set(not self.checkvar.get())
else:
print("Normal behavior")
if __name__ == "__main__":
window = tk.Tk()
app = App(window)
window.mainloop()
I have a top level widget that is created when a button is pressed. How do I make it so when that same button is pressed again, while the top level widget is still open it simply moves the top level widget into focus?
Imagine you have the following method in a class. This method is called when you press the button. You will also have an instance attribute defined in the __init__ method: self.toplevel = None.
def button_press(self):
if self.toplevel is None:
self.toplevel = ... # another method to create toplevel widget
else:
# set focus to self.toplevel
# you can use self.toplevel.deiconify() if self.toplevel is minimised
# also look at self.toplevel.lift() to bring the window to the top
You will also need to reset self.toplevel to None when the toplevel widget is destroyed.
Also look at the focus_set method of widgets. You may have to set the take_focus attribute to True for a toplevel widget. But maybe you want to set the focus to a particular widget (eg a Textbox) on the toplevel widget.