Tkinter Python 3 - Labels - python

I'm making a revision quiz program using Tkinter. The program will automatically get questions from an array and then output the questions using a Label. When the submit button is pressed, the program will check the answer and then should update the Label to the next question in the array but it doesn't, there is no error message. The score is updated but the Label just doesn't update.
def entryQuestion():
entryOpen = open('./files/entry.csv','r')
entryFile = csv.reader(entryOpen, delimiter = ',')
global entryQuestionArray
entryQuestionArray = []
for topic, question, answer in entryFile:
for i in range(0,1):
entryQuestionArray.append(question)
entryQuestionArray = random.sample(entryQuestionArray, len(entryQuestionArray))
arrayLength = len(entryQuestionArray)
for x in entryQuestionArray:
global qOut
qOut = x
global entryQuestion
entryQuestion = Label(entryQ, text = x)
entryQuestion.grid(row = 1, column = 0, columnspan = 3)
global answerEntry
answerEntry = StringVar()
global answerEntryBox
answerEntryBox = Entry(entryQ, textvariable = answerEntry)
answerEntryBox.grid(row = 2, column = 0, columnspan = 3)
submitEntryAnswer = Button(entryQ, text = 'Submit Answer', command = entryQuestionCheck)
submitEntryAnswer.grid(row = 3, column = 2)
def entryQuestionCheck():
entryOpen = open('./files/entry.csv','r')
entryFile = csv.reader(entryOpen, delimiter = ',')
tempScore = score
for topic, question, answer in entryFile:
if qOut == question:
if answerEntry.get() == answer:
tempScore = tempScore + 1
else:
tempScore = tempScore + 0
return
Can anyone help?

Each Widget only needs 1 call, on the top or in a main class, you need to call him only once:
class MainPage(#values):
self.label1 = tk.Label(self, #values)
and in the function, if you need to change your values, for example the text, you only need to pass self values to function, and change him, like this:
def changeValues(self):
self.label["text"] = "the text that u want to put in"
or:
def changeValues(label):
label["text"] = "the text that u want to put in"
and in the main class, or in the scope, you need to call the function with the values, if is in the scope (not in a class) you need to pass the widget in the values:
class MainPage(#values):
self.label1 = tk.Label(self, #values)
changeValues(self)
or:
label1 = tk.Label(self, #values)
changeValues(label1)

Related

Python Tkinker, checkbox append string value to list?

The following code allows me to add/remove France/Italy from a list when clicked on a tkinter checkbox. Rather than passing 0 or 1 to the Add_France function, is it possible to pass through a string "France"? Thanks
My_list = []
root = Tk()
Country_Variable1 = tkinter.IntVar()
Country_Variable2 = tkinter.IntVar()
def Add_France():
if Country_Variable1.get() == 1:
My_list.append("France")
if Country_Variable1.get() == 0:
My_list.remove("France")
print (My_list)
def Add_Italy():
if Country_Variable2.get() == 1:
My_list.append("Italy")
if Country_Variable2.get() == 0:
My_list.remove("Italy")
print (My_list)
check1 = Checkbutton(root, text='France',variable=Country_Variable1, onvalue=1, offvalue=0, command=Add_France)
check2 = Checkbutton(root, text='Italy',variable=Country_Variable2, onvalue=1, offvalue=0, command=Add_Italy)
check1.pack()
check2.pack()
root.mainloop()
You have control over onvalue and offvalue with Checkbutton, so you can play around with the value that it returns in each state. In the below example, you can see how to make checkbutton based on inputs inside the list(assuming it is a list of strings of characters always):
from tkinter import *
root = Tk()
def process(var,text):
try:
val = int(var.get()) # If not selected it will give 0 as int, which will trigger `else` block
except ValueError:
val = var.get()
if val: # if val is not empty, ie, if val is any selected value
slct_ctry_lst.append(val)
else: # if val is 0
slct_ctry_lst.remove(text) # Remove the corresponding text from the list
print(slct_ctry_lst)
slct_ctry_lst = []
countries = ['France','Italy','China','Russia','India']
for idx,i in enumerate(countries):
var = StringVar(value=" ")
Checkbutton(root,text=i,variable=var,command=lambda i=i,var=var: process(var,i),onvalue=i).grid(row=0,column=idx)
root.mainloop()
Thought it would be easier, but maybe not suitable, with ttk.Combobox:
def process(e):
val = e.widget.get()
if val in slct_ctry_lst: # If val present inside the list
slct_ctry_lst.remove(val) # Remove it
else:
slct_ctry_lst.append(val)
print(slct_ctry_lst)
slct_ctry_lst = []
countries = ['France','Italy','China','Russia','India']
cbox = ttk.Combobox(root,values=countries,state='readonly')
cbox.pack(padx=10,pady=10)
cbox.set('Select a country')
cbox.bind('<<ComboboxSelected>>',process)
If the objective here is to make the code reusable for multiple countries, here is initial idea, this can be improved further.
Basically, I am using lambda expression here to the callback command function and passing the country name.
Created a separate function to create a checkbox which is generic for all the countries.
Now to add a new country to the list all you need to do is add an element to the available_country_list
You can make available_country_list global or can pass that as an argument to the list_countries function.
import tkinter
from tkinter import Checkbutton
selected_country_list = []
def select_country(country_name, variable):
global selected_country_list
print(variable)
if variable.get() == 1:
selected_country_list.append(country_name)
elif variable.get() == 0:
selected_country_list.remove(country_name)
print (selected_country_list)
def create_checkbox(parent, country_name):
country_var = tkinter.IntVar()
country_check = Checkbutton(parent, text=country_name, variable=country_var,
onvalue=1, offvalue=0,
command=lambda: select_country(country_name, country_var))
country_check.pack()
def list_countries(root):
available_country_list = ["France", "Italy"]
for country in available_country_list:
create_checkbox(root, country)
def load(root):
# Create list of country checkboxes
list_countries(root)
def start():
root = tkinter.Tk()
root.after(100, load, root) # Enable lazy loading of your components
root.mainloop()
if __name__ == '__main__':
start()

Using list in parameter to create radiobuttons in GUI

I used a list as a parameter for the GUI window, and each individual item in the list is supposed to be created as a radio button in the GUI window. Currently, I used a for loop to generate different values for each radio button and another for loop to generate different columns. However, I am only able to create a radio button for the last item in the list.
This is what I currently have:
from tkinter import *
from tkinter.scrolledtext import *
class ExpenditureGUI:
def __init__(self, listOfCategories):
self._listOfCategories = listOfCategories
self._tk = Tk()
self._tk.title('Expenditure Tracker')
self._tk.geometry('500x200')
self._tk.resizable(False,False)
self.initWidgits()
self._tk.mainloop()
#property
def listOfCategories(self):
return self._listOfCategories
#listOfCategories.setter
def listOfCategories(self, newValue):
self._listOfCategories = newValue
def initWidgits(self):
#flow layout
topF = Frame(self._tk)
self._lblAmount = Label(topF, text = 'Amount: ')
self._txtAmount = Entry(topF, width=30)
rbtnF = Frame(topF)
self._rbtnVar = IntVar()
self._rbtnVar.set(0)
self._lblExpenditure = Label(topF, text = 'Type of Expenditure: ')
n = 0
for type in self._listOfCategories:
self._rbtntype = Radiobutton(rbtnF, text = f'{type}', value = n, variable = self._rbtnVar)
self._lblExpenditure.grid(row = 0, column = 0, sticky = E)
n = 0
for type in self._listOfCategories:
self._rbtntype.grid(row = 0, column = n)
I’m not an expert in the GUI library, but this looks pretty suspect:
for type in self._listOfCategories:
self._rbtntype = Radiobutton(rbtnF, text = f'{type}', value = n, variable = self._rbtnVar)
^^^^^^^^^^^^^^^^
After this loop ends, the self._rbtntype variable will contain text referring to only the last element in the list of categories (i.e. the loop variable type).
You may want to construct a new Radiobutton in the final loop in your code sample. Maybe something like this would work. It probably needs to update n in each loop iteration.
for type in self._listOfCategories:
rbtntype = Radiobutton(rbtnF, text = f'{type}', value = n, variable = self._rbtnVar)
rbtntype.grid(row = 0, column = n)

Python Tkinter - get selection on Radiobutton

I need to retrieve the value of Radiobutton clicked and then use this value .
What is the way to retrieve the value of a Radiobutton clicked ?
the code to setup the Radiobutton is:
radio_uno = Radiobutton(Main,text='Config1', value=1,variable = 1)
radio_uno.pack(anchor=W,side=TOP,padx=3,pady=3)
radio_due = Radiobutton(Main,text='Config2', value=2,variable =1)
radio_due.pack(anchor=W,side=TOP,padx=3,pady=3)
radio_tre = Radiobutton(Main,text='Config3', value=3,variable = 1)
radio_tre.pack(anchor=W,side=TOP,padx=3,pady=3)
This is one solution:
Create a tk.IntVar() to track which button was pressed. I'm assuming you did a from tkinter import *.
radio_var = IntVar()
You'll need to change the way you declared your buttons:
radio_uno = Radiobutton(Main,text='Config1', value=1,variable = radio_var)
radio_due = Radiobutton(Main,text='Config2', value=2,variable = radio_var)
radio_tre = Radiobutton(Main,text='Config3', value=3,variable = radio_var)
Then use the get() method to view the value of radio_var:
which_button_is_selected = radio_var.get()
Then you can make an enum or just three if clauses that'll do stuff depending on which button is chosen:
if(which_button_is_selected == 1):
#button1 code
elif(which_button_is_selected == 2):
#button2 code
else(which_button_is_selected == 3):
#button3 code

return a value from class python

Edit
So I asked this question earlier and I received some good insight, but I feel like my question wasn't really answered. I'm building a small program to practice with python and making GUI's and I'm having a small problem with a button command.
#This is the temperature menu:
def temperM(self, *args):
self.clearscreen(self.frame)
self.frame2 = Frame(self.root)
self.frame2.grid(column = 0, row = 0)
self.firstunit = StringVar()
self.secondunit = StringVar()
self.entryspace = IntVar()
self.displayspace = IntVar()
#Create back button
#This is the part that needs to be fixed
self.back = Button(self.frame2, text = "< Back",
command = lambda: self.redo(self.frame2))
self.back.grid(column = 1, row = 3)
self.label = Label(self.frame2, text = "Convert from: ")
self.label.grid(column = 1, row = 1, padx = 4)
#Create the check boxes
self.celsius = Checkbutton(self.frame2, text = "Celsius",
variable = self.firstunit, onvalue = 'celsius')
self.celsius.grid(column = 2, row = 1)
self.fahrenheit = Checkbutton(self.frame2, text = "Fahrenheit",
variable = self.secondunit, onvalue = 'fahrenheit')
self.fahrenheit.grid(column = 3, row = 2)
#Create entry space to recieve text
#This is where the problem starts.
self.entry = Entry(self.frame2, width = 7,
textvariable = self.entryspace)
self.entry.grid(column = 3, row = 3)
self.compute = Calculate(self.entryspace.get())
self.button = Button(self.frame2, text = "Calculate",
command = lambda: self.displayspace.set(self.compute.celtoFah()))
self.button.grid(column = 3, row = 4)
self.display = Label(self.frame2, textvariable = self.displayspace)
self.display.grid(column = 2, row = 2)
I have this function inside of a class Menu with def__init__(self, root) which creates all the different menu options.
class Calculate:
def __init__(self, number):
self.number = number
def celtoFah(self):
try:
self.temp = Temperature()
self.number = float(self.number)
return self.temp.C2F(self.number)
except ValueError:
pass
And I have this class which holds all the different calculations that will be used in the code.
What I'm having trouble with is with my button command command = lambda: self.displayspace.set(self.compute.celtoFah()). When I run the code and press 'Calculate' which runs the command, self.displayspace.set(), it doesn't set self.displayspace to what I believe the returned value should be. Instead it returns and sets self.displayspace to what self.entryspace.get() is originally without modifications which is 32 and 0 respectively, which causes me to believe that the line self.compute = Calulate(self.entryspace.get()) is not updating when I put in a new value so self.entryspace is not getting a new value but its retaining the same initial value established by IntVar(). Am I doing something wrong in my code for self.entryspace not to be updating with a new value? At first I had it as a StringVar() which would convert to a float in celtoFah but I was throwing ValueError because it was receiving an empty string even after a user inputs a value. I really want to keep all calculations in a separate class since I will be having 20+ in the final version, but should I move these commands into class Menu or is there another I can do this by having a separate class? If you need to see my full code here is a link to it on github: https://github.com/Flameancer/Unit-Conversion-Program-in-Python
In general, you don't pass values between classes, but between instances of those classes. At any given time, you may have 0, 1, or 30 different Foo instances; how does a Bar instance even know which one you want?
The first question is, who's calling that something method on that Bar? Whoever it is, he has the value. Maybe it should be the Foo instance that's doing the calling.
For that to happen, the foo instance has to know about a bar instance. Maybe you want to create one in the constructor:
class Foo:
def __init__(self, argument):
# ...
self.bar = Bar(42)
# ...
… and now you can use it the same way as any other member:
def function(self, *args):
# ...
randomness = self.bar.something()
self.displayfield.set(randomness)
# ...
Or maybe you're already constructing one somewhere, and you just want to pass it to the Foo instance as a constructor:
class Foo:
def __init__(self, argument, bar):
# ...
self.bar = bar
# ...
bar = Bar(42)
foo = Foo(23, bar)
Or maybe you want to construct a new one locally each time you call the function method. Or maybe you want a global Bar instance shared by everyone, no matter how many Foo instances you have. Or…
As you can see, there are many different possible relationships between a Foo instance and a Bar instance, and which one is appropriate depends entirely on what Foo and Bar actually represent. This is the code idea behind object modeling: there are things of some kind that your objects represent, and the relationships between those things are reflected in the relationships between those objects.

Python Tkinter label widget doesn't update

Here's a piece of simplified code which doesn't work as i want it to:
def get_tl(self,x):
self.var_tl = IntVar()
if x == "Random (max = 6)":
self.var_tl.set(randint(1,6))
else:
ask_tl = Toplevel()
def destroy_t_set_tl():
self.var_tl.set(entry_tl_t.get())
ask_tl.destroy()
label_tl_t = Label(ask_tl, text="length:").pack(side=LEFT)
entry_tl_t = Entry(ask_tl, width=25)
entry_tl_t.pack(side=LEFT)
button_enter_tl_t = Button(ask_tl, text="Enter", command=destroy_t_set_tl).pack(side=LEFT)
self.label_tl = Label(self, text="length:").grid(row=1,column=0)
# This only shows the right number when "Random (max = 6)". When "Manual" it shows 0
self.show_tl = Label(self, text=self.var_tl.get()).grid(row=1,column=1)
def get_values(self):
# This always shows the right number.
self.total_label = Label(self, text=self.var_tl.get()).grid(row=4,column=0)
The function get_tl is called by an OptionMenu widget which gives x the values: "Manual" or "Random (max = 6)".
When this function is called I want it to choose a random number or open a Toplevel window which ask the user a number through an Entry. After the random number is chosen or the user has given a number. The number needs to be displayed as a label so the user can see if the number is correct.
The label only show the right number when "Random (max = 6)". When "Manual" it shows 0
After a button is pressed the function get_values is called. This however does give the right number regardless if it is manual or random.
I'm probably making a simple mistake here. But I fail to see it.
In this part:
def get_tl(self,x):
self.var_tl = IntVar()
You're recreating the variable over and over again, so it holds the default value of 0, as explained in the documentation:
VALUE is an optional value (defaults to 0)
Then you set the variable only if x == "Random (max = 6)", so in all other cases it will remain at its default.
Possibly you want to remove this line:
self.var_tl = IntVar()
You should have it only in the constructor of your class. Then all your methods will share the same instance pointed by self.var_tl.
This seems to be the answer to my own question:
def get_tl(self,x):
def tl():
self.label_tl = Label(self, text="length:").grid(row=1,column=0)
self.show_tl = Label(self, text=self.var_tl.get()).grid(row=1,column=1)
if x == "Random (max = 6)":
self.var_tl.set(randint(1,6))
tl()
else:
ask_tl = Toplevel()
def destroy_t_set_tl():
self.var_tl.set(entry_tl_t.get())
ask_tl.destroy()
tl()
label_tl_t = Label(ask_tl, text="length:").pack(side=LEFT)
entry_tl_t = Entry(ask_tl, width=25)
entry_tl_t.pack(side=LEFT)
button_enter_tl_t = Button(ask_tl, text="Enter", command=destroy_t_set_tl).pack(side=LEFT)
def get_values(self):
self.total_label = Label(self, text=self.var_tl.get()).grid(row=4,column=0)
Now both the option "Manual" and "Random" will call the function tl() which will show the number so the user can check it.
I also moved the self.var_tl = IntVar() to the constructor of the class. It might not be the optimal solution but for me it works.

Categories

Resources