Here's a python/tkinter program that puzzles me. The window displays a ttk.Entry that is readonly and a tkinter.Text that is disabled. It programmatically selects one character in the Entry box and never changes this selection. However the selection will change if I try to select text in the other box (the disabledText). This doesn't seem right.
Python 3.5.0 and tcl/tk 8.5.18 on OS X
When you run the program, you can see the "A" highlighted in the Entry (upper) box.
Push the "Write Data" button a few times; the print statement will display the "A" that's selected in the Entry box.
Sweep the mouse over some text in the Text (lower) box; it won't be highlighted, but the highlighting in the Entry will disappear.
Push the "Write Data" button; the print statement will display the characters you swept with the mouse.
Those characters came from selection_get() on the Entry! You can tell that it got them from the Text because the two boxes have no characters in common.
If somebody can explain this, I'd be most grateful.
import tkinter
from tkinter import ttk
class ButtonPanel(ttk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.data = ttk.Entry(self, width=27, takefocus=False)
self.data.insert(0, "ABCDEFG")
self.data.select_range(0, 1) # select the "A"
self.data.state(["readonly"])
self.data.bind('<ButtonPress>', lambda e: 'break') # ignore mouse clicks
button = ttk.Button(self, text="Write Data", command=self.master.write)
self.data.grid(column=0, row=0, padx=10)
button.grid(column=1, row=0, padx=10)
def get(self):
return self.data.selection_get() # should always be the "A"
class App(ttk.Frame):
def __init__(self, parent):
super().__init__(parent)
self.bp = ButtonPanel(self)
self.display = tkinter.Text(self, width=50, height=10, wrap="char", takefocus="False")
self.display.insert('end', "HIJKLMNOPQRSTUV")
self.display.config(state="disabled")
self.bp.pack()
self.display.pack()
def write(self):
char = self.bp.get() # should always be the "A"
print("this should be just one character: ->{}<-".format(char))
if __name__ == "__main__":
root = tkinter.Tk()
root.title("What's up here?")
App(root).pack()
root.mainloop()
What you are observing is the default behavior. Both of those widgets (as well as the listbox) have an attribute named exportselection, with a default value of True. When True, the widget will export the selection to be the primary selection. On old unix systems (where tcl/tk and tkinter got its start), you could only have one "primary" selection at a time.
The simple solution is to set this option to False for the text widget. This will allow your application to have multiple items selected at once, but only the entry widget exports the selection to the clipboard (which is required for selection_get to work.
...
self.display = tkinter.Text(self, ..., exportselection=False)
...
The other issue is that on OSX the selection won't show for a disabled text widget. The text is being selected, you just can't see it. More accurately, the selection won't show except when the widget has focus, and by default, it is not given focus when you click on it.
Related
Background
I have created a tk.Text widget within a frame and used grid to place successfully within my tool. I use methods to run read commands and display the results within the tk.Text widget. I only want the tk.Text to display the results nothing else and this works as expected.
resultsbox = tk.Text(centerframe)
resultsbox.config(state='disabled', bd='10', height=38, width=92, bg='black', fg='green', font='fixedsys')
resultsbox.grid(row=0, column=1, sticky="e")
I use the following commands on each method to enable my write results and then disable the user from typing into the tk.Text widget:
Example
resultsbox.config(state='normal')
resultsbox.insert(tk.INSERT, RESULT1, 'standard', "\n\n")
resultsbox.config(state='disabled')
This again works as expected.
Issue
The problem i have is if a user clicks within the tk.Text widget even though it is disabled the next result that the tool writes to the widget will place it at the point where the user last clicked causing a very unreadable result.
Example
This Screenshot shows if a user has clicked within the widget and then the next result is written.
Summary
Is it possible to stop the user pressing within the tk.Text widget?
You don't have to disable your mouse keys. It is very simple. You just replace tk.INSERT with tk.END. This tells where to insert. Either at the marked position or at the end. Check out my example. I created 2 buttons, one with tk.INSERT and one with tk.END.
import tkinter as tk
def insert_END():
RESULT1 = '---OUTPUT---'
resultsbox.config(state='normal')
resultsbox.insert(tk.END, RESULT1, 'standard', "\n\n")
resultsbox.config(state='disabled')
def insert_INSERT():
RESULT1 = '---OUTPUT---'
resultsbox.config(state='normal')
resultsbox.insert(tk.INSERT, RESULT1, 'standard', "\n\n")
resultsbox.config(state='disabled')
root = tk.Tk()
root.geometry("1000x1000")
frame1 = tk.Frame(root).grid(row=1, column=1)
resultsbox = tk.Text(frame1)
resultsbox.config(state='disabled', bd='10', height=38, width=92, bg='black', fg='green', font='fixedsys')
resultsbox.grid(row=0, rowspan=2, column=1, sticky="e")
btn1 = tk.Button(master=frame1, text='END', command=insert_END)
btn1.grid(row=0, column=0, padx=30)
btn2 = tk.Button(master=frame1, text='INSERT', command=insert_INSERT)
btn2.grid(row=1, column=0, padx=30)
root.mainloop()
The state 'disampled' is an editing property and means that the text cannot be modified. I do not know if that is linked with the cursor position issue you describe (as I get it).
If I understand it correctly the issue is that even when the textbox is disabled and you click in it, the position of the click is remembered and the next time you enable the textbox again it inserts the new text at the last position. If this is the issue, I would try to place the cursor at the end of the actual text (e.g. Text.insert(tk.END,...) before the disable command. When you change the status to 'enable' you can then add the new text to the end of the previous string. Alternatively I would try each time I enable the Text widget to check where the cursor is and move it to the end of the previous string.
Using Python 2.6.1 or 2.7.9 Tkinter's Entry widget entry.insert seems to destroy left justification. How do I get the text to stay left justified? Specifically, after entry.get() provides what I type into the widget, I insert additional text automatically at the beginning and redisplay it. When redisplayed, the justification ~sometime~ changes.
Specifically, when I enter a string of 20 characters or less and the entry.insert makes it longer, all works okay with some of the string not visible off the right side of the entry widget. When I enter a string of 21 characters or more and the entry.insert makes it longer, it displays
as right justified so only the end of the string is visible and "THANK YOU" is never seen off the left side.
Why does this happen and how do I get it to stop? I've considered Entry Widget Justification - text longer than widget but in his case, the OP wasn't doing an insert and the vague question didn't get a good answer.
The default displayed text size for an Enter widget is 20 characters, but the same phenomena happens for whatever Entry width I specify. Phenomena also happens on Raspberry Pi 3 and Mac OSX.
# demo of broken justification 1/14/2017
import Tkinter as tk
master = tk.Tk()
def clear_entry():
entry.delete(0, tk.END)
entry.config(bg='white')
def process_butninput():
if entry.get()=="done":
master.destroy()
else:
entry.insert(0,"THANK YOU ")
datarecord = entry.get()
print( "%s" % datarecord) # debugging only
entry.config(bg='green')
entry.after(1500, clear_entry)
label = tk.Label( master, text="Enter text: ")
label.grid(row=0, padx=20, pady=30)
entry = tk.Entry( master)
entry.grid( row=0, column=1)
button = tk.Button( master, text='Enter', command=process_butninput)
button.grid(row=0, column=3, padx=20, pady=30)
master.mainloop()
Text justify is only relevant for under-sized text strings. If the text string is longer than the Entry width, then justify does nothing and you have to use xview.
I was basing my expectations on how, for example, Word and Excel display justified text, but that is not how Tkinter displays text in the Entry widget.
I don't fully understand what you are asking, but it sounds like you simply need to scroll the contents of the entry widget after inserting "THANK YOU " so that the character at position 0 (zero) is visible.
entry.insert(0,"THANK YOU ")
entry.xview(0)
I have discovered that using tk.Scrollbar allows use of touch the scroll on a text widget. When I was overhauling the graphics of my application I could no longer use touch to scroll the text widget that used ttk.Scrollbar instead.
I am positive it is a difference between the widgets because I have toggled between the two when testing the problem.
That being said it is not a huge problem, since only a handful of devices this program will be installed on will have touch capabilities.
Is there any way to get touch on a ttk.scrollbar?
EDIT: Relevant Section of Code:
EDIT #2: Added the two functions at the bottom
self.text=tk.Text(self,font=("Consolas","11"),wrap="none",undo=True,bg="white",relief="flat")
self.numb=tk.Text(self,font=("Consolas","11"),wrap="none",width=4,relief="flat")
self.vsb=ttk.Scrollbar(self,command=self.scroller)
self.hsb=ttk.Scrollbar(self,command=self.text.xview,orient="horizontal")
self.text.configure(yscrollcommand=self.on_textscroll,xscrollcommand=self.hsb.set)
self.numb.config(bg="grey94",yscrollcommand=self.on_textscroll)
def scroller(self,*args):#Move me
self.text.yview(*args)
self.numb.yview(*args)
def on_textscroll(self, *args):#Move me
self.vsb.set(*args)
self.scroller('moveto', args[0])
(Not necessarily an answer, but at least code to experiment with and maybe build upon to make an answer.)
The following scrolls in all directions with middle mouse button held down with either tk (line 2 commented out) or ttk Scrollbar (line 2 uncommented).
from tkinter import Tk, Text, Scrollbar
from tkinter.ttk import Scrollbar
class Test(Tk):
def __init__(self):
super().__init__()
self.text = Text(self, wrap="none",undo=True,bg="white",relief="flat")
self.numb = Text(self, wrap="none",width=4,relief="flat")
self.vsb = Scrollbar(self,command=self.scroller)
self.hsb = Scrollbar(self,command=self.text.xview,orient="horizontal")
self.text.configure(yscrollcommand=self.on_textscroll,xscrollcommand=self.hsb.set)
self.numb.config(bg="grey94",yscrollcommand=self.on_textscroll)
self.numb.grid(row=0, column=0)
self.text.grid(row=0, column=1)
self.vsb.grid(row=0, column=2, sticky='ns')
self.hsb.grid(row=1, column=1, sticky='ew')
for i in range(100):
self.text.insert('insert',
('abcdefg%2d '%i)*10 + '\n')
def scroller(self,*args):#Move me
self.text.yview(*args)
self.numb.yview(*args)
def on_textscroll(self, *args):#Move me
self.vsb.set(*args)
self.scroller('moveto', args[0])
root=Test()
Adding the following at the end of init exposes middle mouse button clicks (it is botton 2, not 3), without disabling the scroll effect. Do fingers touches trigger this, with either scrollbar? If not, try to find out what event touches do generate.
def pr(event):
print(event)
self.text.bind('<Button-2>', pr)
self.text.bind('<ButtonRelease-2>', pr)
I am trying to make a simple application that scrambles keyboard letters while typing. I am using python along with tkinter. I have a text widget and i need to disable the key tab in my application. I tried it using following code.
text.bind("<Tab>", no_op)
Here no_op is the function given below:
def no_op(self):
return "break"
But I am not getting the expected result. I am posting the whole code below.
import Tkinter as tk
def onKeyPress(event):
first=event.char
second=ord(first)
if second==32:
second=chr(second)
text.insert('end', '%s' % (second ))
elif second==8:
length = len(text.get(1.0, 'end'))
contents = text.get(1.0, 'end')
newcon = contents[:-2]
#text.insert('end', '%s' % (length ))
text.delete(1.0,'end')
text.insert('end', '%s' % (newcon ))
elif(second>=65 and second<=90 or second>=97 and second<=122):
second=chr(second+3)
text.insert('end', '%s' % (second ))
def no_op(self):
return "break"
root = tk.Tk()
root.config(cursor='none')
#root.attributes('-zoomed',True)
text = tk.Text(root, background='white', foreground='black', font=('Comic Sans MS', 12))
text.pack(expand=True,)
text.bind("<Tab>", no_op)
text.bind("<Button-1>", no_op)
text.config(cursor="none")
root.bind('<KeyPress>', onKeyPress)
root.mainloop()
(Note: The problem is that when tab is pressed when some other widget has focus, the text cursor comes in the text area. Then, if I press any letter say,'a' both 'a' and 'd' is inserted to text field. I want to fix that.)
Your problem isn't with the tab key, your problem is focus management. You've made your code work only if the text widget never gets keyboard focus. There are at least two solutions:
continue down the path of preventing the user from focusing on the text widget
allow focus on the text widget, and adjust your bindings accordingly
For the first, instead of trying to change the behavior of the tab (and also the shift-tab), you can merely move the focus whenever the text widget gets it. For example:
text.bind("<FocusIn>", lambda event: root.focus_set())
That will prevent the text widget from ever getting focus, and your code should work.
Another solution is to modify your <KeyPress> binding to be on the text widget rather than the root widget, and then simply reject all handling of key presses. That means to do text.bind('<KeyPress>', ...) rather than root.bind.... You then need to modify onKeyPress to return "break" to prevent the default text widget bindings from happening.
I'm writing a Tkinter GUI in Python. It has an Entry for searching with a results ListBox below it. The ListBox also has a Scrollbar. How can I get scrolling with the mouse and arrow keys to work in the ListBox without switching focus away from the search field? IE I want the user to be able to type a search, scroll around, and keep typing without having to tab back and forth between widgets. Thanks
Add bindings to the entry widget that call the listbox yview and/or see commands when the user presses up and down or uses the up/down scrollwheel.
For example, you can do something like this for the arrow keys:
class App(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
self.entry = Tkinter.Entry()
self.listbox = Tkinter.Listbox()
self.entry.pack(side="top", fill="x")
self.listbox.pack(side="top", fill="both", expand=True)
for i in range(100):
self.listbox.insert("end", "item %s" % i)
self.entry.bind("<Down>", self.OnEntryDown)
self.entry.bind("<Up>", self.OnEntryUp)
def OnEntryDown(self, event):
self.listbox.yview_scroll(1,"units")
def OnEntryUp(self, event):
self.listbox.yview_scroll(-1,"units")