Text updation from a function in a Label in Tkinter - python

I am having a question with with four options and a timer.Now I had read a json file content and get it in questions list. After setting the GUI and shuffling the questions in question list , Now I want to update the questionText and options in Buttons. I called loadQuestion() function after all this, But my program stops abruptly after that.
from Tkinter import *
import json
from random import shuffle
import tkMessageBox
class ProgramGUI(Frame):
def __init__(self, master=None):
master.title('QuizBox')
master.update()
master.minsize(350, 150)
Frame.__init__(self, master)
try:
with open('questions.txt') as data_file:
try:
questions=json.load(data_file)
except ValueError,e:
tkMessageBox.showerror("Invalid JSON","Invlid JSON Format")
master.destroy()
questions=[]
data_file.close()
except (OSError, IOError) as err:
tkMessageBox.showerror("File Not Found","File Not Found!!")
master.destroy()
questionText = StringVar()
Label(master,textvariable=questionText,justify=CENTER,wraplength=200).pack()
questionText.set("Question text goes here")
timer = IntVar()
Label(master,textvariable=timer,justify=CENTER,fg="blue").pack()
timer.set("10")
buttonList = ["Answer 1", "Answer 2", "Answer 3", "Answer 4"]
self.rowconfigure(0, pad=3)
for i, value in enumerate(buttonList):
self.columnconfigure(i, pad=3)
Button(self, text=value).grid(row=0, column=i)
self.pack()
score = IntVar()
Label(master,textvariable=score,justify=CENTER).pack()
score.set("Score: 0")
shuffle(questions)
#print questions
loadQuestion(self)
master.mainloop()
def loadQuestion(self):
print questions
if len(questions) > 0:
# Modify the questionText StringVar with first question in question[] and delete it from questions[]
root = Tk()
gui = ProgramGUI(master=root)
root.destroy()
The loadQuestion() method is responsible for displaying the next question in the GUI and starting the timer. The method must first select a question (i.e. the dictionary containing the question and answers) from the questions list and then display the text of the question in the appropriate Label and the answers in the Buttons of the GUI in a random order. Also rather than trying to randomise the order of the answers, I need to shuffle the buttonList list to randomise the order of the buttons before assigning answers to them.
The question should be removed from the question list so that it is not selected again.We can use “pop()” list method to removes the last element in a list.
The timer IntVar is set to 11 and then “updateTimer()” is called to start the timer. Rather than trying to randomise the order of the answers, I am trying to shuffle the buttonList list to randomise the order of the buttons before assigning answers to them. Since the timer is immediately updated after being set, the first number the user sees is 10.
The updateTimer() method will first subtract one from the timer IntVar, and then check if the timer is 0. If it is, a messagebox with a “Game Over” message and the user’s score, then destroy the main window to end the program. Otherwise (if the timer is not 0), We need to call the “updateTimer()” method again in 1 second. I think to do we can use the “after()” method for this, and by storing the ID of the upcoming call in a variable we can cancel it as needed.
Note : questionList is json format of type :
[{
"question": "Example Question 1",
"wrong1": "Incorrect answer",
"wrong2": "Another wrong one",
"wrong3": "Nope, also wrong",
"answer": "Correct answer"
} ]

There are a few problems in your code that relate to your use of instance variables, these being variables unique to an instance of an object. Instance variables are accessible within methods of the same class, and this is what you need to do to access the question list within your loadQuestion() method. For example:
questions = json.load(data_file)
defines a local variable named questions in the __init__() method, however, this variable does not exist once the __init__() function terminates. You need to make it an instance variable with self like this:
self.questions = json.load(data_file)
Now this variable can be accessed with self.questions in methods of the same class such as loadQuestions(), which would be written like this (note use of self.):
def loadQuestion(self):
print self.questions
if len(self.questions) > 0:
# Modify the questionText StringVar with first question in question[] and delete it from questions[]
pass
Now, to update the value of the question label requires similar changes. Declare questionText as an instance variable in __init__():
self.questionText = StringVar()
and update it within loadQuestions():
def loadQuestion(self):
print self.questions
if len(self.questions) > 0:
# just take the first question and answers from the pre-shuffled list
q_and_a = self.questions.pop()
self.questionText.set(q_and_a['question'])
# update answer buttons too....
You will find that you will need to use a similar method for each of the answer buttons, i.e. make these instance variables and update the button's text in loadQuestions().

Related

How should be deleted widgets that were create by a loop, in python tkinter?

I created entries and labels by loops. If user click the button there will be new entries and lables in two-two seperated row. Now I want to delete them by other button clicking, but labels are not deletable -only entries are.
My other problem is I want to delete just only the last widgets that were added by one clicking. To make it clear: I want to delete the last 18-18 labels and entries by one clicking. Currently, all the added entries so far will be deleted. The labels are not at all.
Here is the relevant code:
self._1thLabel_list = ['Label1', 'Label2', 'Label3', 'Label4', 'Label5',
'Label6', 'Label7', 'Label8', 'Label9']
self._2thLabel_list = ['Label10', 'Label11', 'Label12', 'Label13', 'Label14',
'Label15', 'Label16', 'Label17', 'Label18']
nothingList2 = []
self.col = 4
for j in range(len(self._1thLabel_list)):
self.myLab = Label(root, text=self._1thLabel_list[j]).grid(row=0, column=j+1)
for k in range(1):
self.myEntry_loop = Entry(root)
self.myEntry_loop.grid(row=k + 1, column=j+1, pady=10, padx=10)
self.myEntry_loop_2 = Entry(root)
self.myEntry_loop_2.grid(row=k + 3, column=j + 1, pady=10, padx=10)
nothingList2.append(self.myEntry_loop)
nothingList2.append(self.myEntry_loop_2)
for l in range(len(self._2thLabel_list)):
self.myLab_2 = Label(root, text=self.mylist_2[l]).grid(row=2, column=l + 1)
self.myButton_newWidgets = Button(root, text="Add", command=self.Add)
self.myButton_newWidgets.grid(row=self.col, column=4)
self.myButton_deleteWidgets = Button(root, text="Delete", command=self.deleteThem)
self.myButton_deleteWidgets.grid(row=self.col, column=5)
And here is how try to delete them:
def deleteThem(self):
for v in range(18):
nothingList2.pop(0)
for dele in nothingList2:
dele.destroy() # Here, it is deleting entries but delete all of them. I want just delete the last 18 of them.
for w in range(9):
self._1thLabel_list.pop(0)
for delet in self._1thLabel_list:
delet.destroy() # I got "AttributeError: 'str' object has no attribute 'destroy'" error in this line
for x in range(9):
self._2thLabel_list.pop(0)
for delet2 in self._2thLabel_list:
delet2.destroy()
Here is a solution (explanation below):
from tkinter import Tk, Button
root = Tk()
removable_widget_dict = {}
class RemovableWidget(Button):
def __init__(self, parent, title, key):
Button.__init__(self, parent)
self.key = key
self.config(text=title)
self.pack()
def delete(self):
self.destroy()
removable_widget_dict.pop(self.key)
for i in range(10):
key = f'item{i}'
removable_widget_dict[key] = RemovableWidget(root, f'Button {i}', key)
for key in removable_widget_dict.keys():
if key == 'item5':
removable_widget_dict[key].delete()
break
root.mainloop()
Explanation:
First of I would like to mention that this is something I came up with (at least similar to this) when doing a personal project so all of this comes from experience in a sense that this may not be the best solution but it certainly works.
After doing the basic stuff with tkinter (root = Tk(), root.mainloop()) You create a dictionary which will sort of store classes. Now classes are an important factor here since each instance of a class can act independently which cannot really be achieved with functions at least not as easy.
So then You create a class for whatever widget You need, You can create multiple classes for multiple widgets or somehow merge them all into one (don't know how that would work) but lets stick to one class for one type of widget.
In this class You define all the basic stuff You would for a widget but also You add an important argument "key". This is not as important but certainly is necessary because You do not want to leave unused classes in memory (don't know about the technical aspects but imo it keeps everything more clean especially if You have to read it[dictionary] for some reason)
Then You define the deletion function and here is where the independence of class instances comes in: for each class instance You will be able to call this function which will only affect that class instance. So now in this function You will destroy the widget that class created (in this case the button in self.button) or multiple widgets. and then the cleanup part comes in: You globally define the dictionary and then delete the "key" from it. and the key also makes it easier to access the class instance for other reasons.
The final note. You can access the classes who are stored in dictionaries functions like this: dictionary[key].that_class_function

Choosing from a List of methods in a tkinter Button

Good Day,
I'm new to this forum (and quite new to programming), so I hope my question is properly formulated.
I've been trying to create a GUI in python using tkinter, and I want to have two buttons calling methods of two different classes. One method is defining an integer, the second one is reporting content. I'd have a list of objects of the latter class, and I want to choose the right instance by the integer. Here's a MWE:
import tkinter as tk
class data:
def __init__(self, content):
self.content = content
def report(self):
print("This is reported as self.content:" + str(self.content)) #This doesnt report the correct value for some reason?
print("The Class does register the correct idx:" + str(Selector.idx))
print("Using the Dict the correct value can be returned:" + str(vocables[Selector.idx].content))
class increment:
def __init__(self):
self.idx = 0
def increase(self):
self.idx += 1
print(self.idx)
vocables[self.idx].report()
root = tk.Tk()
Selector = increment()
vocables = []
for id in range(10):
vocables.append(data(id))
# print(vocables[id].content)
CheckVocable = tk.Button(root, text="Report", command=vocables[Selector.idx].report)
CheckVocable.pack()
NextVocable = tk.Button(root, text="Increase Index", command=Selector.increase)
NextVocable.pack()
root.mainloop()
I do not understand why the print of line 8 always reports the value of the first item in the list (vocabules[0] in this instance) instead of my desired value, which is returned in all other print cases. Am I messing up the work with classes or is the button behavior confusing me?
Thanks in advance!

(Radiobutton with JSON) how to check which Radiobutton has been selected

I would like to sort numbers when I clicked sort numbers Radiobutton. I already achieve this by calling a function when the Radiobutton is clicked. however, i couldn't sort numbers without calling a function.
this is my code
R1=Radiobutton(root,text="Sort Student Numbers",value=1)
R1.pack(anchor=W)
R2=Radiobutton(root,text="Sort Student Names",value=2)
R2.pack(anchor=W)
with open("student.json", "r"") as f:
data = json.load(f)
for d in data["student"]:
if value == 1:
data["student"].sort(key = lambda d: d["Numbers"])
elif value == 2:
data["student"].sort(key = lambda d: d["Names"])
label_1 = Label(frame , text="Name: %s" %(d["Names"]))
label_1.pack()
label_2 = Label(frame , text="Student Numbers: %d" %(d["Numbers"]))
label_2.pack()
if I say for example R1=Radiobutton(root,text="Sort Student Numbers",value=1, command = sorted_numbers(1)) everything works fine but the reason I don't want to use function calling is I would have to create 3 functions to achieve what I want. thanks
One way to solve this problem is to tie these radio buttons to a shared tkinter variable instance. When a radio button is selected, the value of the variable will be set to the value of the radio button, and then you can use that value in your code.
I haven't had time to test this code, but I have copied your code and modified it in a way that should work. This code assumes that you are importing everything from tkinter, using the line from tkinter import *; otherwise, you will need to do something like from tkinter import IntVar. There are several types of tkinter variable subclasses (IntVar, BooleanVar, etc.), and each has the methods get and set, which behave exactly as you'd expect (as demonstrated below).
# This is the variable that will store the value of the currently selected radio button
sort_value = IntVar()
# For each radio button, assign sort_value to the keyword parameter "variable"
R1=Radiobutton(root,text="Sort Student Numbers",variable=sort_value,value=1)
R1.pack(anchor=W)
R2=Radiobutton(root,text="Sort Student Names",variable=sort_value,value=2)
R2.pack(anchor=W)
with open("student.json", "r") as f:
data = json.load(f)
for d in data["student"]:
# sort_value is an IntVar, so sort_value.get returns a Python int
if sort_value.get() == 1:
data["student"].sort(key = lambda d: d["Numbers"])
elif sort_value.get() == 2:
data["student"].sort(key = lambda d: d["Names"])
label_1 = Label(frame , text="Name: %s" %(d["Names"]))
label_1.pack()
label_2 = Label(frame , text="Student Numbers: %d" %(d["Numbers"]))
label_2.pack()
Edit: Like Nae pointed out in the comments, you can also initialize the variable to a default value like this:
sort_value = IntVar(value=1)
Otherwise, its default value will be 0. I believe that by setting it to 1, this will also cause the radio button whose value is 1 to be selected by default.
I hope this helps.

Returning PY_VARxxx instead of expected string

I'm currently creating a GUI in order to turn a lot of individual instruments into one complete system. In def smuSelect(self) I create a list self.smuChoices I can use to call individual choices such as smuChoices[0] and it will return "2410(1)".
Once I call def checkBoxSetup it returns PY_VARxxx. I've tried searching the different forums and everything. I've seen mentions using the .get() which just gives me the state of the individual choice. The reason I want the actual string itself is I would like to use it in def testSetup(self) for the user to assign specific names to the individual machine, for example, 2410 = Gate.
My initial attempt was to create another variable smuChoice2 but I believe this is still changing the original list self.smuChoices.
import tkinter as tk
import numpy as np
from tkinter import ttk
def checkBoxSetup(smuChoice2): #TK.INTVAR() IS CHANGING NAME OF SMUS NEED TO CREATE ANOTHER INSTANCE OF SELF.SMUCHOICES
for val, SMU in enumerate(smuChoice2):
smuChoice2[val] = tk.IntVar()
b = tk.Checkbutton(smuSelection,text=SMU,variable=smuChoice2[val])
b.grid()
root = tk.Tk()
root.title("SMU Selection")
"""
Selects the specific SMUs that are going to be used, only allow amount up to chosen terminals.
--> If only allow 590 if CV is picked, also only allow use of low voltage SMU (maybe dim options that aren't available)
--> Clear Checkboxes once complete
--> change checkbox selection method
"""
smuChoices = [
"2410(1)",
"2410(2)",
"6430",
"590 (CV)",
"2400",
"2420"
]
smuChoice2 = smuChoices
smuSelection = ttk.Frame(root)
selectInstruct = tk.Label(smuSelection,text="Choose SMUs").grid()
print(smuChoices[0]) #Accessing list prior to checkboxsetup resulting in 2410(1)
checkBoxSetup(smuChoice2)
print(smuChoices[0]) #Accessing list after check box setup resulting in PY_VAR376
variableSMUs = tk.StringVar()
w7_Button = tk.Button(smuSelection,text="Enter").grid()
w8_Button = tk.Button(smuSelection,text="Setup Window").grid()
root.mainloop()
I was able to solve the problem by changing my list, smuChoices, to a dictionary then modifying
def checkBoxSetup(smuChoice2):
for val, SMU in enumerate(smuChoice2):
smuChoice2[val] = tk.IntVar()
b = tk.Checkbutton(smuSelection,text=SMU,variable=smuChoice2[val])
b.grid()
to
def checkBoxSetup(self):
for i in self.smuChoices:
self.smuChoices[i] = tk.IntVar()
b = tk.Checkbutton(self.smuSelection,text=i,variable=self.smuChoices[i])
b.grid()
Previously I was replacing the variable with what I'm guessing is some identifier that tkinter uses to store the state which is why I was getting PYxxx.
First of all getting PY_VARXX instead of what's in a variable class indicates the lack of get().
replace:
print(self.smuChoices[0])
with:
print(self.smuChoices[0].get())
Secondly, if you want to display the value of a variable class on a label, button, etc. you could rather just use the textvariable option by simply assigning the variable class to it.
Replace:
tk.Label(self.smuName,text=SMU).grid()
with:
tk.Label(self.smuName, textvariable=self.smuChoices[val]).grid()
Your question is still a bit unclear to me but I will try to provide an answer to the best of my understanding.
As I understand it, you're trying to create a set of Checkbuttons for a given list of items. Below is an example of a method that takes items as an argument and returns a dictionary of checkboxes that have root as their parent:
import tkinter as tk
def dict_of_cbs(iterable, parent):
if iterable:
dict_of_cbs = dict()
for item in iterable:
dict_of_cbs[item] = tk.Checkbutton(parent)
dict_of_cbs[item]['text'] = item
dict_of_cbs[item].pack() # it's probably a better idea to manage
# geometry in the same place wherever
# the parent is customizing its
# children's layout
return dict_of_cbs
if __name__ == '__main__':
root = tk.Tk()
items = ("These", "are", "some", "items.")
my_checkboxes = dict_of_cbs(items, root)
root.mainloop()
Additionally note that I haven't used any variable classes (BooleanVar, DoubleVar, IntVar or StringVar) as they seem to be redundant in this particular case.

Tkinter, saving functions to a list and then running them

I'm working on a GUI for a project in school. All the buttons that I have in my GUI are bound with functions that I have created. These functions call for already predefined functions. For some of the predefined functions, I need one or two arguments and I have solved that with entries. I type in the arguments in the right entries that are connected to the specific button and when I press the button, the function will run with the corresponding arguments.
The thing I want to do is to in some way when I press a button, the function should be saved to a list instead of being executed right away. And when I push the "run" button(a new button that I will create) everything in my list will be executed. I have been thinking about using a list box but I don't know exactly how they work or if its even possible to run a list box that contains a number of functions. Does someone have any ideas or solutions for me? Can I use the list box for this or is there something else that is better to use?
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.entry1 = IntVar()
self.entry2 = IntVar()
def do_something():
value1 = self.entry1.get()
value2 = self.entry2.get()
self.listbox.insert(END, "predefined_function(value1, value2)")
def run_listbox_contents():
pass
self.button = Button(frame, text="Move", command=lambda: do_something())
self.button.pack(side=TOP)
self.entry1.set("value1")
self.entry = Entry(frame, textvariable=self.entry1)
self.entry.pack(side=TOP)
self.entry2.set("value2")
self.entry = Entry(frame, textvariable=self.entry2)
self.entry.pack(side=TOP)
self.listbox = Listbox(master)
self.listbox.pack(side=TOP)
root = Tk()
app = App(root)
root.title("Mindstorms GUI")
root.geometry("800x1200")
root.mainloop()
root.destroy()
Just use a standard list.
something like this
def hest(txt):
print "hest: " +txt
def horse(txt):
print "horse: " + txt
funcList = []
funcList.append(hest)
funcList.append(horse)
for x in funcList:
x("Wow")
This outputs
hest: Wow
horse: Wow
Was this what you wanted?
If I were you, I wouldn't want to save functions to a list. I would suggest another solution for you.
I suppose you have heard of the principle of MVC (Model-View-Controller). In your case, the list box is a part of view, and the process that saves functions and then calls them at once is a part of controller. Separate them.
You might want to save and display any string in the list box to let the users know that the corresponding functions have been enlisted and ready to run. For example, save a string "Function1 aug1 aug2 aug3" or "Funtion2 aug1 aug2" or whatever you like as a handle of the corresponding function.
And for the controller part, write a function (let's say conductor()). It reads the handle strings from the list, parses them and calls the corresponding functions. Where you want to run the enlisted functions, there you just call conductor().
Update:
Due to your comment I understand that you are pretty new to program. Let me show you how to write a simplest parser with your given variable names.
def run_listbox():
to_do_list = #get the list of strings
for handle_string in to_do_list:
#Let's say you got
#handle_string = "Predfined_function1 value1 value2"
#by here
handle = handle_string.split(" ")
#Split the string by space, so you got
#handle = ["Predfined_function1", "value1", "value2"]
#by here
if handle[0] == "Predfined_function1":
Predfined_function1(handle[1], handle[2]) #Call Predfined_function1(value1, value2)
elif handle[0] == "Predfined_function2":
Predfined_function2(handle[1], handle[2])
#elif ...
#...
#elif ...
#...
#elif ...
#...
This is not a perfect parser, but I hope it could let you know what does a parser look like.

Categories