I am creating a multiple choice quiz in Tkinter and am using radiobuttons and checkbuttons. For this question I am using radiobuttons. How do I get the value from the radio buttons in order to compare them in the if statement 'Calculate Score'? The computer returns with: 'calculate_score_1() takes exactly 1 argument (0 given)'
Also, how do I pass a variable between classes? I have ten classes for ten questions but want all of them to be able to access the variable 'Score' when adding 1 to the score when the user gets the answer correct.
class Question_1_Window(tk.Toplevel):
'''A simple instruction window'''
def __init__(self, parent):
tk.Toplevel.__init__(self, parent)
self.text = tk.Label(self, width=75, height=4, text = "1) Do you have the time to do at least twenty minutes of prefect duty each week?")
self.text.pack(side="top", fill="both", expand=True)
question_1_Var = IntVar() #creating a variable to be assigned to the radiobutton
Yes_1 = Radiobutton(self, text = "Yes", variable = question_1_Var, value=1, height=5, width = 20)
Yes_1.pack() #creating 'yes' option
#Here we are assigning values to each option which will be used in the validation
No_1 = Radiobutton(self, text = "No", variable = question_1_Var, value=2, height=5, width = 20)
No_1.pack() #creating 'no' option
def calculate_score_1(self):
Enter_1.config(state="disabled")
#self.question_1.config(state="disabled")
if (question_1_Var.get() == 1) and not (question_1_Var.get() == 2):
print("calculate score has worked") #test lines
#score = score + 1
else:
print("not worked") #testlines
Enter_1 = Button(self, text= "Enter", width=10, command = calculate_score_1)
Enter_1.pack()
calculate_score_1 is not a method of the instance, but is defined inside the __init__ method. Thus, that method should not have the self parameter. Remove that parameter, then it should work. If you need it (you seem not to) you can still use the self parameter of the outer __init__ method.
If you want to access the score from another class (or in fact from another method of the same class) you have to make it a member of the instance, by defining it as self.score = .... You can then access it like this: your_question_1_window_instance.score.
Finally, if you have "ten classes for ten questions" you should try to find some common ground for all those questions and create either a common super class or even one class that can be parametrized to fit all the questions. You just need the title, the type (select one/select many) and a list of answers, and which ones are correct. Everything else -- creating the check boxes, checking the answer, etc. -- should always be the same.
Related
My objective is to create three variables called 'Number of pipes','Anisotropy ratio', 'Filter depth (in cm)' which values are chosen by the user (through a User interface). I achieved to create the scrollbar of Tkinter and to enter values but these are not stored as variables. Could someone give me a hand solving that? I am new in Python and specially I am struggling with Tkinter. The code is developped in Python 3.7:
root = tk.Tk()
root.geometry('1000x1000')
#Description show in window
info=tk.Label(root,anchor='e')
info.pack()
#Parameters
parameters = ('Number of pipes','Anisotropy ratio', 'Filter depth (in cm)')
def ask_parameter(entry):
user_pipes = str (entry['Number of pipes'].get())
user_aniso = str (entry['Anisotropy ratio'].get()) #effective screen length = b
user_depth = str (entry['Filter depth (in cm)'].get())
print(user_pipes,user_aniso,user_depth)
#if parameters
# return True
# else:
# tkinter.messagebox.showwarning ('Only numbers', 'Try again')
# return True
#
def form(parameters):
entry = {}
for parameter in parameters:
print(parameter)
row = tk.Frame(root)
lab = tk.Label(row, width=15, text=parameter+": ", anchor='w')
ent = tk.Entry(row)
row.pack(side=tk.TOP,
fill=tk.X,
padx=2,
pady=2)
lab.pack(side=tk.LEFT)
ent.pack(side=tk.RIGHT, expand=tk.YES,fill=tk.X)
entry[parameter] = ent
return entry
if __name__ == '__main__':
ents = form(parameters)
save_button = tk.Button(root)
save_button.configure(text='Save', command=lambda: ask_parameter(ents))
save_button.pack()
root.mainloop()
Any problem is shown with the code but the parameters are not stored as variables with the entered values.
Thank you very much for your time.
Your code is storing the values inputted through the GUI to variables, your problem is probably that your variables aren't global, so you won't be able to use them outside of the function they are created in. you can either get around this with a return, or make them global by putting global varname above the definition of each variable as well as at the start of every function that will use it.
I am trying to create a program that allows the user to select any number of check boxes and hit a button to return a random result from those check boxes. Since I am basing my list off the roster of Smash bros ultimate, I am trying to avoid creating 70+ variables just to place check boxes. However, I am unable to figure out how to iterate this. The various values set for rows are just placeholders until I can figure this out. I would also like to have a reset button at the top that allows the user to automatically uncheck every box. This code is what I have so far. Any help would be greatly appreciated.
#!/usr/bin/python3
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
f = [] #check boxes
ft = open("Fighters.txt").readlines() #list of all the character names
fv=[0]*78 #list for tracking what boxes are checked
ff=[] #list to place final character strings
def reset():
for i in fv:
fv[i]=0
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for y in range (0,77):
f[y] = Checkbutton(window, text = ft[y], variable = fv[y])
f[y].grid(column=0, row=4+y)
def done():
for j in fv:
if fv[j] == 1:
ff.append(fv[j])
result = random.choice(ff)
r=Label(window, text=result)
d = Button(window, text="Done", command=done)
d.grid(column=0, row = 80)
window.mainloop()
Unfortunately I'm afraid you are going to have to create variables for each checkbox.
tkinter has special purpose Variable Classes for holding different types of values, and if you specify an instance of one as the variable= option when you create widgets like Checkbutton, it will automatically set or reset its value whenever the user changes it, so all your program has to do is check its current value by calling its get() method.
Here's an example of the modifications to your code needed to create them in a loop (and use them in the done() callback function):
import random
from tkinter import *
window = Tk()
#window name and header
window.title("Custom Random SSBU")
lbl = Label(window, text="Select the fighters you would like to include:")
lbl.grid(column=1, row=0)
with open("Fighters.txt") as fighters:
ft = fighters.read().splitlines() # List of all the character names.
fv = [BooleanVar(value=False) for _ in ft] # List to track which boxes are checked.
ff = [] # List to place final character strings.
def reset():
for var in fv:
var.set(False)
rst = Button(window, text="Reset", command=reset)
rst.grid(column=0, row=3)
for i, (name, var) in enumerate(zip(ft, fv)):
chk_btn = Checkbutton(window, text=name, variable=var)
chk_btn.grid(column=0, row=i+4, sticky=W)
def done():
global ff
ff = [name for name, var in zip(ft, fv) if var.get()] # List of checked names.
# Randomly select one of them.
choice.configure(text=random.choice(ff) if ff else "None")
d = Button(window, text="Done", command=done)
d.grid(column=0, row=len(ft)+4)
choice = Label(window, text="None")
choice.grid(column=1, row=3)
window.mainloop()
I wasn't sure where you wanted the Label containing the result to go, so I just put it to the right of the Reset button.
variable = fv[y]
This looks up the value of fv[y] - i.e, the integer 0 - at the time the Checkbutton is created, and uses that for the variable argument.
You need to use an instance of one of the value-tracking classes provided by TKinter, instead. In this case we want BooleanVar since we are tracking a boolean state. We can still create these in a list ahead of time:
text = open("Fighters.txt").readlines()
# Let's not hard-code the number of lines - we'll find it out automatically,
# and just make one for each line.
trackers = [BooleanVar() for line in text]
# And we'll iterate over those pair-wise to make the buttons:
buttons = [
Checkbutton(window, text = line, variable = tracker)
for line, tracker in zip(text, trackers)
]
(but we can not do, for example trackers = [BooleanVar()] * len(text), because that gives us the same tracker 78 times, and thus every checkbox will share that tracker; we need to track each separately.)
When you click the checkbox, TKinter will automatically update the internal state of the corresponding BooleanVar(), which we can check using its .get() method. Also, when we set up our options for random.choice, we want to choose the corresponding text for the button, not the tracker. We can do this with the zip trick again.
So we want something more like:
result_label = Label(window) # create it ahead of time
def done():
result_label.text = random.choice(
label
for label, tracker in zip(text, trackers)
if tracker.get()
)
I am trying to make a class in Python (using Tkinter) whom I can pass multiple Widgets to store them; of these widgets, the class should always display only one in a frame, and when I press a button, a drop-down menu should open from which I can choose which one of the widgets the frame should go on displaying.
My actual implementation of this idea is working pretty well, but only when I put only one of these frames into a window. As soon as I define more than one, they start to behave oddly...
One example:
I used the class and gave it three Tkinter labels as an input. One of these Labels displays the text "Label 1", the second the text "Label 2" and the third the text "Label 3".
I then .pack() the whole thing into my label and it works fine. This is how it looks:
image of the widget
When I click on the '+'-Button, it unwraps like this:
image of the widget with all Labels displayed
And when I click on the 'x'-Button, it goes back to its initial state. When I, however, click on the radio button next to 'Label 2', it goes back to its initial state, but now displaying 'Label 2' instead of 'Label 1':
widget now displaying label 2
So far, It's doing exactly what I want it to do: I can switch between multiple sub-widgets using one widget in which they stay contained.
However, the problems start as soon as I try to put multiple of these things into a single window. I tried to put one into the window containing "Label 1"-"Label 3", one containing "Label 4"-"Label 6" and one containing "Label 7"-"Label 9". The problem is that when I press the "+"-Button of one of them, all of them start to show their whole content (so suddenly, I have "Label 1"-"Label 9" unwrapped and not only "Label 1"-"Label 3"). As long as they are closed, they look somewhat like this: weird formatted stuff
And that's how it looks when I have clicked the "+"-Button: all contents wrapped out
I think the reason for all of them unwrapping at once is that for some reason, the buttons (the '+'- and 'x'-Button) call the specified function in ALL of the frames at once, not only in the one it's in. I tried to fix this using lambda whilst specifying the function the buttons should call, but it is still not working.
I am not a native english speaker, so sorry if my explanation seems a little weard, and also, I have all my python knowledge from learning-by-doing and the internet, so also sorry if I mess up the words for everything a little. Maybe my code helps explayning the problem a little better:
class MultiInput(Frame):
"""use .add(Widged)-Method to add widged!"""
subs = []
adders = []
index = 0
def __init__(self, parent):
Frame.__init__(self, parent)
self.indexVar = IntVar()
self.indexVar.set(0)
self.dropdown = Button(self, text="+", command=self.drop)
self.dropdown.grid(row=0, column=0)
self.dropup = Button(self, text="x", command=self.undrop)
def drop(self):
num = -1
self.dropdown.grid_forget()
self.dropup.grid(row=0, column=0)
for i in range(0, len(self.subs)):
num += 1
if i != self.index:
self.subs[i].grid(column=1, row=num+1)
self.adders[i].grid(column=0, row=num+1)
def undrop(self):
for i in range(0, len(self.subs)):
self.subs[i].grid_forget()
self.adders[i].grid_forget()
self.subs[self.index].grid(row=0, column=1)
self.dropup.grid_forget()
self.dropdown.grid(row=0, column=0)
def add(self, Widged):
if len(self.subs) == 0:
self.subs.append(Widged)
self.adders.append(Radiobutton(self,
variable = self.indexVar,
command = self.change,
value = 0))
self.change()
else:
self.subs.append(Widged)
self.adders.append(Radiobutton(self,
variable = self.indexVar,
command = self.change,
value = len(self.subs) - 1))
def change(self):
self.index = self.indexVar.get()
self.undrop()
and here is how I called it in my first example, where it is perfectly working:
root = Tk()
multi = MultiInput(root)
multi.add(Label(multi, text="Label 1"))
multi.add(Label(multi, text="Label 2"))
multi.add(Label(multi, text="Label 3"))
multi.pack()
As soon as I ad this to the end of the code (to add mere than one MultiInput), it starts doind the stuff from my second example:
multi2 = MultiInput(root)
multi2.add(Label(multi2, text="Label 4"))
multi2.add(Label(multi2, text="Label 5"))
multi2.add(Label(multi2, text="Label 6"))
multi2.pack()
multi3 = MultiInput(root)
multi3.add(Label(multi3, text="Label 7"))
multi3.add(Label(multi3, text="Label 8"))
multi3.add(Label(multi3, text="Label 9"))
multi3.pack()
So here comes my question: How can I get the buttons to call the function they should call so that they only use it on the Object they are supposed to use it on? Or do I have an entirely wrong idea of using classes in Python?
I tried making a simple integer sign calculator using tkinter. It has a class with two different functions. The second function is supposed to be initiated when the user presses the "Enter" button. When I run the code the window comes up just as it is supposed to. But when I type and hit "Enter" the second function fails to run and does not update the label. I want for it to update as either "This number is positive.", "This number is 0.", or "This number is negative." Instead it remains blank.
I doubt it is relevant, but I made this program in PyCharm Community Edition 5.0.4, and I am using Python 3.5 (32-bit).
import tkinter
class IntegerSign:
def __init__(self):
self.window = tkinter.Tk()
self.window.title("Integer Sign Calculator")
self.window.geometry("300x150")
self.number_frame = tkinter.Frame(self.window)
self.solution_frame = tkinter.Frame(self.window)
self.button_frame = tkinter.Frame(self.window)
self.number_label = tkinter.Label(self.number_frame, text="Enter an integer:")
self.number_entry = tkinter.Entry(self.number_frame, width=10)
self.number_label.pack(side='left')
self.number_entry.pack(side='left')
self.statement = tkinter.StringVar()
self.solution_label = tkinter.Label(self.solution_frame, textvariable=self.statement)
self.statement = tkinter.Label(self.solution_frame, textvariable=self.statement)
self.solution_label.pack(side='left')
self.calc_button = tkinter.Button(self.button_frame, text='Enter', command=self.calc_answer)
self.quit_button = tkinter.Button(self.button_frame, text='Quit', command=self.window.destroy)
self.calc_button.pack(side='left')
self.quit_button.pack(side='left')
self.number_frame.pack()
self.solution_frame.pack()
self.button_frame.pack()
tkinter.mainloop()
def calc_answer(self):
self.number = int(self.number_entry.get())
self.statement = tkinter.StringVar()
if self.number > 0:
self.statement = "This number is positive."
elif self.number == 0:
self.statement = "This number is 0."
else:
self.statement = "This number is negative."
IntegerSign()
The first problem: in your constructor you initialize a variable named self.statement to a StringVar and then initialize again to a Label. After that second initialization, you have no way of accessing the first object. You need to use two different names.
The second problem: in your event handler, calc_answer, you create a new object named self.statement, but instead you need to set a new value into the old one (see docs). Here is a modified version of your program that works as intended:
import tkinter
class IntegerSign:
def __init__(self):
self.window = tkinter.Tk()
self.window.title("Integer Sign Calculator")
self.window.geometry("300x150")
self.number_frame = tkinter.Frame(self.window)
self.solution_frame = tkinter.Frame(self.window)
self.button_frame = tkinter.Frame(self.window)
self.number_label = tkinter.Label(self.number_frame, text="Enter an integer:")
self.number_entry = tkinter.Entry(self.number_frame, width=10)
self.number_label.pack(side='left')
self.number_entry.pack(side='left')
self.solution_string = tkinter.StringVar()
self.solution_label = tkinter.Label(self.solution_frame, textvariable=self.solution_string)
self.statement = tkinter.Label(self.solution_frame, textvariable=self.solution_string)
self.solution_label.pack(side='left')
self.calc_button = tkinter.Button(self.button_frame, text='Enter', command=self.calc_answer)
self.quit_button = tkinter.Button(self.button_frame, text='Quit', command=self.window.destroy)
self.calc_button.pack(side='left')
self.quit_button.pack(side='left')
self.number_frame.pack()
self.solution_frame.pack()
self.button_frame.pack()
tkinter.mainloop()
def calc_answer(self):
self.number = int(self.number_entry.get())
if self.number > 0:
self.solution_string.set("This number is positive.")
elif self.number == 0:
self.solution_string.set("This number is 0.")
else:
self.solution_string.set("This number is negative.")
IntegerSign()
This code works but contains a bad practice that I recommend you fix. The function tkinter.mainloop() is essentially an infinite loop, and you have placed it inside the constructor. Thus the constructor won't return the way a constructor is normally supposed to. Take that statement out of the __init__ function and put it at the end, after the call to IntegerSign, and make this a pattern to be used in the future.
To set the value of a StringVar you need to use the set method.
Right now all you did was re-assign the variable. You can also set a stringvar's (default) value by giving it a value when you first initialize it. e.g. - var = tk.StringVar(value="some value")
Edit: Didn't see that you also set self.statement to be the label widget... This would work if you used the method all the way at the bottom of this answer, and disregarded (optionally) stringvar's entirely. But, when you do this you can think of it as sticky notes. You stuck a sticky note that says "this variable holds this value", then you re-assigned the variable put another sticky note over the previous one that says "it now holds this value" as a really loose visual analogy.
>>> import tkinter as tk
>>> root = tk.Tk()
>>> statement = tk.StringVar()
>>> type(statement)
>>> <class 'tkinter.StringVar'>
>>> statement = "This number is positive"
>>> type(statement)
>>> <class 'str'>
>>> statement = tk.StringVar()
>>> statement.set("This number is positive")
>>> statement.get()
'This number is positive'
>>> type(statement)
>>> <class 'tkinter.StringVar'>
Alternatively you could just change the labels text by doing label_widget['text'] = 'new_text'
I am a newbie at programming and my program is not stellar but hopefully it's ok because I have only been using it for a couple days now.
I am having trouble in my class "Recipie". In this class I am having trouble saving the text in my Entry widget. I know to use the .get() option but when I try to print it, it doesn't (whether it is within that defined method or not). So that is my main concern. I want it to save the text entered as a string when I press the button: b.
My other minor question is, how can I move the label. When I have tried I have used the height and width options, but that just expands the label. I want to move the text to create a title above my Entry boxes. Is label the right widget to use or would it be easier to use a message box widget? So it would look like, for example (but like 8 pixels down and 20 to the right):
ingredients
textbox
button labeled as: add an ingredient
And I am not sure the option .pack(side="...") or .place(anchor="...") are the right options to use for my buttons or entry boxes or labels.
And if you could add comments to your code explaining what you did, that would be so helpful.
import Tkinter
class Cookbook(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
self.title("Cookbook")
self.geometry("500x500+0+0")
self.button = []
for r in range(1):
for c in range(1):
b = Button(self).grid(row=r,column=c)
self.button.append(b)
class Button(Tkinter.Button):
def __init__(self,parent):
b = Tkinter.Button.__init__(self, parent, text="Add A New Recipie", height=8, width=15, command=self.make_window)
def make_window(self):
popwindow = Recipie()
popwindow.name()
popwindow.ingredients()
popwindow.addingredient()
popwindow.savebutton()
popwindow.save()
class Recipie(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
self.title("New Recipie")
self.geometry("500x500")
def name(self):
name = Tkinter.Label(self, text="Title:")
name.pack() #used to be name.place(anchor="nw")
self.insert_name = Tkinter.Entry(self) #edited with the answer below, used to be insert_name = Tkinter.Entry(self)
self.insert_name.pack() #edited with the answer from below, used to be insert_name.pack()
self.insert_name.focus_set() #edited with the answer from below, used to be insert_name.focus_set()
def ingredients(self):
self.e = Tkinter.Entry(self) #edited with the answer from below, used to be e.get()
self.e.pack() #edited with the answer from below, used to be e.pack()
self.e.focus_set() #edited with the answer from below, used to be e.focus_set()
def addingredient(self):
but = Tkinter.Button(self, text="Add Ingredients", width=15, command=self.ingredients)
but.pack(side="bottom")
def procedure(self):
txt = Tkinter.Label(self, text="List the Steps:")
txt.place(anchor="n")
self.p = Tkinter.Entry(self) #edited with the answer from below, used to be p = Tkinter.Entry(self)
self.p.place(anchor="nw") #edited with the answer from below, used to be p.place(anchor="nw")
self.p.focus_set() #edited with the answer from below, used to be p.focus_set
def savebutton(self):
print self.insert_name.get() #edited with the answer from below
print self.e.get() #edited with the answer from below
print self.p.get() #edited with the answer from below
def save(self):
b = Tkinter.Button(self, text="Save Recipie", width=15,command=self.savebutton)
b.pack()
top = Cookbook()
top.mainloop()
Part 1...
You are currently defining your entry widget as a local variable inside the ingredients method (i.e. these are variables that only exist inside the method). To maintain a reference to the entry widget you are creating, you can assign it as an instance attribute to your Recipie object.
i.e.
e = Tkinter.Entry(self)
e.pack()
e.focus_set()
becomes
self.e = Tkinter.Entry(self)
self.e.pack()
self.e.focus_set()
and
print e.get()
becomes
print self.e.get()
Some required reading before you continue with Python:
Python classes
Explaining 'self' in Python
Part 2...
So in answer to the second part of the question on how to position your labels. It just looks like you need to alter the way in which you are packing widgets. An example of how to pack entry widgets is a clean way (emulating the functionality of your example) would be:
import Tkinter as tk
class IngredientAdder(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry("500x500+0+0")
self.init_gui()
# function to add new ingredients
def add_ingredient_entry(self):
entry = tk.Entry(self)
entry.pack(side=tk.TOP)
self.ingredient_entries.append(entry)
# get contents of all entry boxes
def save_recipe(self):
for ingredient in self.ingredient_entries:
print ingredient.get()
print "[Recipe saved]"
# build initial widgets
def init_gui(self):
# this is a list of ingredients entry boxes
self.ingredient_entries = []
# put a label at the top of the window and anchor it there
tk.Label(self,text="Ingredients").pack(anchor=tk.N,side=tk.TOP)
# Put these two buttons at the bottom of the window and anchor them there
tk.Button(self,text="Save recipe",command=self.save_recipe).pack(anchor=tk.S,side=tk.BOTTOM)
tk.Button(self,text="Add ingredient",command=self.add_ingredient_entry).pack(anchor=tk.S,side=tk.BOTTOM)
# new ingredients will be added between the label and the buttons
self.add_ingredient_entry()
cookbook = IngredientAdder()
cookbook.mainloop()