Having trouble with traversal order over tkinter entry fields [duplicate] - python

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.

Related

ttk combobox list values remain open after alt-tabbing

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)

WASD input in python

In python, how can I receive keyboard input. I'm well aware of console input with input("...") but I'm more concerned with receiving keyboard input while the console window is not active. For example if I created an instance of a Tkinter screen how could I check to see if let's say "w" was pressed. Then if the statement returned true i could move an object accordingly.
The way you do this with a GUI toolkit like tkinter is to create a binding. Bindings say "when this widget has focus and the user presses key X, call this function".
There are many ways to accomplish this. You can, for example, create a distinct binding for each character. Or, you could create a single binding that fires for any character. With these bindings, you can have them each call a unique function, or you can have all the bindings call a single function. And finally, you can put the binding on a single widget, or you can put the binding on all widgets. It all depends on exactly what you are trying to accomplish.
In a simple case where you only want to detect four keys, four bindings (one for each key) calling a single function makes perhaps the most sense. For example, in python 2.x it would look something like this:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, width=400, height=400)
self.label = tk.Label(self, text="last key pressed: ", width=20)
self.label.pack(fill="both", padx=100, pady=100)
self.label.bind("<w>", self.on_wasd)
self.label.bind("<a>", self.on_wasd)
self.label.bind("<s>", self.on_wasd)
self.label.bind("<d>", self.on_wasd)
# give keyboard focus to the label by default, and whenever
# the user clicks on it
self.label.focus_set()
self.label.bind("<1>", lambda event: self.label.focus_set())
def on_wasd(self, event):
self.label.configure(text="last key pressed: " + event.keysym);
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

How to disable selecting text in a Python/Tk program?

How would that be done?
I have been unable to find it on here or with Google.
#Refrences
from tkinter import *
class Interface:
def __init__(self,stage):
topLeft = Frame(stage,bg='black')
test = Entry(topLeft,bg='#2a2a2a',fg='white',insertontime=0)
test.config(insertbackground='white', exportselection=0)
test.grid()
topLeft.grid(row=0,column=0)
def launch():
window = Tk()
lobby = Interface(window)
window.mainloop()
launch()
I assume you wish for users to not be able to edit the Entry box? If so, you must use the config parameter of (state="disabled"), eg.
test.config(insertbackground='white', exportselection=0, state="disabled")
be wary though, I could not find a way to keep the background of your entry box black. Hope this helps
You can set the text highlight color to match the background of entry widget. Note that the text in the widget can still be selected but the user will not see it visually which will give the illusion that selection is disabled.
test = Entry(topLeft,bg='#2a2a2a',fg='white',insertontime=0)
test.configure(selectbackground=test.cget('bg'), inactiveselectbackground=test.cget('bg'))
When we select a text we tkinter trigger(fire) 3 events, which are ButtonPress, Motion and ButtonRelease all these 3 events call a event handler fucntion.
The function run select_range(start, end) method which highlight selected text.
To disable this we have to hanlde ButtonPress and Motion events together and call select_clear method on the widget.
If you handle the events and call select_clear method it work but not properly, the text will be highlighted and when another event occured highligh color will be cleared.
This happend because of execution order of events. That's mean you have to tell to tk handle your event after default event handler. We can change order of events execution with bindtags and bin_class methods
example:
from tkinter import *
from tkinter import ttk
def on_select(event):
event.widget.select_clear() # clear selected text.
root = Tk()
name_entry = ttk.Entry(root)
name_entry.pack()
# with PostEvent tag, on_select the event hanlde after default event handler
name_entry.bindtags((str(name_entry), "TEntry", "PostEvent", ".", "all"))
name_entry.bind_class("PostEvent", "<ButtonPress-1><Motion>", on_select)
root.mainloop()
A useful solution to Tkinter not having tons of built-in widgets (like, say, JavaFX does), is that you can easily make your own if you don't mind them being not-quite what you wanted :) Here's a rough-around-the-edges example of using a canvas to emulate an entry field that can't be selected. All I've given is the insert functionality (and delete too, I suppose, if you were clever about it), but you may want more. (Another plus is that, because it's a canvas item, you can do nifty formatting with it).
Am I right that by a not-selectable entry widget, you mean a canvas with text written on it? If you want to disable text-highlighting in ALL widgets, including the top-level frame, you could highjack the Button-1 event, deselect everything, and then consume the event whenever text is selected.
from tkinter import *
class NewEntry(Canvas):
def __init__( self, master, width, height ):
Canvas.__init__( self, master, width=width,height=height )
self.width = width
self.height = height
self.text = ''
def insert( self, index, text ):
self.text =''.join([self.text[i] for i in range(index)] + [text] + [self.text[i] for i in range(index,len(self.text))])
self.delete(ALL)
self.create_text(self.width//2,self.height//2,text=self.text)
root = Tk()
entry = NewEntry(root,100,100)
entry.insert(0,'hello world')
entry.insert(5,'world')
entry.pack()
root.mainloop()

Select all text in a Text widget using Python 3 with tkinter

I'm working on my first Python program and have little idea what I'm doing. I want to re-bind ctrl-a (control a) to select all text in a Text widget. The current binding is ctrl-/ (control /). The binding part jumps right to the function but the actual text selection doesn't work. Instead, the cursor jumps to the first character on the first line (like it should) and nothing else happens. I'm sure this is embaressingly easy to fix but after spending hour an hours on it, I can't figure out what's wrong.
Python 3, Windows:
from tkinter import *
# Select all the text in textbox (not working)
def select_all(event):
textbox.tag_add(SEL, "1.0", END)
textbox.mark_set(INSERT, "1.0")
textbox.see(INSERT)
# Open a window
mainwin = Tk()
# Create a text widget
textbox = Text(mainwin, width=40, height=10)
textbox.pack()
# Add some text
textbox.insert(INSERT, "Select some text then right click in this window")
# Add the binding
textbox.bind("<Control-Key-a>", select_all)
# Start the program
mainwin.mainloop()
So the new code is...
from tkinter import *
# Select all the text in textbox
def select_all(event):
textbox.tag_add(SEL, "1.0", END)
textbox.mark_set(INSERT, "1.0")
textbox.see(INSERT)
return 'break'
# Open a window
mainwin = Tk()
# Create a text widget
textbox = Text(mainwin, width=40, height=10)
textbox.pack()
# Add some text
textbox.insert(INSERT, "Select some text then right click in this window")
# Add the binding
textbox.bind("<Control-Key-a>", select_all)
textbox.bind("<Control-Key-A>", select_all) # just in case caps lock is on
# Start the program
mainwin.mainloop()
and yes it works flawlessly. Thank you, very much Bryan Oakley. Steven Rumbalski: that's a VERY good point, I've followed your advice as well.
You need to both do the selection and then inhibit the default action by having your function return the string "break".
This is due to how Tkinter processes events. It uses what it calls "bind tags". Even though it looks like you are binding to a widget, you are actually binding to a tag that is the name of the widget. There can also be bindings to the widget class, to the toplevel window that the widget is in, and the tag "all" (plus, you can invent your own tags if you wish).
The default ordering of these tags is from most-specific to least-specific, and events are processed in that order. Meaning, if you have a binding both on the widget (most specific) and the class (less specific), the binding will fire for the widget first, and then for the class binding (and then for the toplevel, and then for "all").
What this means is that by default, a binding on a widget augments rather than replaces a default binding. The good news is, you can inhibit any further bindings from firing by simply returning the string "break", which stops the chain of bindings from doing any more work.
You can do that with a module named pyautogui
Just run the command where you want to add the event,
import pyautogui
..., command=lambda *awargs:pyautogui.hotkey("ctrl","a")
Make sure you install the module. If you are on windows, install it by
pip install pyautogui

Tkinter Label bound to StringVar is one click behind when updating

The problem I'm running into here is that, when I click on the different file names in the Listbox, the Label changes value one click behind whatever I'm currently clicking on.
What am I missing here?
import Tkinter as tk
class TkTest:
def __init__(self, master):
self.fraMain = tk.Frame(master)
self.fraMain.pack()
# Set up a list box containing all the paths to choose from
self.lstPaths = tk.Listbox(self.fraMain)
paths = [
'/path/file1',
'/path/file2',
'/path/file3',
]
for path in paths:
self.lstPaths.insert(tk.END, path)
self.lstPaths.bind('<Button-1>', self.update_label)
self.lstPaths.pack()
self.currentpath = tk.StringVar()
self.lblCurrentPath = tk.Label(self.fraMain, textvariable=self.currentpath)
self.lblCurrentPath.pack()
def update_label(self, event):
print self.lstPaths.get(tk.ACTIVE),
print self.lstPaths.curselection()
self.currentpath.set(self.lstPaths.get(tk.ACTIVE))
root = tk.Tk()
app = TkTest(root)
root.mainloop()
The problem has to do with the fundamental design of Tk. The short version is, bindings on specific widgets fire before the default class bindings for a widget. It is in the class bindings that the selection of a listbox is changed. This is exactly what you observe -- you are seeing the selection before the current click.
The best solution is to bind to the virtual event <<ListboxSelect>> which is fired after the selection has changed. Other solutions (unique to Tk and what gives it some of its incredible power and flexibility) is to modify the order that the bindings are applied. This involves either moving the widget bindtag after the class bindtag, or adding a new bindtag after the class bindtag and binding it to that.
Since binding to <<ListboxSelect>> is the better solution I won't go into details on how to modify the bindtags, though it's straight-forward and I think fairly well documented.

Categories

Resources