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
Related
I am trying to get my code to display text from a print statement onto the Tkinter GUI - does anyone know how to do this?
Use this:
import tkinter as tk
# This function acts just like the `print` function:
def print_on_gui(*args, sep=" ", end="\n"):
text = sep.join(args) + end
# Set the Text widget's state to normal so that we can edit its text
text_widget.config(state="normal")
# Insert the text at the end
text_widget.insert("end", text)
# Set the Text widget's state to disabled to disallow the user changing the text
text_widget.config(state="disabled")
# Create a new tkinter window
root = tk.Tk()
# Create a new `Text` widget
text_widget = tk.Text(root, state="disabled")
# Show the widget on the screen
text_widget.pack(fill="both", expand=True)
# Your code should go here
print_on_gui("Hello world!")
print_on_gui("Hello", "world!")
# Go inside tkinter's mainloop
root.mainloop()
The problem with this approach is that if your program runs for too long, it can make the window unresponsive. To avoid that you can use threading but that will complicate things a lot more. If you want to, I can write a solution that uses threading.
I am using the tkinter module in my python code to create my gui for an simple app I am creating. I was wondering if it is possible to keep the insertion cursor visible even when the the state on my entry widget is set to "readonly". I want the user to still see where the insertion cursor is in the text but not be able to edit the text directly using the keyboard.
Note:
When I say Insertion Cursor I don't mean the normal mouse cursor/pointer but the flashing bar which shows up on text which you can edit. I'm not sure what the proper name for it is... Below is an example of what I mean (the black bar in the Entry)
I have tried setting the 'insertwidth' and other insertion cursor config options in the Entry widget to see if the cursor was perhapes just hidden but it did not work.
Any help is appreciated.
Thanks!
Instead of making the entry widget readonly, you could block all keyboard events on the widget, which makes typing in it impossible:
entry.bind("<Key>", lambda e: "break")
I will suggest you to disable the Entry and change its disabledbackground. Example:
entry.config(disabledbackground ="white",state="disabled")
So , assuming that you are using python 3 ,your code can be like:
from tkinter import *
root = Tk()
ent = Entry(root)
ent.config(disabledbackground ="white",state="disabled")
ent.place(x=0,y=0)
mainloop()
You entry will look like:
Edit:
As the question is edited, so I will suggest you to use bind. Unlike #fhdrsdg's answers , you can use the bind function in a more easy way:
ent.bind("<Key>", "pass")
So , you can say that your complete code is:
from tkinter import *
root = Tk()
ent = Entry(root)
ent.bind("<Key>", "pass")
ent.pack()
mainloop()
Here is a screenshot:
What are my options for getting validation with the Tkinter.Text widget? I don't require Text's advanced functionality, just its multiline attribute. Unfortunately, it lacks both textvariable and validate commands, so it seems that I cannot attach some kind of callback that checks things every time the text changes. I'd like to avoid having to bind to <KeyRelease>, as that looks to capture ALL keypresses, including the likes of Shift, Ctrl, etc, keys, and would appear to be a bit of a mess to work right.
I basically just need to check if the Text field is blank or not, and enable/disable an "Ok" button as appropriate (i.e., if no text, then the button is disabled).
In lieu of this, has anyone come across a decent subclass of Entry that adds multiline functionality that is written in Python? There is this, which adds textvariable to Text, but it is written in pure TCL, not Python, and seems like it would be difficult to integrate into my existing Python environment.
The binding to the <KeyRelease> button does not need to be messy, you don't have to check the value of the key pressed but fetch the content of the widget. Keep in mind that it always has a '\n' at the end, so when you retrive the contents don't forget to discard it:
content = text.get(1.0, "end-1c")
Then you just need to change the state of the button based on this value:
import Tkinter as tk
def configure_ok_button(event):
content = event.widget.get(1.0, "end-1c")
state = "active" if content else "disabled"
button.configure(state=state)
root = tk.Tk()
text = tk.Text(root)
button = tk.Button(root, text="Ok", state="disabled")
text.bind("<KeyRelease>", configure_ok_button)
text.pack()
button.pack()
root.mainloop()
How would i go about locking a Text widget so that the user can only select and copy text out of it, but i would still be able to insert text into the Text from a function or similar?
Have you tried simply disabling the text widget?
text_widget.configure(state="disabled")
On some platforms, you also need to add a binding on <1> to give the focus to the widget, otherwise the highlighting for copy doesn't appear:
text_widget.bind("<1>", lambda event: text_widget.focus_set())
If you disable the widget, to insert programatically you simply need to
Change the state of the widget to NORMAL
Insert the text, and then
Change the state back to DISABLED
As long as you don't call update in the middle of that then there's no way for the user to be able to enter anything interactively.
Sorry I'm late to the party but I found this page looking for the same solution as you.
I found that if you "disable" the Text widget by default and then "normal" it at the beginning of a function that gives it input and "disable" it again at the end of the function.
def __init__():
self.output_box = Text(fourth_frame, width=160, height=25, background="black", foreground="white")
self.output_box.configure(state="disabled")
def somefunction():
self.output_box.configure(state="normal")
(some function goes here)
self.output_box.configure(state="disable")
I stumbled upon the state="normal"/state="disabled" solution as well, however then you are unable to select and copy text out of it. Finally I found the solution below from: Is there a way to make the Tkinter text widget read only?, and this solution allows you to select and copy text as well as follow hyperlinks.
import Tkinter
root = Tkinter.Tk()
readonly = Tkinter.Text(root)
readonly.bind("<Key>", lambda e: "break")
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.