what is the difference between a variable and StringVar() of tkinter - python-3.x

Code:
import tkinter as tk
a = "hi"
print(a)
a1 = tk.StringVar()
a1.set("Hi")
print(a1)
Output:
hi ##(Output from first print function)
AttributeError: 'NoneType' object has no attribute '_root' (Output from second print function)
My question:
What is the difference between a and a1 in above code and their use-cases. Why a1 is giving error?

A StringVar() is used to edit a widget's text
For example:
import tkinter as tk
root = tk.Tk()
my_string_var = tk.StringVar()
my_string_var.set('First Time')
tk.Label(root, textvariable=my_string_var).grid()
root.mainloop()
Will have an output with a label saying First Time
NOTE:textvariable has to be used when using string variables
And this code:
import tkinter as tk
def change():
my_string_var.set('Second Time')
root = tk.Tk()
my_string_var = tk.StringVar()
my_string_var.set('First Time')
tk.Label(root, textvariable=my_string_var).grid()
tk.Button(root, text='Change', command=change).grid(row=1)
root.mainloop()
Produces a label saying First Time and a button to very easily change it to Second Time.
A normal variable can't do this, only tkinter's StringVar()
Hopes this answers your questions!

StringVar() is a class from tkinter. It's used so that you can easily monitor changes to tkinter variables if they occur through the example code provided:
def callback(*args):
print "variable changed!"
var = StringVar()
var.trace("w", callback)
var.set("hello")
This code will check if var has been over-written (this mode is defined by the w in var.trace("w", callback).
A string such as "hello" is just a data type, it can be manipulated and read and all sorts, the primary difference is that if the string was assigned to a variable, such as a = 'hello', there is no way of telling if a has changed (i.e if now a = 'hello') unless you do a comparison somewhere which could be messy.
Put it simply: StringVar() allows you to easily track tkinter variables and see if they have been read, overwritten, or if they even exist which you can't easily do with just a typical a = 'hello'
Helpful : http://effbot.org/tkinterbook/variable.htm
Edit : Replaced 'variables' with 'tkinter variables' where appropriate as per #Bryan Oakley's suggestion

Tkinter is a wrapper around an embedded tcl interpreter. StringVar is a class that provides helper functions for directly creating and accessing such variables in that interpreter. As such, it requires that the interpreter exists before you can create an instance. This interpreter is created when you create an instance of Tk. If you try to create an instance of StringVar before you initialize tkinter, you will get the error that is shown in the question.
Once tkinter has been properly initialized and a StringVar instance has been created, it can be treated like any other python object. It has methods to get and set the value that it represents.

At the beginning add
root = tk.Tk()
These Variables are designed for tkinter. and these do not work independently.

Suppose if you are building a GUI calculator, you want to display the values the user inputs in the screen of the calculator. If the user is trying to add 5 + 5, we have to show, "5" "+" "5" in the display. And when the equals button is pressed, we want to display "10". That is the use of StringVar(). It holds the string equivalent of the value the interpreter holds.

Related

populating one combobox based on another combo box using tkinter python

from tkinter import *
from tkinter.ttk import Combobox
v1=[]
root = Tk()
root.geometry('500x500')
frame1=Frame(root,bg='#80c1ff',bd=5)
frame1.place(relx=0.5,rely=0.1,relwidth=0.75,relheight=0.1,anchor='n')
lower_frame=Frame(root,bg='#80c1ff',bd=10)
lower_frame.place(relx=0.5,rely=0.25,relwidth=0.75,relheight=0.6,anchor='n')
v=[]
def maincombo():
Types=["MA","MM","MI","SYS","IN"]
combo1=Combobox(frame1,values=Types)
combo1.place(relx=0.05,rely=0.25)
combo2=Combobox(frame1,values=v)
combo2.bind('<<ComboboxSelected>>', combofill)
combo2.place(relx=0.45,rely=0.25)
def combofill():
if combo1.get()=="MA":
v=[1,2,3,45]
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
if combo1.get()=="MM":
v=[5,6,7,8,9]
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
maincombo()
root.mainloop()
I want to populate the one combobox based on selection of other combobox I,e types.But failed to do so with simple functions.
Looking at you code, most of what you need is already there. The changes I have made are as follows:
Bound to combo1 rather than combo2 (as combo1 is the one you want to monitor)
Set combo1 and combo2 as global variables (so they can be used in the combofill method)
Set the combofill method to accept the event arg (it would raise a TypeError otherwise)
Use the .config method on combo2 rather than creating a new one each time
Set combo2 to be empty when neither "MA" or "MM" are selected
Here is my implementation of that:
from tkinter import *
from tkinter.ttk import Combobox
v1=[]
root = Tk()
root.geometry('500x500')
frame1=Frame(root,bg='#80c1ff',bd=5)
frame1.place(relx=0.5,rely=0.1,relwidth=0.75,relheight=0.1,anchor='n')
lower_frame=Frame(root,bg='#80c1ff',bd=10)
lower_frame.place(relx=0.5,rely=0.25,relwidth=0.75,relheight=0.6,anchor='n')
v=[]
def maincombo():
global combo1, combo2
Types=["MA","MM","MI","SYS","IN"]
combo1=Combobox(frame1,values=Types)
combo1.place(relx=0.05,rely=0.25)
combo1.bind('<<ComboboxSelected>>', combofill)
combo2=Combobox(frame1,values=v)
combo2.place(relx=0.45,rely=0.25)
def combofill(event):
if combo1.get()=="MA":
v=[1,2,3,45]
elif combo1.get()=="MM":
v=[5,6,7,8,9]
else:
v=[]
combo2.config(values=v)
maincombo()
root.mainloop()
A couple other ideas for potential future consideration:
I would recommend using the grid manager rather than the place manager as it will stop widgets overlapping, etc. (on my system, combo2 slightly covers combo1)
Use a dictionary rather than if ... v=... elif ... v= ... and then use the get method so you can give the default argument. For example:
v={"MA": [1,2,3,45],
"MM": [5,6,7,8,9]}. \
get(combo1.get(), [])
EDIT:
Responding to the question in the comments, the following is my implementation of how to make a "toggle combobox" using comma-separated values as requested.
As the combobox has already overwritten the value of the text area when our <<ComboboxSelected>> binding is called, I had to add a text variable trace so we could keep track of the previous value of the text area (and therefore append the new value, etc.). I am pretty sure that explanation is completely inadequate so: if in doubt, look at the code!
from tkinter import *
from tkinter.ttk import Combobox
root = Tk()
def log_last():
global last, cur
last = cur
cur = tx.get()
def append_tx(event):
if last:
v = last.split(",")
else:
v = []
v = list(filter(None, v))
if cur in v:
v.remove(cur)
else:
v.append(cur)
tx.set(",".join(v))
combo.selection_clear()
combo.icursor("end")
last, cur = "", ""
tx = StringVar()
combo = Combobox(root, textvariable=tx, values=list(range(10)))
combo.pack()
combo.bind("<<ComboboxSelected>>", append_tx)
tx.trace("w", lambda a, b, c: log_last())
root.mainloop()

Tkinter Entry validatation

I am writing a program in Python 3.6 using Tkinter where a customer has multiple(11) entry fields. I want these entry fields to only accept integers and also be able to define the maximum amount of characters.
I already have a function that does this. But this function only works for one entry field. I have tried entering variables with calling the function so it changes another entry field for example. I was not able to do this.
This is the function I have that works with 1 entry field.
def limitMuntgeld(self, *args):
value = self.invoerM.get()
if len(value) > 5:
self.invoerM.set(value[:5])
if value.lower() in "abcdefghijklmnopqrstuvwxyz-=[];/":
self.invoerM.set(value[:0])
This is the example entry field code that works with the function
self.invoerMuntgeld = Entry(self, font=('Arial', 14), textvariable=self.invoerM)
This is combined with a trace on the entry field posted below.
self.invoerM = StringVar()
self.invoerM.trace('w', self.limitMuntgeld)
I have also tried it with vcmd and validatecommand. However, no good results.
My endresult would be one function working with all entry fields. If anyone has any suggestions, I am all ears!
The proper way to do entry validation is with the validatecommand option rather than using trace. With the validation feature built into the widget you don't need a reference to the widget itself (though you can use it if you want).
When the validatecommand is run, you can have it pass in what the new value will be if the input is valid. You only need to check this value and then return True or False, without having to know which widget it applies to.
For example:
import tkinter as tk
def validate_input(new_value):
valid = new_value .isdigit() and len(new_value) <= 5
return valid
root = tk.Tk()
validate = root.register(validate_input)
for i in range(10):
entry = tk.Entry(root, validate="key", validatecommand=(validate, "%P"))
entry.pack(side="top", fill="x")
root.mainloop()
For information about what %P represents, and what else can be used as arguments to the command, see this question: Interactively validating Entry widget content in tkinter

Python & TkInter - How to get value from user in Entry Field then print it on terminal

I'm learning Python & Tkinter and following a tutorial. I'm making a program to do what I'm used to do in scripting languages such as Powershell, get a user value, and then do something with it, for starter.
I can't find any example code that looks like mine. So I'm asking for your help.
Basically, this program creates a frame with a Quit Button, an Entry widget where the user enters a value, & a OK button. Then, he presses the OK button, and the value should be printed in the python terminal.
I'm getting an error but I don't understand why. I'm following a tutorial so I'm doing it the way they show, by defining a class to create the frame, putting the buttons & text box inside it.
The function to get the user input text from the entry widget is set below, where I would put my other functions, if let's say, I would like to add functions for buttons to do a print. I managed to make the print work if the value to print is set before, but I can't get the value from the Entry Widget.
from tkinter import *
class Window:
def __init__(self, master):
self.master = master
master.title("Get a value and print it")
self.info = Label(master, text="Please write something then click ENTER")
self.info.pack()
self.ebox= Entry()
self.ebox.pack()
self.viewnumber = Button(master, text="OK", activebackground="red", command=get_int)
self.viewnumber.pack()
self.quit = Button(master, text="Quit", activebackground="blue", command=quit)
self.quit.pack()
def get_int():
intnum = ebox.get
print(intnum)
def quit():
root.quit()
root = Tk()
root.geometry("400x400")
my_gui = Window(root)
root.mainloop()
I am getting this error:
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\USERNAME\AppData\Local\Programs\Python\Python35-32\lib\tkint
er\__init__.py", line 1549, in __call__
return self.func(*args)
File "C:\Users\USERNAME\Desktop\pyth222184.py", line 20, in get_int
intnum = ebox.get
NameError: name 'ebox' is not defined
Thank you for your help.
Actually the problem is that ebox can't be accessed from the function. You need to use self.ebox, so that it actually references the instance variable ebox.
To see more about that, visit here.
Also, you are using self.ebox.get instead of self.ebox.get(). self.ebox.get on its own would return the raw function object of self.entry.get. To actually call the function and get the contents, use self.ebox.get().
But in order for that to work, self has to be defined. To do this, add self as the first argument in every method. When you call instance.method() Python will automatically pass instance in as self. Essentially, self lets you access the current instance.
That's right. The get_int in Class is a method, and you can not call that like a function. You'd have to call that like a method something like this
elf.viewnumber = Button(master, text="OK",bg="red",command=lambda:my_gui.get_int() )
self.viewnumber.pack()
This is an example of code that would work.

Display objects attributes in tkinter label

I would like a label that updates when I push a button. The label is a formated string that prints some attributes of an object.
This is what I tried. It displays properly but won't update.
from class_mnoply import *
from tkinter import *
Player1=Player("Hat")
message=str('''
____________________
{0}
Bank account: ${1}
Dice1: {2}
Dice2: {3}
____________________
'''.format(Player1.name, Player1.bank, Player1.dice1, Player1.dice2))
mainWin = Tk()
topFrame=Frame(mainWin)
topFrame.pack(side=TOP)
button1 Button(mainWin,text="ThrowDice",fg="red",command=Player1.rollDice())
button1.pack(side=BOTTOM)
plLabel = Label(mainWin, textvariable=message)
plLabel.pack()
mainWin.mainloop()
You have 1 typo in the following statement and 1 potential error:
button1 Button(mainWin,text="ThrowDice",fg="red",command=Player1.rollDice())
Can you guess what is the typo? If no, you are just missing the = sign.
On the other hand, you are assigning the return value of Player1.rollDice() to command, but this is not what you want. What you want is just set the Player1.rollDice method as the command that is called when button1 is pressed. Here's the correct syntax (note the absence at the end of ()):
button1 = Button(mainWin,text="ThrowDice",fg="red",command=Player1.rollDice)
Then, where is message defined in the following statement:
plLabel = Label(mainWin, textvariable=message)
There's no need to use a StringVar object, but if you want, you have first to declare it:
message = StringVar()
and finally you can use it as textvariable for your label.
Assuming you don't know what lambda is, this is a working example of what you are trying to do (without using a StringVar variable):
from tkinter import *
def set_label_text():
plLabel.config(text='Hello World!')
mainWin = Tk()
topFrame=Frame(mainWin)
topFrame.pack(side=TOP)
button1 =Button(mainWin,text="ThrowDice",fg="red",
command=set_label_text) # note the absence of ()
button1.pack(side=BOTTOM)
plLabel = Label(mainWin)
plLabel.pack()
mainWin.mainloop()

Saving StringVars to text and problems with globals

This is my first project with tkinter and I'm having some problems with StringVars. I am trying to create a program where a user can input information and then it will save it to a txt document, the simplified code is.
def New_Condition():
def Global_Vars():
global Str_Name
Str_Name = StringVar()
global Str_Eff
Str_Eff = StringVar()
Global_Vars()
Gui = Tk()
def Save_Condition():
CND_InfoList = [StringVar.get(Str_Name),StringVar.get(Str_Eff)]
TXT_CND = open("C:\\Users\\Clark\\DnD\\Conditiontxt\\Conditions.txt","a")
TXT_CND.write("$".join(CND_Info_List) + "$\n")
TXT_CND.close()
Name = Entry(textvariable = Str_Name).pack()
Eff = Entry(textvariable = Str_Eff).pack()
Save = Button(text = "Save",command = Save_Condition).pack()
Gui.mainloop()
The program will save to a textfile but it only saves blanks and not the user input in the entry boxes.
I'm not sure if the error is with the way I'm declaring global variables or if it is with the StringVar itself. Thanks for any help.
The crux of the problem is that you're creating the instances of StringVar before creating the instance of Tk. You must always initialize tkinter before creating widgets or StringVars. When I move Gui = Tk() before creating the StringVar, your code works.
According to some comments to your original question, you say you are creating multiple instances of Tk. This can also cause unexpected problems. If you need to create additional windows you need to be creating instances of Toplevel rather than Tk. Your program should always have exactly once instance of Tk, and it needs to be created before any other Tkinter objects.

Resources