I'm using TKinter (I'm new with GUI tools), and I would like to know if it is possible to add (or activate) a entry with base on the answer of a option menu. Below is a part of the code
from Tkinter import *
win=Tk()
Label(win, text="Is This a Data Cube?",font='20').grid(row=14, column=0,sticky=W)
DataCubeValue = StringVar(win)
DataCubeValue.set("False")
DataCube = OptionMenu(win,DataCubeValue,"True","False")
DataCube.grid(row=15, column=0,sticky=W)
If the answer is True is choosen I would like to display this:
Label(win, text="X and Y values (x,y)",font='20').grid(row=14, column=1,sticky=W)
XYValue = StringVar(win)
XYValue.set("10,7")
XY = Entry(win,textvariable=XYValue)
XY.grid(row=15, column=1,sticky=W)
A central idea of GUI programming is to register code to be executed in reaction of user actions. Such code is usually named callback (the toolkit call it back depending on user actions on the interface).
You can bind to DataCubeValue changes with the following line. callback method (to be defined before) will be cause each time the value of DataCubeValue change.
DataCubeValue.trace("w", callback)
In the callback method, you can either choose to place the block of code with Label and Entry instantiation, but think that callback will be called every time the user change the OptionMenu value. You could either deactivate the OptionMenu once the user used it, but I would advise to instantiate your widgets in the initial run, and just display or hide them from the callback.
def callback(*args):
if DataCubeValue.get() == "True":
label.grid(row=14, column=1,sticky=W)
XY.grid(row=15, column=1,sticky=W)
else:
label.grid_forget()
XY.grid_forget()
Related
I know how to change a label's color with a command attached to a button, but I want to do it programmatically, based on a variable's value.
If an error message is sent to the build_window function, I want to display the error message on a label with a red background, and if a non-error message is sent, no message is to be displayed and no color changes are expected.
In my code, I call the function to build a tkinter window two times.
The first time, I pass it a non-error message, the second time, an error message.
My code works for displaying the error message when expected, the problem is only with changing the background color of the error message label.
If I un-comment the l1.config(bg="red") command shown, I get this error when passing an error message to build_window():
UnboundLocalError: local variable 'l1' referenced before assignment
on the l1.config(bg="red") command.
If I move the entire if structure to just before mainloop(), I get this error even when passing the non-error message:
UnboundLocalError: local variable 'error_message' referenced before assignment
on the l1=Label(root,text = error_message) command.
If I add global l1 and global error_message to build_window(), the error I get is this:
tkinter.TclError: invalid command name ".!label"
I also tried just initially defining the label with bg="red", hoping that when I send a zero-length string, it would still be gray, but it displays a bit of red in the middle of the l1 label.
I've been writing simple python for a while, but I'm new to GUI apps and tkinter just confuses me every time I try something.
I've searched for a solution but could not find anything addressing changing a window without using a command attached to a button.
Any advice or clarification would be greatly appreciated!
from tkinter import Tk, IntVar, Label, mainloop, Button
def build_window(incoming_error_message) :
if incoming_error_message == "initial value" :
error_message = ""
else :
#l1.config(bg="red")
error_message = incoming_error_message
def quitHandler():
root.destroy()
root = Tk()
l1=Label(root,text = error_message)
l1.grid(row=0,column=0)
quitButton = Button(root, text="To end, click here",command=quitHandler)
quitButton.grid(row=1,column=0)
mainloop()
def call_build_window() :
build_window("initial value")
build_window("Error!")
call_build_window()
One option is to place the label creation within the condition and reorder the surrounding code a little bit. I also replaced grid by pack and skipped keeping names for variables which are actually constants, all that changes were made to keep the code small:
from tkinter import Tk, Label, mainloop, Button
def build_window(error_message):
root = Tk()
if error_message == "initial value":
Label(root, text="").pack()
else:
Label(root, text=error_message, bg="red").pack()
def quitHandler():
root.destroy()
Button(root, text="To end, click here", command=quitHandler).pack()
mainloop()
def call_build_window():
build_window("initial value")
build_window("Error!")
call_build_window()
This of course is a small case study and shouldn't be used to build on it.
I think it should be reworked so that there is a maybe function that takes the indicator if the message is as a second argument with the text always being displayed. All that would be much easier in a more object-oriented approach. (Also remember that Tk() is normally called only once and secondary top-level windows are created via Toplevel(), see Python - Tkinter - invisible text in ttk.Entry in sub window or tkinter: Open a new window with a button prompt. )
By using tkinter.ttk it's also possible to change the widget's style at runtime, see the answer to Changing ttk widget text color for an example.
I have a tkinter GUI which includes an Entry Widget (tk.Entry). At the moment this displays a numeric value, which can be incremented up/down by a couple of tkinter Buttons. In addition to incrementing the value displayed in the Entry Widget, whenever the Button is clicked it executes a command which updates the appropriate setting on a physical instrument with the new value from the Entry Widget.
I am wondering if it is possible at all to also have the option that if the user types a number into the Entry Widget to overwrite what is already there, that this can execute the same command as clicking the up/down buttons? For example, if the value is set to 10, and the user enters 100, then the command is executed to update the real instrument to 100.
I tried to add the code command=mycommandname to the Entry Widget (e.g. input_wavelength = tk.Entry(pwr_mtr_ctrl_frame, relief=tk.GROOVE, font=( "Ariel", 11), justify='center', validate='key', vcmd=pwr_vcmd, command=mycommandname) but get the error "unknown option "-command""
I guess this means that the Entry Widget does not have the option to execute a function, like a Button widget does? Are there ways to implement this somehow?
def print_something(*args):
print('hello world')
root = TK()
entry1 = Entry(root)
entry1.bind("<Return>", print_something)
# you can use other keys and replace it with "<Return>". EX: "f"
# by default, this function will pass an unknown argument to your function.
# thus, you have to give your function a parameter; in this case, we will use *args
root.mainloop()
According to what you described you would be better "served" by using a Spinbox.
This being said you need to bind a function to the <Return> event captured by the Entry if you want to execute something when the user enters something:
Here is how to do it:
input_wavelength = tk.Entry(pwr_mtr_ctrl_frame, relief=tk.GROOVE, font=( "Ariel", 11), justify='center', validate='key', vcmd=pwr_vcmd)
input_wavelength.bind("<Return>",mycommandname)
If mycommandname is also used as a command you need to declare it like this:
def mycommandname(event=None):
...
The question's in the title, essentially: how do I get the validatecommand callback to continue being called after setting the Entry's textvariable? Here's the Minimum Working Example (MWE):
import tkinter as tk
root = tk.Tk()
sv = tk.StringVar()
def callback():
print(sv.get())
sv.set('Set Text.')
return True
e = tk.Entry(root, textvariable=sv, validate="focusout",
validatecommand=callback)
e.grid()
e = tk.Entry(root)
e.grid()
root.mainloop()
Note that the second tk.Entry widget is there to allow the first one to lose focus, which is the event we're trying to capture.
As the code is now, when you run it, you can change the top Entry widget's text once. It'll correctly get set to Set Text. Then, if you try to change the Entry's text again, the new text will be in the widget, but the callback doesn't happen.
On the other hand, if you comment out the sv.set('Set Text.') code, this behavior completely disappears, and the callback gets called as many times as you wish.
How can I have the sv.set() functionality, while still maintaining the callback getting called every time the Entry widget loses focus?
This is discussed in the Tk manual page for entry:
The validate option will also set itself to none when you edit the entry widget from within either the validateCommand or the invalidCommand. Such editions will override the one that was being validated.
Presumably, this is done to avoid infinite recursion.
You can run this (instead of the given Tcl code, after idle {%W config -validate %v})
root.after_idle(lambda: e.config(validate="focusout"))
from the callback to schedule a reconfiguration of the widget to enable validation again (after changing your sources so that e is the right Entry widget, i.e. not the second one).
Below is some code that I'm testing with. In this code, I am able to create a window and have a Label on top and a Entry field on bottom. When I type in that entry field, I can dynamically change what's in the label. Now, I have included a function that is trying to evaluate a variable assigned to "tex", by storing what is predefined in the Entry widget. Which is "cat". This is picked up by:
tex = e.get()
I understand that get() is not changing dynamically as I change the text in the entry widget. So it cannot change to "dog" when I change the string in the entry widget. Is this possible? Here is the code:
from Tkinter import *
import time
root = Tk()
def change():
if tex == ("cat"):
time.sleep(0.5)
pass
else:
time.sleep(0.5)
e.delete(0, END)
e.insert(0, "dog")
v = StringVar()
e = Entry(root, textvariable=v)
e.insert(0, "cat")
e.pack(side=BOTTOM)
tex = e.get() #When text is defined with get(), it does not change
#dynamically with the entry widget
l = Label(root, textvariable=v)
l.pack(side=TOP)
change()
root.mainloop()
Any help would be appreciated.
To answer your specific question, no, there is no way for tex to magically keep updated when the entry widget changes. That feature is exactly why the textvariable attribute exists -- it causes the variable to always be updated with the value in the entry widget. If you need the data in another variable, you will have to call the .get() method.
That being said, you can set a trace on the variable, or a binding on the entry widget, and have it call code that can update tex to whatever you want when the entry widget changes.
For example, the following code shows how to have callback called whenever the value in the entry widget changes:
def callback(*args):
global tex
tex = e.get()
v.trace("w", callback)
For more information about what arguments are passed to the callback, see What are the arguments to Tkinter variable trace method callbacks?
All that being said, you have a couple of critical flaws in your code. First, you are creating the StringVar incorrectly. It needs to be v = StringVar() (note the trailing ()).
Also, you should never call sleep in the main thread of a GUI program. Read up on the after method if you want to execute some code after some time has elapsed.
I'm using Tkinter to create a GUI for a simple geometry calculator I'm creating.
Basically, what I have is an Entry box. What I want is for the program/GUI/system to detect when the user of the program hits the 'Enter' or 'return' key WHILE they are in the Entry box. When this is detected, I want the contents of the Entry box to be appended to a list I have defined earlier. I also want a simple label to be created on the GUI that displays the contents of the list (including the appended item(s)). Note that the list begins with nothing in it.
Here is my code so far:
from tkinter import *
#Window setup(ignore this)
app = Tk()
app.title('Geometry Calculator')
app.geometry('384x192+491+216')
app.iconbitmap('Geo.ico')
app.minsize(width=256, height=96)
app.maxsize(width=384, height=192)
app.configure(bg='WhiteSmoke')
#This is the emtry list...
PointList = []
#Here is where I define the variable that I will be appending to the list (which is the object of the Entry box below)
StrPoint = StringVar()
def list_add(event):
#I don't really know how the bind-checking works and how I would implement it; I want to check if the user hits enter while in the Entry box here
if event.char == '':
PointList.append(StrPoint)
e1 = Entry(textvariable=StrPoint).grid(row=0, column=0)
app.bind('<Return>', list_add)
mainloop()
I don't really know the proper way to check for 'Return' and then use it in an if statement.
I hope you understand what I'm trying to get help with, and I've looked all around for an explanation that I could understand with no success.
Instead of binding with the app just bind it with the Entry widget object,i.e,e1
from tkinter import *
#Window setup(ignore this)
app = Tk()
app.title('Geometry Calculator')
app.geometry('384x192+491+216')
app.iconbitmap('Geo.ico')
app.minsize(width=256, height=96)
app.maxsize(width=384, height=192)
app.configure(bg='WhiteSmoke')
#This is the emtry list...
PointList = []
#Here is where I define the variable that I will be appending to the list (which is the object of the Entry box below)
StrPoint = StringVar()
def list_add(event):
print ("hello")
#I don't really know how the bind-checking works and how I would implement it; I want to check if the user hits enter while in the Entry box here
if event.char == '':
PointList.append(StrPoint)
e1 = Entry(textvariable=StrPoint)
e1.grid(row=0, column=0)#use grid in next line,else it would return None
e1.bind('<Return>', list_add)# bind Entry
mainloop()
The solution is to set the binding on the widget itself. That way, the binding will only apply while focus is on that widget. And since you're binding on a specific key, you don't need to check for the value later. You know the user pressed return, because that's the only thing that will cause the binding to fire.
...
e1.bind('<Return>', list_add)
...
You have another problem in that your list_add function needs to call the get method of the variable rather than accessing the variable directly. However, since you aren't using any of the special features of a StringVar, you really don't need it -- it's just one more thing you have to manage.
Here's how to do it without the StringVar:
def list_add(event):
PointLit.append(e1.get())
...
e1 = Entry(app)
e1.grid(row=0, column=0)
e1.bind('<Return>', list_add)
Note that you need to create the widget and lay out the widget in two steps. Doing it the way you did it (e1=Entry(...).grid(...) will cause e1 to be None since that is what .grid(...) returns.