I would like to enable ctrl+a to select the text within a combobox. Instead of selecting all it does <end> (more or less at least).
minimal example:
#!/usr/bin/env python3
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showinfo
root = tk.Tk()
def month_changed(event):
msg = f'You selected {month_cb.get()}!'
showinfo(title='Result', message=msg)
# month of year
months = ['Jan', 'Feb']
# create a combobox
selected_month = tk.StringVar()
month_cb = ttk.Combobox(root, textvariable=selected_month)
month_cb['values'] = months
month_cb.pack(fill='x', padx=5, pady=5)
month_cb.bind('<<ComboboxSelected>>', month_changed)
month_cb.bind('<Control-a>', doSomeSelecting) #doSomeSelcting tbd
root.mainloop()
I stole the example and minimized it from here to get a quick example: https://www.pythontutorial.net/tkinter/tkinter-combobox/
So what you are doing is overriding the default bindings for your platform. On X11 Tk sets up a default binding for Control-Key-slash to generate the <<SelectAll>> virtual event. On Win32 this is extended with Control-Key-a as well. On X11 Control-a is bound to <<LineStart>>.
So the platfrom correct thing is to leave it alone and learn to use Control-slash to select all. To override this you need to bind Control-a to a function that generates the SelectAll virtual event and also prevents the default event handler from then moving the insertion point to the start of the line. For that:
def selall(ev):
ev.widget.event_generate('<<SelectAll>>')
return 'break'
month_cb.bind('<Control-a>', selall)
The return 'break' is important here otherwise the event handlers will continue being called and our selection will be undone when something generates the <<LineStart>> event after our <<SelectAll>>.
This can be investigated in IDLE using month_cb.bindtags() to find that it's class bindings are TCombobox. Then month_cb.bind_class('TCombobox') to see all the events that are bound to this class. For the virtual events, root.event_info('<<SelectAll>>') shows the set of events that cause this virtual event to be raised.
Related
I've been trying to create a message box when a button turns from disabled to active but when the button actually turns from disabled to active for some reason my callback is not even being called I've tried to get it working so for quite a bit of time now and I'm stuck.
Here is an example of the problem:
from tkinter import *
from tkinter import Tk
def disable_and_activate():
b.config(state = DISABLED)
b.config(state = ACTIVE)
def is_working(event):
print('working')
root = Tk()
b = Button (root, text = 'click me', command = disable_and_activate)
b.pack()
b.bind('<Activate>', is_working)
root.mainloop()
Console:
the button is clicked but there's nothing printed on the console
The <Activate> event is not triggered when you set the state of the button to "active". The event is triggered when the window becomes the active window.
For example, when I run your code on my OSX machine, if I click on some other application to give it focus and then I click back to the tkinter window, the event will fire when the tkinter window becomes the active window.
This is explained in the canonical tcl/tk documentation which says this:
Activate, Deactivate
These two events are sent to every sub-window of a toplevel when they change state. In addition to the focus Window, the Macintosh platform and Windows platforms have a notion of an active window (which often has but is not required to have the focus). On the Macintosh, widgets in the active window have a different appearance than widgets in deactive windows. The Activate event is sent to all the sub-windows in a toplevel when it changes from being deactive to active. Likewise, the Deactive event is sent when the window's state changes from active to deactive. There are no useful percent substitutions you would make when binding to these events.
Here the problem was just the code inside the function , It seems like you needed to call EventGenerate('<<Activate>>') I also recommend adding 2 << and 2 >>
So I rewrote the code and its now working perfectly fine:
from tkinter import *
from tkinter import Tk
import tkinter
def disable_and_activate():
b.configure(state=tkinter.DISABLED)
b.configure(state=tkinter.ACTIVE)
b.event_generate("<<Activate>>")
def is_working(event):
print('working')
root = Tk()
b = Button (root, text = 'click me', command = disable_and_activate)
b.pack()
b.bind('<<Activate>>', is_working)
root.mainloop()
tl;dr: When the application calls tkinter.filedialog, entry fields do not properly focus.
Long explanation:
When initializing a tkinter application, the entry fields are enabled by default. Their state is tk.ENABLED, they can be focused on by scrolling through fields with tab, and, most importantly, they can be clicked on to select the field.
For some reason, this behavior is broken by calling tkinter.filedialog. If a method of tkinter.filedialog is called, such as askdirectory or askopenfile(), the entry field will still have the tk.ENABLED state, and the background will be properly styled, but clicking on the entry field will not insert the cursor or select the field. Typing, of course, does not register.
This can be worked around by toggling to a different window and toggling back. However, the file dialog windows (properly) return the user directly back to the main window, and so users are always presented with a main window that appears to be locked up.
See this example:
import tkinter as tk
from tkinter import filedialog
BR8K = True
root = tk.Tk()
if BR8K:
filedialog.askdirectory()
entry = tk.Entry(root, takefocus=True, highlightthickness=2)
entry.grid(sticky="WE")
root.mainloop()
Here, the code behaves properly if BR8K is False, and incorrectly if BR8K is True.
(Note: In a production environment, this would be object oriented. The issue persists in an object oriented environment.)
This is a known issues resulting from a dialog window being called prior to the mainloop() being reached for the first time.
The simplest way to fix this is to add update_idletask() before the dialog.
Try this:
import tkinter as tk
from tkinter import filedialog
BR8K = True
root = tk.Tk()
# By adding this you avoid the focus breaking issue of calling dialog before the mainloop() has had its first loop.
root.update_idletasks()
if BR8K:
filedialog.askdirectory()
entry = tk.Entry(root, takefocus=True, highlightthickness=2)
entry.grid(sticky="WE")
root.mainloop()
I'm developing a GUI using python 3.6 but I need the user to double-click on a tkinter Entry widget to allow input (to prevent modification of fields by accident), instead of just press any key to input text.
Is there any way to prevent the user input by keystroke until double
click on Entry?
I've tried by first to override events with the methods below to unbind keystrokes but none of them worked, so re-definition of new bind (double click) is not yet implemented.
Entry.unbind_all('<Key>')
Entry.unbind_all('<KeyPress>')
Entry.unbind_all('<KeyRelease>')
One simple way of doing preventing the user input by keystrokes until a double-click, is simply manipulating the state of an Entry with double click and focus out events. As in by default every widget is read-only, when double-clicked a single one gets enabled, and when lost focus it's read-only again:
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
def on_double_click(widget):
widget['state'] = 'normal'
def on_lose_focus(widget):
widget['state'] = 'readonly'
def main():
root = tk.Tk()
entries = list()
for i in range(3):
entries.append(tk.Entry(root, state='readonly'))
entries[-1].bind('<Double-Button-1>',
lambda e, w=entries[-1]: on_double_click(w))
entries[-1].bind('<FocusOut>',
lambda e, w=entries[-1]: on_lose_focus(w))
entries[-1].pack()
tk.mainloop()
if __name__ == '__main__':
main()
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
I'm proposing a continuation of the discussion in disable tkinter keyboard shortcut: I have an event handler for an event that Tkinter also uses, so that my prog & Tkinter interact badly.
Since it is a problem that I've been unable to solve I'm re-proposing here, where I tried to boil it down to the simplest form in the following code:
#!/usr/bin/env python
from Tkinter import *
import tkFont
def init():
global root,text
root = Tk()
root.geometry("500x500+0+0")
dFont=tkFont.Font(family="Arial", size=10)
text=Text(root, width=16, height=5, font=dFont)
text.pack(side=LEFT, fill=BOTH, expand = YES)
root.bind("<Control-b>", setbold)
text.tag_config("b",font=('Verdana', '10', 'bold' ))
text.tag_config("i",font=('Verdana', '10', 'italic' ))
def removeformat(event=None):
text.tag_remove('b',SEL_FIRST,SEL_LAST)
text.tag_remove('i',SEL_FIRST,SEL_LAST)
def setbold(event=None):
removeformat()
text.tag_add('b', SEL_FIRST,SEL_LAST)
text.edit_modified(True)
def main():
init()
mainloop()
if __name__ == '__main__':
main()
What it should do is simply to produce a text window where you write into.
Selecting some text and pressing Ctrl+B the program should remove any preexisting tag, then assign to it the 'b' tag that sets the text to bold.
What instead happens is an exception at the first tag_remove, telling me that text doesn't contain any characters tagged with "sel".
The suggestion to use a return 'break' is of no use, since the selection disappears before setbold() has any chance to act...
Set your binding on the text widget, not on the root. (Whole toplevel bindings are processed after widget class bindings – where the standard <Control-Key-b> binding is – and those are processed after the widget instance bindings, which is what you want to use here.) And you need to do that 'break'; it inhibits the subsequent bindings. (If you're having any problems after that, it's probably that the focus is wrong by default, but that's easy to fix.)
The only other alternative is to reconfigure the bindtags so that class bindings are processed after toplevel bindings, but the consequences of doing that are very subtle and far-reaching; you should use the simpler approach from my first paragraph instead as that's the normal way of handling these things.
Bindings are handled in a specific order, defined by the bindtags of that widget. By default this order is:
The specific widget
The widget class
The toplevel window
The special class "all"
If there are conflicting bindings -- for example, a control-b binding on both the widget and class -- they both will fire (in the described order) unless you break the chain by returning "break".
In the case of the code you posted, however, you are binding to the toplevel window (ie: the root window), and the conflicting binding is on the class. Therefore, the binding will fire for the class before it is processed by the toplevel, so even if your binding returned "break" it wouldn't matter since the class binding happens first.
The most straight-forward solution is to move your binding to the actual widget and return "break". That will guarantee your binding fires first, and the return "break" guarantees that the class binding does not fire.
If you really want your binding on the root window, you can remove the binding for the class using the bind_class method with the value of "Text" for the class.
You might find the Events and Bindings page on effbot.org to be useful.