Python tkinter Entry widget displays bar characters instead of line breaks - python

I'm coding a very simple Python Tkinter GUI to drive a command-line python script.
My GUI will run on a Windows host and I want it to display the multi-line plain-text output of the script, which is returned to the GUI as a string containing - of course - line breaks (\n characters).
So, I put a Text widget into the GUI and when my script - in example - returns an output which starts with this substring:
RESULT STATUS: OK- Service is currently running\n\nDETAILS: ...
the text displayed contains black vertical bars (|) whenever there is a \n line break character.
Lines are correctly broken but those strange bars make me think that the \n line break character is not correctly decoded, and I don't want the bars in my displayed output.
Any idea on how to make Tkinter display correctly line-endings? Thanks in advance.
Code
This is the workflow for my GUI:
I click a button, which calls the callMe() callback function
The callMe() function parses the arguments coming from an Entry widget and then invokes the python command-line script
The script returns the above-mentioned string and this is used by the callback to update the text of the Text widget
Here is the code:
#init the GUI elements and the Text widget
from Tkinter import *
root = Tk()
top = Frame(root)
outputFrame = Frame(top)
outputFrame.pack(side='top', padx=20, pady=20)
outputTextArea = Text(outputFrame, yscrollcommand=scrollbar.set, width=150, height=40)
outputTextArea.pack(side='left', expand=YES, fill='both')
#callback executed when the button is clicked
def callMe()
#get parameters
# .....
#command line execution script execution
process = subprocess.Popen(command_line, stdout=subprocess.PIPE, shell=True)
#get script output
matr = process.stdout.readlines()
#from a list of strings to a single string
output = "".join(matr)
#write output into the Text widget
outputTextArea.insert(0.0, output)

It could be an issue of '\r' characters before each '\n' character (you said you're on Windows).
Before updating the widget, try first:
text_output= text_output.replace('\r', '')
(text_output contains the output of your script, whose contents are to be inserted in the widget)
If you give us more information, we can help you more.

When to use the Entry Widget (from http://effbot.org/tkinterbook/entry.htm)
The entry widget is used to enter text strings. This widget allows the user to enter one line of text, in a single font.
To enter multiple lines of text, use the Text widget.

Without seeing your code it's impossible to say for certain what the problem is. You say you use a text widget but the behavior seems consistent with using an entry widget. Do you still see the vertical bars with the following code?
import Tkinter as tk
OUTPUT = "RESULT STATUS: OK- Service is currently running\n\nDETAILS: ... "
root = tk.Tk()
text = tk.Text(root, height=4, width=80)
text.pack(fill="both", expand="true")
text.insert("end", OUTPUT)
root.mainloop()

I solved the question thanks to ΤΖΩΤΖΙΟΥ..it was a matter of /r characters..I guess that when I invoke the subprocess.Popen to run my command-line script, a Windows Command Prompt is open, the script executed, and the standard output stream is returned by the Prompt with /r/n line-endings instead of just /n.
Anyway, I will post the code and GUI workflow all the way...
Workflow
This is the workflow for my GUI:
I click a button, which calls the callMe() callback function
The callMe() function parses the arguments coming from an Entry widget and then invokes the python command-line script
The script returns the above-mentioned string and this is used by the callback to update the text of the Text widget
Code
Here is the code:
#init the GUI elements and the Text widget
from Tkinter import *
root = Tk()
top = Frame(root)
outputFrame = Frame(top)
outputFrame.pack(side='top', padx=20, pady=20)
outputTextArea = Text(outputFrame, yscrollcommand=scrollbar.set, width=150, height=40)
outputTextArea.pack(side='left', expand=YES, fill='both')
#callback executed when the button is clicked
def callMe():
#get parameters
# .....
#command line execution script execution
process = subprocess.Popen(command_line, stdout=subprocess.PIPE, shell=True)
#get script output
matr = process.stdout.readlines()
#from a list of strings to a single string
output = "".join(matr) # <<< now it is: output = "".join(matr).replace('\r', '')
#write output into the Text widget
outputTextArea.insert(0.0, output)
Thanks very much to all of you, guys!

Related

How do you make Tkinter GUI output text from print statement?

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.

tkinter tag_config does not work

I am building a notepad like application in tkinter-python. There is an option to change the font of the text writen in the text field of the application.
I have created a Font Chooser popup screen to be called from main window on clicking 'font' menu, which basically creates a FontChooser class object and passes to the main window, which sets the font in man window.
A sample of the code where font is getting set in main window is,
root = Tix.Tk(className="Notepad")
notepad = ScrolledText(root, width=100, height=100)
def open_font():
font = MyFont.askChooseFont(root)
notepad.tag_add("bt", "sel.first", "sel.last")
notepad.tag_config("bt", font=font.getFontTuple())
Now when I first run the application and select a portion of text and change the font, it works correctly. But after that, whatever portion of text I am selecting and changing the font, it is ignoring the selection and applying the font on the whole text. Can anyone let me know what is the problem here?
IDLE uses tag_config to syntax color python code and it works on all Python versions and major OSes for the last 15 years.
To have some idea of why it seems to fail for you, you need to find an MCVE that fails. Start without tix and scrollbars. (Tix is deprecated in 3.6 and bugs are not being fixed.) Also notice that your code uses the same tag for each selection, so that when you change the configuration, it applies to all previous selections.
Here is simplified code that works as intended and expected.
import tkinter as tk
import time
root = tk.Tk()
text = tk.Text(root)
text.pack()
text.insert('1.0', "line 1\nline 2\nline 3\n")
text.tag_add('bg', '1.0', '1.4')
text.tag_config('bg', background='red')
root.update()
time.sleep(1)
text.tag_add('bg', '2.0', '2.4')
text.tag_config('bg', background='blue')
root.update()
You could try modifying it step by step until it either reproduces your problem or does what you want.
EDIT with example modification: use 'sel.first' and 'sel.last' instead of hard-coded indexes.
import tkinter as tk
import time
root = tk.Tk()
text = tk.Text(root)
text.pack()
text.insert('1.0', "line 1\nline 2\nline 3\n")
root.update() # make text visible for selection
input('select some text')
text.tag_add('bg', 'sel.first', 'sel.last')
text.tag_config('bg', background='red')
root.update() # make change visible
input('select some text')
text.tag_add('bg', 'sel.first', 'sel.last')
text.tag_config('bg', background='blue')
root.update() # make 2nd change visible
input('look at result')
Run in console. Move tk window so console and GUI are both visible. Make selection as prompted. Click on console* and hit return to allow input statement to return. Repeat. The result for me is that both selections, but not everything, turns blue. I suggest changing font instead of bg color for the next experiment.
On Windows, the selection highlighting in the tk windows disappears when one clicks on the console because Windows only allows visible selection in one window at a time. However, the select markers are still present in the text widget so that tag_add still works.

Tkinter Entry returns float values regardless of input

I have some pretty simple code right now that I am having issues with.
root = Tk()
label1 = Label(root, text ="Enter String:")
userInputString = Entry(root)
label1.pack()
userInputString.pack()
submit = Button(root,text = "Submit", command = root.destroy)
submit.pack(side =BOTTOM)
root.mainloop()
print(userInputString)
When I run the code everything operates as I would expect except
print(userInputString)
for an input asdf in the Entry print will return something like 0.9355325
But it will never be the same value back to back always random.
I am using python 3.5 and Eclipse Neon on a Windows 7 Machine.
Ultimately the goal is to accept a string from the user in the box that pops up and then be able to use that value as string later on. For example, it might be a file path that needs to be modified or opened.
Is Entry not the correct widget I should be using for this? Is there something inherently wrong with the code here? I am new to python and don't have a lot of strong programming experience so I am not even certain that this is set up right to receive a string.
Thanks in advance if anyone has any ideas.
There are two things wrong with your print statement. First, you print the widget, not the text in the widget. print(widget) prints str(widget), which is the tk pathname of the widget. The '.' represents the root window. The integer that follows is a number that tkinter assigned as the name of the widget. In current 3.6, it would instead be 'entry', so you would see ".entry".
Second, you try to print the widget text after you destroy the widget. After root.destroy, the python tkinter wrapper still exists, but the tk widget that it wrapped is gone. The following works on 3.6, Win10.
import tkinter as tk
root = tk.Tk()
label = tk.Label(root, text="Enter String:")
entry = tk.Entry(root)
def print_entry(event=None):
print(entry.get())
entry.bind('<Key-Return>', print_entry)
entry.focus_set()
submit = tk.Button(root, text="Submit", command=print_entry)
label.pack()
entry.pack()
submit.pack()
root.mainloop()
Bonus 1: I set the focus to the entry box so one can start typing without tabbing to the box or clicking on it.
Bonus 2: I bound the key to the submit function so one can submit without using the mouse. Note that the command then requires an 'event' parameter, but it must default to None to use it with the button.
The NMT Reference, which I use constantly, is fairly complete and mostly correct.

How do I execute a callback when the Enter key is pressed after entering text into a Text widget?

I've really tried to find this out but have only discovered disconnected snippets such as tag_bind. I cannot work out how to use this (if indeed this is the right way forward).
I've got a Text widget into which the user can enter some text (such as a command or question) and I'd like to execute a function that reads that text then responds to it. Obviously I could provide a nearby Button widget which when clicked, reads the text in the Text widget. But I don't want people to have to move their hand from the keyboard to the mouse. I'd just like the callback to be triggered if they hit the Enter key. How do I do it please?
import Tkinter as tk
def on_enter(event):
widget = event.widget
print(widget.get())
root = tk.Tk()
entry = tk.Entry()
entry.pack(padx=5, pady=5)
entry.focus()
entry.bind('<Return>', on_enter)
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

Categories

Resources