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.
Related
Aspects of this question have most certainly been asked before, but not altogether.
[using Python 3.7.1]
Most of it works fine, but the display of scrolling textboxes is just not working... they expand southwards, and the scrollbar sliders never appear.
Never mind that the boxes and labels are too spaced apart.
Whilst it would probably be better to start again, with classes and use pack(), for the sake of understanding better, I would like to see if there's a way to make this work with grid() in a procedural way.
I can't quite get it over the line...
rtmiss = StringVar()
...
# COMMENT: Scrolled Text boxes
scrolledtext1_box=ScrolledText(framemain,width=30,height=10)
scrolledtext1_box.grid(sticky='n',row=5,column=0,rowspan=1,columnspan=1)
ttk.Label(scrolledtext1_box, anchor='w', width=20, text='placeholder', style='TLabel').grid(column=1, row=0, padx=0, pady=0)
tdiff_label = ttk.Entry(scrolledtext1_box, width=20, textvariable=tdiff)
scrolledtext1_box.insert(1.0, chars="anything")
tdiff_label.grid(column=0, row=5, columnspan=1, padx=1, pady=1)
no idea why this is so difficult. It's not the same as just inserting some free text into a textbox and adding a scrollbar.
tdiff_labelframe = ttk.LabelFrame(framemain, text='Title')
tdiff_labelframe.grid(column=0, row=5, padx=8, pady=4)
scrol_w = 30
scrol_h = 3
scrolledtext1_box = scrolledtext.ScrolledText(tdiff_labelframe, width=scrol_w, height=scrol_h, wrap=tk.WORD)
scrolledtext1_box.grid(sticky='nw',row=5,column=0,rowspan=1,columnspan=1)
tdiff_labelframe = ttk.Entry(framemain, width=20, textvariable=tdiff)
tdiff_entry = ttk.Entry(scrolledtext1_box, width=12, textvariable=tdiff)
tdiff_entry.grid(column=0, row=5)
I looked inside the Class Entry(Widget, XView) stuff...
def insert(self, index, string):
"""Insert STRING at INDEX."""
You can insert a string, as most examples will show you, as "my string", and I am able to get the contents of a StringVar into the text box, but only as a contiguous string, with no scrolling.
In the form in the codesnippet above...
tk.Tk()= myframe(root)
my_stringvar = stringvar()
my_labelframe = ttk.LabelFrame(my_frame, text='My Text')
my_labelframe = Entry(my_frame, textvariable = my_stringvar)
The scrolled stuff seems to be on a different "layer". The two blocks of code seem to be unconnected: apart from the scrolledtext function variable referring to the labelframe, which just sticks it onto that layer.
my_scrollheight = 20
my_scrollwidth = 10
my_scrolledtext = scrolledtext.ScrolledText(my_labelframe, width=my_scrollwidth, height=my_scrollheight, wrap=tk.WORD)
my_scrolledtext.grid(row=0,column=0,sticky='news')
So is the StringVar just being inserted into the Frame, above the LabelFrame layer, and thus not being "seen" by the scrolledtext part?
I have a partial answer to this, that I happened upon myself, but it creates a new problem. Before I could print the filepath, but not get the file contents to print and scroll; now it's the other way round.
I started off with this code:
Button errors in Tkinter - Selecting files, adding their paths
...trimmed it down to bare bones and one of each thing; then added my own junk, and eventually I accidentally made it work.
The solution to getting the text into a scrolled text widget, as in the question "how to send StringVar() content to Scrolled Text box using grid() and using procedural code?" is this:
def forkscroll():
mylabelframe = tk.LabelFrame(myframe, text='My Long Text:').pack()
scroll_w = 30
scroll_h = 10
myscrolledtext = scrolledtext.ScrolledText(mylabelframe, width=scroll_w, height=scroll_h, wrap=tk.WORD)
myscrolledtext.vbar.config(width=20)
myscrolledtext.grid(sticky='news', row=6, column=0, rowspan=1, columnspan=1, padx=5, pady=5)
# Open a dialogue; select and load a text file; read the text file directly into the scrolledtext widget (box)
fork = open(filedialog.askopenfilename(), 'r') # it's the same as "infile" above; but it's not a string
myscrolledtext.insert(tk.END, fork.read()) ### This reads the content of "fork" into the scrolledtext widget
...mainly those bottom two lines.
I then go on to put the file path of the selected text file into an Entry widget, but I can't get it to work... the best I could do was to keep the browsefunc and open the file twice... once for the path, and once for the contents... which is obviously a rubbish bodge.
forkit = StringVar()
forkit.set(fork.get())
mynormaltext = Text(mylabelframe, width = 10, height = 1, wrap=tk.WORD)
mynormaltext.grid(sticky='news', row=5, column=0, rowspan=1, columnspan=1, padx=5, pady=5)
mynormaltext.insert(tk.END, forkit.get())
# Even though it does the same thing in one line as it does in three, the outcome is not the same for scrolledtext
# forkname = filedialog.askopenfilename()
# forkfile = open(forkname, 'r')
# fork = forkfile.read()
# above... "fork" is a textIO, but Text can read it...
The best I can do, is get the content of the text file to fill the scrolledtext widget, and to print "<_IO.TextWrapper", as in the attached image. That's a bit better than just printing "PYVAR4" or something.
I have tried converting the "TextIO" or "PathLike" file into a string to inset it; I've tried doing set()/get(), and read() and lambda and tried putting things in different functions, and inputting and returning things, all sorts to try and get the filepath out again, but not really getting anywhere.
At least I've become more familiar with what some things do, and how, but I think I've hit a wall with the total solution, which is:
present 2 labels with 2 buttons each with a field: one as a text widget field; and scrolledtext widget field.
to click a button to browse to a text file, and read it into memory, and print its path into the text widget field
to click the other button, and print the contents of the same file into the scrolledtext field, and have it present within the frame, with a nice long scrollbar.
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)
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.
I'm hoping this is something that I'm overlooking, since I'm fairly new to Python, but I just can't figure this one out. I have a root window with a menu. When the user selects Open Project, a toplevel is created to display the data, the data is loaded from a file into a dictionary, and also loaded into Entry widgets to display the data for the user. All that works fine. The "title" appears on the left and the Entry text area appears to the right with the appropriate text loaded.
However, at that point, I can't edit the data. Clicking on any of the Entry texts does nothing. UNLESS I switch to the root window, then back to the toplevel. Then I can edit the data.
I want to be able to edit the data right away, then click the "Update" button to re-save the data and update the dictionary.
I'm using Python 3.4 on Windows 8.1.
Here's the relevant code:
def displayData(self):
tk = __import__("tkinter")
self.projectStrVars = {} # list of the StrVariables used for the Entry widgets
# create the frame to hold the labels and entry widgets
dataFrame = tk.LabelFrame(self.projectWin, width=800, height=458, text="Project Data: ")
dataFrame.grid(row=0, column=0, columnspan=2, sticky='WE', padx=5, pady=5, ipadx=5, ipady=5)
# populate the form Labels and Entry widgets with the data in self.projectData
for index in range(self.numFields):
key = self.formFields[index]
value = self.projectData[key]
tk.Label(dataFrame, text=key).grid(row = index+1, column = 0)
self.projectStrVars[index] = tk.StringVar()
self.entry = tk.Entry(dataFrame, textvariable = self.projectStrVars[index])
self.entry.grid(row = index+1, column = 1)
self.entry.insert(index, value)
updateButton = tk.Button(dataFrame, text="Update", command=self.storeNewProjectData)
updateButton.grid(row=20, column=0, columnspan=2)
Ok, I found the problem. It came down to WHEN the toplevel was being created. I was originally calling it before accessing the file and loading the data into a dictionary. For whatever reason, that prevented the toplevel from getting control/focus (whatever it's called) until/unless I switched to the root window and then back.
I changed it so that the toplevel is being created right before the data is displayed to the user (after the file is opened and the data is loaded into the dictionary). Works great.
I just started working with Tkinter today and can't seem to figure out how to auto-update the text of a button (like binding).
The buttons are dynamically created:
#creates frames visualization
for frame_num in range(OSBehavior.NUM_FRAMES):
f_label = Label(frames_frame, text='Frame ' + str(frame_num))
f_label.grid(row=frame_num, column=0, padx=5)
f_button = Button(frames_frame, width=30, textvariable=msim.frames[frame_num].page.name)
f_button.grid(row=frame_num, column=1, padx=5, pady=3)
When msim.frames[frame_num].page.name (type is string) changes, I'd like the button text to reflect that. It indeed changes as I have a text-based version running at the same time. However, the button text doesn't change.
If possible, I'm avoiding setting the text of the buttons manually in the backend code.
If someone could point me in the right direction, that would be awesome. Thanks!
msim.frames[frame_num].page.name needs to be an instance of a StringVar, it won't work with ordinary variables.