Drawing on tkinter canvas from command line - python

I read some tutorial (basic) on tkinter and learned how to create a mainloop and add gui elements to that. Also learned how to bind actions to button widgets.
Now I would like to do this:
launch the tkinter canvas
be able to read command from the console and update the canvas after those commands.
example:
I write command with arguments on the console and some graphics elements is being added to the canvas (and canvas is updated after).
Is something possible, maybe threding related?
Can you point me in one direction which you think is the most reasonable to follow?

Here's a simple demo of grabbing user input from the console via the standard input function. This technique is a little clunky, since we have to explicitly tell Tkinter to fetch the input string by clicking on a Button (or some other GUI event), but that might not be a big deal for your application.
import tkinter as tk
root = tk.Tk()
stuff = tk.StringVar()
display = tk.Label(root, textvariable=stuff)
display.pack()
def get_input():
s = input("CMD: ")
stuff.set(s)
tk.Button(root, text="Get input", command=get_input).pack()
root.mainloop()
When you click on the "Get input" Button, the "CMD: " prompt will be printed in the console window. Once the input is entered, the string is copied to the Label. Bad Things™ will happen if you click the button again before the input line is entered. :)

Came up with this:
from Tkinter import *
import random
root = Tk()
width = 800
height = 600
def key(event):
s = raw_input("CMD: ")
if s == 'quit':
root.destroy()
if s == 'l':
x1 = random.randint(0,width)
x2 = random.randint(0,width)
y1 = random.randint(0,height)
y2 = random.randint(0,height)
frame.create_line(x1,y1,x2,y2)
frame.focus_force()
frame = Canvas(root, width=width, height=height)
frame.bind("<Key>", key)
frame.pack()
frame.focus_set()
root.mainloop()
In this way it is a bit complicated because before entering something on the console I have to get the focus back clicking on its window. Maybe it would be nicer to read command from Tkinter directly and then opening dialogs for setting up command parameters.

Related

When designing a desktop app to be resizable, are there any hidden drawbacks to using two sequential Tk instances?

In order to ensure that my program will be compatible with any screen size, I first have a very small 'setup' window open when the program is launched, which will then allow the user to select the desired dimensions for the main window.
Once the 'finalize' button on the 'setup' window is pressed, the setup window disappears and the main window opens. This is handled by calling .destroy() on the setup window and creating a new instance with Tk() inside the button's command function.
To make the example code more compact and highlight the part which is relevant to this question, I left out the size selector and just set the main window to be a fixed 800x800 pixels here:
import tkinter as tk
# Start out small, to fit on any screen size
startWindow = tk.Tk()
startWindow['width'] = 400
startWindow['height'] = 200
startWindow.title("Setup")
# Use lists here so that the widgets created inside 'initializeMainWindow' will be
# accessible from the global scope.
mainWindow = [None]
mainWindowButtons = [None]
# Closes the 'setup' window and opens a new window which will be the main application.
# Also initializes all widgets which will belong to the new window.
def initializeMainWindow():
startWindow.destroy()
mainWindow[0] = tk.Tk()
print("New window initialized.")
mainWindow[0]['width'] = 800
mainWindow[0]['height'] = 800
mainWindow[0].title("Main Window")
mainWindowButtons[0] = tk.Button(master=mainWindow[0], text="Test", command=testNewWindow)
mainWindowButtons[0].place(x=350, y=375, width=100, height=50)
mainWindow[0].bind('<Key>', test2)
#mainWindow[0].mainloop()
# To demonstrate that the new window is interactive
def testNewWindow():
print("Success!")
# Works whether or not 'mainloop' is called on the new window
def test2(e):
print("Also success! '" + e.keysym + "' key pressed.")
setSizeButton = tk.Button(master=startWindow, text="Resize", command=initializeMainWindow)
setSizeButton.place(x=150, y=75, width=100, height=50)
# This prints BEFORE the 'setup' window is closed, as expected
# If 'mainloop' is uncommented, it works the same except IDLE won't show the '>>>' prompt
# after the text "Not yet initialized".
if startWindow:
print("Not yet initialized.")
#startWindow.mainloop()
This works exactly as I intend it to so far. However I'm aware that when an application has more than one window, it's standard to use Toplevel() and not create multiple instances of Tk(). But this example isn't trying to run multiple instances of Tk() at once, instead, they're sequential: think of it as a separate 'launcher' program that then opens the main program, as is common on many desktop games. (This is exactly how I'm using it in the full program).
Before building on a potentially flawed foundation, I'd like to know if there are any hidden problems which could surface later with this approach. If the consensus is that it's better to switch to using Toplevel() or even have two separate Python files, I'd rather find out sooner than later!
I have already viewed this question and answer:
What's the difference between tkinter's Tk and Toplevel classes?
but they don't cover this specific question.
A related issue: I also experimented with calling .mainloop() vs. not calling it, and at least with the Mac version of IDLE and using Python 3.9.4, it seems to be optional. This was discussed here When do I need to call mainloop in a Tkinter application?
and it looks like the reason omitting it still works is that IDLE has its own event loop (credit to Ori for this solution https://stackoverflow.com/a/8684277/18248018).
If this is the case, is it advisable to explicitly call .mainloop() anyway (where I have it commented out in the example code) for reliability? I haven't tested this yet, but if it's something IDLE does, I'd guess the automatic event loop functionality might not transfer over when I convert the program to a standalone app using py2app, without explicit calls to .mainloop() in the .py file.
Here's the code in your answer with a minor change — the finalFrame doesn't get created until the openMainWindow() function is called since it's not needed until then. I think this is a little more logical instead of having creation and usage scattered about.
import tkinter as tk
window = tk.Tk()
# Start out with a small popup, so it will fit on any screen size.
# Width and height must be specified as attributes of the frame, so that the
# frame can set the window's size.
setupFrame = tk.Frame(master=window, bg='green', width=400, height=200)
# Using 'pack' will cause the window's size to be equal to setupFrame's size
# Using 'place' here would not work: the window would open with the default size
# (small and square) and cut off the frame.
setupFrame.pack()
# In the actual program, these values will be determined by user input
W = 800
H = 800
def openMainWindow(w, h):
# finalFrame # Uncomment if ever needed.
# Destroy the 'launcher' frame once it is no longer needed.
setupFrame.destroy()
# This frame will reset the size of the window and will display the
# program's main content.
finalFrame = tk.Frame(master=window, bg='lightblue', width=w, height=h)
sizeScalingExample = tk.Label(master=finalFrame, bg='purple', fg='white',
text="This label's size is set with `place` and "
"depends on the frame's size.")
finalFrame.pack()
sizeScalingExample.place(x = 0.125*w, y = 0.375*h, width=0.75*w, height=0.25*h)
resizeButton = tk.Button(master=setupFrame, text="Finalize", bg="yellow",
fg="darkblue", command=lambda: openMainWindow(W, H))
resizeButton.place(x=150, y=75, width=100, height=50)
window.mainloop()
Following martineau's suggestion in the comments of switching between two different Frame widgets, I rewrote the code in my question as the following. This achieves the same visual effect as the original code, and eliminates any need for a second Tk() instance.
This is a minimal example of the method I will be using in my resizable application:
import tkinter as tk
window = tk.Tk()
# Start out with a small popup, so it will fit on any screen size.
# Width and height must be specified as attributes of the frame, so that the
# frame can set the window's size.
setupFrame = tk.Frame(master=window, bg='green', width=400, height=200)
# Using 'pack' will cause the window's size to be equal to setupFrame's size
# Using 'place' here would not work: the window would open with the default size
# (small and square) and cut off the frame.
setupFrame.pack()
# This frame will reset the size of the window and will display the program's
# main content
finalFrame = tk.Frame(master=window, bg='lightblue')
sizeScalingExample = tk.Label(master=finalFrame, bg='purple', fg='white',
text="This label's size is set with `place` and "
"depends on the frame's size.")
# In the actual program, these values will be determined by user input
W = 800
H = 800
def openMainWindow(w, h):
# Hide the 'launcher' once it is no longer needed
setupFrame.pack_forget()
finalFrame['width'] = w
finalFrame['height'] = h
finalFrame.pack()
sizeScalingExample.place(x = 0.125*w, y = 0.375*h, width=0.75*w, height=0.25*h)
resizeButton = tk.Button(master=setupFrame, text="Finalize", bg="yellow",
fg="darkblue", command=lambda: openMainWindow(W, H))
resizeButton.place(x=150, y=75, width=100, height=50)
window.mainloop()
Although the ability to implement the same functionality using only Frame widgets means there is no practical reason to use a second Tk() instance, I'd still be interested from a theoretical perspective to learn about any unexpected outcomes which could result from using the original approach.

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.

Closing a Tkinter Entry Box in Python

I am trying to create a simple popup text entry for the user in which a user enters a text and hits submit (a button). Upon clicking submit, I want the popup entry box to close off and continue on with the rest of the code. Following is a sample code for display that I borrowed from an old post here:
from Tkinter import *
root = Tk()
nameLabel = Label(root, text="Name")
ent = Entry(root, bd=5)
def getName():
print ent.get()
submit = Button(root, text ="Submit", command = getName)
nameLabel.pack()
ent.pack()
submit.pack(side = BOTTOM)
root.mainloop()
print "Rest of the code goes here"
I don't have much experience with Tkinter so I am not sure where and how exactly to call the appropriate functions for closing the entry box after the user hits 'Submit'. My guess is it would have to be inside the getName() function?
If I understand you correctly, then all you need to do is call the root window's destroy method at the end of the getName function:
def getName():
print ent.get()
root.destroy()
Doing so is equivalent to manually clicking the X button in the corner of the window.
Alternate method:
since there isn't much to your popup you could also eliminate several lines of code in your GUI, save some CPU and get pretty much the same output with this:
submitvariablename=raw_input('Please enter a Name')
same functionality and much faster, cleaner.
Just a thought.

How do I prepopulate a text field with suggested text in Tkinter?

I'm trying to prepopulate a text field based on the most recent entry. As this is not a Listbox, I don't see how to do it, and I'm not seeing any examples on the web. Thanks.
Update. I've managed to find a partial way of doing this. Still wondering, is it possible to supply suggested text in Tkinter which fades when the text box is clicked?
from Tkinter import *
app = Tk()
app.title("GUI Example")
app.geometry('560x460+200+200')
x = Text(app)
x.insert(END, "Before")
x.pack()
def replace():
x.delete(1.0, END)
x.insert(END, "After")
abutton = Button(app, text="Click me", command=replace)
abutton.pack()
app.mainloop()
Well, I personally don't know of any options to do this (any answers giving one will easily trump this one).
However, you can closely mimic this behavior with a little coding. Namely, you can bind the textbox to a function that will insert/remove the default text for you.
Below is a simple script to demonstrate:
import Tkinter as tk
tk.Tk()
textbox = tk.Text(height=10, width=10)
textbox.insert(tk.END, "Default")
textbox.pack()
# This is for demonstration purposes
tk.Text(height=10, width=10).pack()
def default(event):
current = textbox.get("1.0", tk.END)
if current == "Default\n":
textbox.delete("1.0", tk.END)
elif current == "\n":
textbox.insert("1.0", "Default")
textbox.bind("<FocusIn>", default)
textbox.bind("<FocusOut>", default)
tk.mainloop()
Notice how:
When you click in the top textbox, the default text disappears.
When you click in the bottom textbox, the top one loses focus and the default text reappears.
This behavior will only occur if there is nothing in the top textbox.

Widgets disappear after tkMessageBox in Tkinter

Every time I use this code in my applications:
tkMessageBox.showinfo("Test", "Info goes here!")
a message box pops up (like it is supposed to), but after I click OK, the box disappears along with most of the other widgets on the window. How do I prevent the other widgets from disappearing?
Here Is My Code:
from Tkinter import *
import tkMessageBox
root = Tk()
root.minsize(600,600)
root.maxsize(600,600)
p1 = Label(root, bg='blue')
p1.place(width=600, height=600)
b1 = Button(p1, text="Test Button")
b1.place(x="30", y="50")
tkMessageBox.showinfo("Test", Info")
root.mainloop()
Ok, there are a few things going wrong here. First, your label has no string or image associated with it. Therefore, it's width and height will be very small. Because you use pack, the containing widget (the root window) will "shrink to fit" around this widget and any other widgets you pack in the root window.
Second, you use place for the button which means its size will not affect the size of the parent. Not only that, but you place the button inside the very tiny label. Thus, the only thing controlling the size of the parent is the label so the main window ends up being very small.
You have another problem is that you're showing the dialog before entering the event loop. I'm a bit surprised that it even works, but Tkinter sometimes does unusual things under the covers. You should enter the event loop before calling the dialog.
Try this variation of your code as a starting point:
from Tkinter import *
import tkMessageBox
def showInfo():
tkMessageBox.showinfo("Test","Info")
root = Tk()
p1 = Label(root, bg='blue', text="hello")
p1.pack()
b1 = Button(root, text="Test Button", command=showInfo)
b1.pack()
root.mainloop()

Categories

Resources