Saving StringVars to text and problems with globals - python-3.x

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.

Related

How to correctly change the number of columns and their names after creating the treeview (tkinter)

I am trying to create my own class treeview
I ran into the fact that after trying to change the number of columns, it persistently creates one more.
I can't figure out why this is happening and how to change it.
Help.
import tkinter
from tkinter import ttk
class TableTreeView (ttk.Treeview):
def __init__(self, root):
super().__init__(root)
def create_columns(self, columns_count):
columns_names = []
for i in range(columns_count):
columns_names.append('№'+str(i+1))
columns_names = tuple(columns_names)
self['columns'] = columns_names
for i in range(columns_count):
self.heading(columns_names[i], text=columns_names[i])
root = tkinter.Tk()
table = TableTreeView(root)
table.create_columns(2)
table.pack()
root.mainloop()
I have also tried this approach:
self.config({"columns": columns_names})
You should not use append command to change. You can change existing object by reaching it self or other pointed variables and set it's content like below
self['show'] = 'headings'

How do I dynamically set the name of a tkinter label

I have this code in my project:
def savespack():
savelabelcount = 0
for i in range(saveamount):
savelabelcount = savelabelcount + 1
savebuttontext = "Save " + str(savelabelcount)
Button(scrollable_frame, text=savebuttontext, width="53", height="5").pack()
The user can create multiple saves in the program, and when the project is reopened, the correct amount of save files will be shown as Tkinter buttons. I've created the buttons by reading a number from an external file. I need to make the buttons have different variable names to call the correct save file and load the correct properties when a button is pressed (for example "Save 2" needs to load save2.txt's properties)
Simply put I need to give Button(scrollable_frame, text=savebuttontext, width="53", height="5").pack() a new variable name for each iteration of the loop, I dont know how to do this. I read something about dictionaries and lists but it was very confusing and I dont know if it can be applied in this situation.
The variable names should be something like: savebutton1, savebutton2, savebutton3 etc.
Any way I could do this would be appreciated, thanks!
The example below shows how to save each button widget inside a list rather than as separate variables.
It also will call the saveFile function when the button is pressed which currently just prints to the screen the save file number but this function could be replaced with the code that actually saves each file. With this method, it might not even be necessary to keep the buttons in a list.
I'd normally do this using classes rather than globals but this will work for your purposes
import tkinter as tk
saveamount = 5
save_buttons = []
def saveFile(fileNumber):
print("Saving File ", fileNumber)
def savespack():
global save_buttons
savelabelcount = 0
for i in range(saveamount):
savelabelcount = savelabelcount + 1
savebuttontext = "Save " + str(savelabelcount)
currentButton = tk.Button(scrollable_frame, text=savebuttontext, width="53", height="5")
currentButton['command'] = lambda x=savelabelcount: saveFile(x)
currentButton.pack()
save_buttons.append(currentButton)
root = tk.Tk()
scrollable_frame = tk.Frame(root)
scrollable_frame.pack()
savespack()
root.mainloop()
You could use a dictionary rather than a list but given your use case, I'm not sure there is any benefit. Just use save_buttons[2] to access the third button.

Gather input from multiple tkinter checkboxes created by a for loop

I made an application with tkinter which creates a list of checkboxes for some data. The checkboxes are created dynamically depending on the size of the dataset. I want to know of a way to get the input of each specific checkbox.
Here is my code, which you should be able to run.
from tkinter import *
root = Tk()
height = 21
width = 5
for i in range(1, height):
placeholder_check_gen = Checkbutton(root)
placeholder_check_gen.grid(row=i, column=3, sticky="nsew", pady=1, padx=1)
for i in range(1, height):
placeholder_scope = Checkbutton(root)
placeholder_scope.grid(row=i, column=4, sticky="nsew", pady=1, padx=1)
root.mainloop()
I looked over other answers and some people got away by defining a variable inside the checkbox settings "variable=x" and then calling that variable with a "show():" function that would have "variable.get()" inside. If anyone could please point me in the right direction or how I could proceed here. Thank you and much appreciated.
Normally you need to create an instance of IntVar or StringVar for each checkbutton. You can store those in a list or dictionary and then retrieve the values in the usual way. If you don't create these variables, they will be automatically created for you. In that case you need to save a reference to each checkbutton.
Here's one way to save a reference:
self.general_checkbuttons = {}
for i in range(1, self.height):
cb = Checkbutton(self.new_window)
cb.grid(row=i, column=3, sticky="nsew", pady=1, padx=1)
self.general_checkbuttons[i] = cb
Then, you can iterate over the same range to get the values out. We do that by first asking the widget for the name of its associated variable, and then using tkinter's getvar method to get the value of that variable.
for i in range(1, self.height):
cb = self.general_checkbuttons[i]
varname = cb.cget("variable")
value = self.root.getvar(varname)
print(f"{i}: {value}")

what is the difference between a variable and StringVar() of tkinter

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.

StringVar.get() returns a blank value

I've asked this before - but I think I phrased it incorrectly.
What I essentially want is to have a user fill out a tkinter entry box, and have the value saved - somewhere.
I the want to click on a button, and have the user's entered text to manipulate.
I want essentially what is below - although that doesn't work at all - as I can't enter a StringVar there, apparently. I'm really at my wits end here :(
How can I rewrite this, so it will work?
class Driver():
firstname = StringVar()
def __init__(self):
root = Tk()
root.wm_title("Driver")
firstname_label = ttk.Label(root, text="First Name *").grid(row=0, column=0)
firstname_field = ttk.Entry(root, textvariable=self.firstname).grid(row=0, column=1)
ttk.Button(root, text="Send", command=self.submit).grid()
root.mainloop()
def submit(self):
print(self.firstname.get())
I've having lots, and lots of trouble with this. It seems to printing blank values and references to the variable - rather than the value inside it
You cannot use StringVar in this way -- you can't create a StringVar until after you've created the root window. Because you are creating the root window inside the constructor the code will throw an error.
The solution is to move the creation of the StringVar inside your constructor:
class Driver():
def __init__(self):
root = Tk()
root.wm_title("Driver")
self.firstname = StringVar()
firstname_label = ttk.Label(root, text="First Name *").grid(row=0, column=0)
Note that the way you've written the code, firstname_label and firstname_field will always be None, because that is what grid returns. It's always best to separate widget creation from layout.
Also, you don't really need the StringVar under most circumstances (assuming you correctly store a reference to the widget). Just omit it, and when you want the value of the entry widget you can just get it straight from the entry widget:
...
self.firstname_field = Entry(...)
...
print(self.firstname_field.get())
The use of a StringVar is only necessary if you want to share the value between widgets, or you want to put a trace on the variable.

Resources