Related
I have a settings page with lots of checkbuttons on it so I am trying to reduce the code but I am struggling when it comes to getting the checkbutton value to work so far I have:-
def _create_checkbox(self, label, index, state=0):
x = label.replace(" ", "-").lower()
self.settings_list[x] = state
ttk.Label(self.settings_frame, text=label).grid(
row=index, column=0)
ttk.Checkbutton(
self.settings_frame, variable=self.settings_list[x]
).grid(row=index, column=1)
the idea was to put the checkbutton names in a dict and then update the dict with the value but it is not working as planned, with my code all checkbutton values update as if they were one.
example list:
self.settings_list = {"force-gamemode": "0", "allow-cheats": "1"}
Edit to show minimal working example, I did originally try to use variables (IntVar) but it failed (I cant remember why) but that's why I then switched to a dict:-
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("tkinter dynamic checkbox example")
self.geometry("700x450")
self.settings_list = {"force-gamemode": "0", "allow-cheats": "1"}
self.settings_frame = tk.Frame(self)
self.settings_frame.grid(row=0, column=0)
# create settings content
self._create_checkbox("Force Gamemode", 0, 0)
tk.Label(
self.settings_frame, text="Label to show content between checkboxes"
).grid(row=1, column=0)
self._create_checkbox("Allow Cheats", 2, 0)
tk.Button(
self.settings_frame,
text="Create Properties File",
command=self._create_properties,
).grid(row=3, column=0, sticky="ew")
def _create_checkbox(self, label, index, state=0):
x = label.replace(" ", "-").lower()
self.settings_list[x] = state
ttk.Label(self.settings_frame, text=label).grid(
row=index, column=0, padx=5, pady=5, sticky="w"
)
ttk.Checkbutton(self.settings_frame, variable=self.settings_list[x]).grid(
row=index, column=1, padx=5, pady=5, sticky="w"
)
def _create_properties(self):
print(self.settings_list["force-gamemode"])
print(self.settings_list["allow-cheats"])
if __name__ == "__main__":
app = App()
app.mainloop()
Since you passed an integer 0 as the textvariable option of those Checkbutton widgets, an implicit IntVar will be created with name "0" for them. Therefore they will be changed together because they share same tkinter variable.
You need to change
self.settings_list[x] = state
to
self.settings_list[x] = tk.IntVar(value=state)
inside _create_checkbox().
In addition to acw1668's answer, to check the value
def _create_properties(self):
print(self.settings_list["force-gamemode"].get())
print(self.settings_list["allow-cheats"].get())
so your code is ...
import tkinter as tk
from tkinter import ttk
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title("tkinter dynamic checkbox example")
self.geometry("700x450")
self.settings_list = {"force-gamemode": "0", "allow-cheats": "0"}
self.settings_frame = tk.Frame(self)
self.settings_frame.grid(row=0, column=0)
# create settings content
self._create_checkbox("Force Gamemode", 0, 0)
tk.Label(self.settings_frame, text="Label to show content between checkboxes"
).grid(row=1, column=0)
self._create_checkbox("Allow Cheats", 2, 0)
tk.Button(self.settings_frame, text="Create Properties File",
command=self._create_properties
).grid(row=3, column=0, sticky="ew")
def _create_checkbox(self, label, index, state=0):
x = label.replace(" ", "-").lower()
self.settings_list[x] = tk.IntVar(value=state)
ttk.Label(self.settings_frame, text=label
).grid(row=index, column=0, padx=5, pady=5, sticky="w")
ttk.Checkbutton(self.settings_frame, variable=self.settings_list[x]
).grid(row=index, column=1, padx=5, pady=5, sticky="w")
def _create_properties(self):
print(self.settings_list["force-gamemode"].get())
print(self.settings_list["allow-cheats"].get())
if __name__ == "__main__":
app = App()
app.mainloop()
I have only been programming 3 months so any advice on improvement to my code is appreciated even if it inst related to my specific question.
Its a simple small project with tkinter. Two fields to enter your first and last name then you hit the swap button and it will swap what ever you put in the name fields.
Problem is I dont want to use globals and I cant seem to figure it out I know its probably something easy and I did spend time trying to figure it out.
If you have any improvements to the code let me know.
from tkinter import *
### I dont Want Globals but cant figure out another method for doing this
### Hope some one can help me with this part
evar = ""
evar1 = ""
def mainWindow():
root = Tk()
root.title("Swap Names")
root.geometry("400x150+100+250")
return root
def createVar():
global evar
global evar1
evar = StringVar()
evar1 = StringVar()
def firstNameFrame(root):
frame1 = Frame(root)
frame1.pack(side=TOP, padx=2, pady=2)
label = Label(frame1, text="First Name:")
label.pack(side=LEFT, padx=2, pady=2)
entry = Entry(frame1, textvariable = evar)
entry.pack(side=LEFT, pady = 2)
def lastNameFrame(root):
frame2 = Frame(root)
frame2.pack(side=TOP, padx=2, pady=2)
label = Label(frame2, text="Last Name:")
label.pack(side=LEFT, padx=1, pady=1)
entry = Entry(frame2, textvariable = evar1)
entry.pack(side=LEFT, pady = 5)
def swapFrame(root):
frame3 = Frame(root)
frame3.pack(side=TOP, padx=10, pady = 10)
swapButton = Button(frame3, text="Swap",command = swap)
swapButton.pack(side=LEFT, padx =5, pady=5)
### I would like to some how use swap with out using a global
def swap():
b=evar.get()
evar.set(evar1.get())
evar1.set(b)
def main():
root = mainWindow()
createVar()
firstNameFrame(root)
lastNameFrame(root)
swapFrame(root)
root.mainloop()
main()
One of the solutions can be wrapping all the code related to the initialization and working with Tk in a separate class, so instead of global variables, we will use the class instance variables:
from tkinter import *
class Gui(object):
def __init__(self):
self.root = Gui._init_main_window()
self.first_name_var = StringVar()
self.last_name_var = StringVar()
self._init_first_name_frame()
self._init_last_name_frame()
self._init_swap_frame()
#staticmethod
def _init_main_window():
root = Tk()
root.title("Swap Names")
root.geometry("400x150+100+250")
return root
def _init_first_name_frame(self):
frame1 = Frame(self.root)
frame1.pack(side=TOP, padx=2, pady=2)
label = Label(frame1, text="First Name:")
label.pack(side=LEFT, padx=2, pady=2)
entry = Entry(frame1, textvariable=self.first_name_var)
entry.pack(side=LEFT, pady=2)
def _init_last_name_frame(self):
frame2 = Frame(self.root)
frame2.pack(side=TOP, padx=2, pady=2)
label = Label(frame2, text="Last Name:")
label.pack(side=LEFT, padx=1, pady=1)
entry = Entry(frame2, textvariable=self.last_name_var)
entry.pack(side=LEFT, pady=5)
def _init_swap_frame(self):
frame3 = Frame(self.root)
frame3.pack(side=TOP, padx=10, pady=10)
swap_button = Button(frame3, text="Swap", command=self._swap)
swap_button.pack(side=LEFT, padx=5, pady=5)
def _swap(self):
tmp = self.first_name_var.get()
self.first_name_var.set(self.last_name_var.get())
self.last_name_var.set(tmp)
def mainloop(self):
return self.root.mainloop()
def main():
gui = Gui()
gui.mainloop()
if __name__ == '__main__':
main()
A small comment to the code above: adding a prefix __ to variables or methods allows you to hide access to them directly by name outside the class using the name mangling.
UPD: According to #Coal comment, changed the double underscore prefixes to single underscore, as there is no need to use a name mangling.
This is assuming that when you say you don't want to use global, that you also mean that you don't want to use self:
from tkinter import Tk, Button, Entry
def swap(fn, ln):
# Get the contents of the two fields.
first = fn.get()
last = ln.get()
# Clear the contents of both fields.
first_name.delete(0, 'end')
last_name.delete(0, 'end')
# Set each field to the previous content of the other field.
first_name.insert(0, last)
last_name.insert(0, first)
root = Tk()
first_name = Entry(root)
last_name = Entry(root)
first_name.insert(0, "Enter first name")
last_name.insert(0, "Enter last name")
first_name.pack()
last_name.pack()
swap_button = Button(root, text="SWAP", command=lambda:swap(first_name, last_name))
swap_button.pack()
root.mainloop()
I have programmed a script which takes random four elements from a table and question to the user using tkinter, random and sqlite3. Currently, I can ask a question. Implement four choices with radiobuttons. I can also test if the answer is correct or not and show the result to the user via toplevel().
Problem is, how can I refresh the question after the continue button clicked?
My whole code is below. I have tried refreshing the random numbers and labels under continue_asking or another def called from continue_asking. But it doesn't work at all.
from tkinter import *
from sqlite3 import *
from random import *
class Question(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.grid()
self.prepare_question()
def prepare_question(self):
self.tumu = {0:['ask1','answer1'], # instead of SQL query
1:['ask2','answer2'],
2:['ask3','answer3'],
3:['ask4','answer4']}
self.create_widgets()
def create_widgets(self):
self.choiceFrame = Frame(self)
self.choiceFrame.grid(row=2, column=0)
self.choiceNum = IntVar()
for i in range(4):
Radiobutton(self.choiceFrame, text=self.tumu[i][1], variable=self.choiceNum, value=i) \
.grid(row=2, column=i, padx=5, pady=5)
self.q_num = randrange(4)
self.q_word = self.tumu[self.q_num][0]
lbl_question = Label(self, text="Which one is the meaning of the word: " + self.q_word, font="Courier 12")
lbl_question.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky=W)
txt_question = Text(self, height=1, font="Courier 12", pady=2)
txt_question.tag_configure("myStyle", font="Courier 12 bold")
txt_question.insert("end", "Please choose the answer and ")
txt_question.insert("end", "click okay to see the results.", "myStyle")
txt_question.configure(state="disabled")
txt_question.grid(row=1, column=0, columnspan=4, padx=5, sticky=W)
btn_okay = Button(self, text="Okay", font="12", command=self.a_control)
btn_okay.grid(row=3, column=0, columnspan=2)
def a_control(self):
self.choosenWord = self.q_num
self.frm_answer = Toplevel()
self.frm_answer.title("Result")
self.selectedWord = self.choiceNum.get()
txt_result = Text(self.frm_answer, height=4, width = 40)
if self.choosenWord == self.selectedWord:
txt_result.insert("end", "Congrats! Your answer is correct.\n")
else:
txt_result.insert("end","Your answer is not correct.\n")
txt_result.insert("end", "Correct answer is " + self.tumu[self.q_num][1] + '\n')
txt_result.insert("end", "Please click Continue to continue.\n")
txt_result.insert("end", "Click cancel to quit.")
txt_result.grid(row=0, column=0, columnspan=2, padx = 5, pady=5)
txt_result.configure(state="disabled")
btn_continue = Button(self.frm_answer, text="Continue", command=lambda: self.continue_asking(self.frm_answer))
btn_continue.grid(row=1, column=0, padx=5, pady=5, sticky = W)
btn_quit = Button(self.frm_answer, text="Cancel", command=self.end_asking)
btn_quit.grid(row=1, column=1, padx=5, pady=5, sticky = W)
def continue_asking(self,frm_answer):
frm_answer.destroy()
def end_asking(self):
root.destroy()
root = Tk()
app = Question(root)
root.mainloop()
I have tried adding prepare_question to continue_asking. It keeps asking questions but widgets are not changing. They are just overlapping.
EDIT
So let's restart from scratch, i was totally wrong because no widget was removed and they stacked in the main Frame children list.
You still don't need to write so much code, mostly move some parts.
First, to be able to update the widgets and prepare the new question peacefully, move
self.create_widgets() in the constructor and put the random index self.q_num and self.q_word inside prepare_question, since it belongs to the logic of the question creation.
In create_widgets() you only need to keep some control on the label question, so we add self.lbl_question...
Finally, i suggest to create a new function update_widgets(), but you can put the logic inside continue_asking().
In this function, call prepare_question to update the next question (sql query and random stuff). Since we move the random index, everything is ready to update each widget:
text of the question label
text of radiobuttons. I'm not so proud of the loop to change those, but that'll do the trick. (we keep the values created for the indexes to match the new ones, i'm not very sure about this logic with SQL queries, i follow your first implementation with text=self.tumu[i][1])
If someone can tell how to get the radiobutton value more easily, i'm interested
Here is the whole code:
from tkinter import *
from sqlite3 import *
from random import *
class Question(Frame):
def __init__(self, master):
Frame.__init__(self, master)
self.grid()
self.prepare_question()
self.create_widgets()
def prepare_question(self):
self.tumu = {0:['ask1','answer1'], # instead of SQL query
1:['ask2','answer2'],
2:['ask3','answer3'],
3:['ask4','answer4']}
self.q_num = randrange(4)
self.q_word = self.tumu[self.q_num][0]
def create_widgets(self):
self.choiceFrame = Frame(self)
self.choiceFrame.grid(row=2, column=0)
self.choiceNum = IntVar()
for i in range(4):
Radiobutton(self.choiceFrame, text=self.tumu[i][1], variable=self.choiceNum, value=i) \
.grid(row=2, column=i, padx=5, pady=5)
self.lbl_question = Label(self, text="Which one is the meaning of the word: " + self.q_word, font="Courier 12")
self.lbl_question.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky=W)
txt_question = Text(self, height=1, font="Courier 12", pady=2)
txt_question.tag_configure("myStyle", font="Courier 12 bold")
txt_question.insert("end", "Please choose the answer and ")
txt_question.insert("end", "click okay to see the results.", "myStyle")
txt_question.configure(state="disabled")
txt_question.grid(row=1, column=0, columnspan=4, padx=5, sticky=W)
btn_okay = Button(self, text="Okay", font="12", command=self.a_control)
btn_okay.grid(row=3, column=0, columnspan=2)
def a_control(self):
self.choosenWord = self.q_num
self.frm_answer = Toplevel()
self.frm_answer.title("Result")
self.selectedWord = self.choiceNum.get()
txt_result = Text(self.frm_answer, height=4, width = 40)
if self.choosenWord == self.selectedWord:
txt_result.insert("end", "Congrats! Your answer is correct.\n")
else:
txt_result.insert("end","Your answer is not correct.\n")
txt_result.insert("end", "Correct answer is " + self.tumu[self.q_num][1] + '\n')
txt_result.insert("end", "Please click Continue to continue.\n")
txt_result.insert("end", "Click cancel to quit.")
txt_result.grid(row=0, column=0, columnspan=2, padx = 5, pady=5)
txt_result.configure(state="disabled")
btn_continue = Button(self.frm_answer, text="Continue", command=self.continue_asking)
btn_continue.grid(row=1, column=0, padx=5, pady=5, sticky = W)
btn_quit = Button(self.frm_answer, text="Cancel", command=self.end_asking)
btn_quit.grid(row=1, column=1, padx=5, pady=5, sticky = W)
def continue_asking(self):
self.frm_answer.destroy()
self.update_widgets()
def update_widgets(self):
self.prepare_question()
# change question text
self.lbl_question.configure(text = "Which one is the meaning of the word: " + self.q_word)
# change Radiobutton
for child in self.choiceFrame.children.values():
index = child.config()['value'][4]
child.configure(text = self.tumu[index][1])
if index == 0: # reset the focus
child.select()
def end_asking(self):
root.destroy()
root = Tk()
app = Question(root)
root.mainloop()
First crap post: (the not to do part)
You don't need to change so much code to fix the present issue, have you already tried the following ?
def continue_asking(self,frm_answer):
frm_answer.destroy()
self.prepare_question()
I won't review the whole code, there is another place for that, but you can also avoid the lambda when you call continue_asking(), since you store the frame in self.frm_answer
btn_continue = Button(self.frm_answer, text="Continue", command=self.continue_asking)
# [...]
def continue_asking(self):
self.frm_answer.destroy()
self.prepare_question()
What I want to get: change of checkbox state changes the state of the Entry widget from 'disabled' into 'normal'. (checkbox off = Entry disabled, checkbox on = Entry normal).
My problem is that I don't know how to access and update the state of entry.
My code:
from tkinter import *
from tkinter import ttk
class App(Frame):
def __init__(self, master):
ttk.Frame.__init__(self, master, padding='20')
self.grid()
self.create_checkbox()
self.create_entry()
def create_checkbox(self):
self.limit = BooleanVar()
Checkbutton(self,
text='Limit length',
variable= self.limit,
command= self.state_update,
).grid(row=1, column=1, sticky=W)
def create_entry(self):
self.entry_low = StringVar()
Entry(self,
width=6,
textvariable=self.entry_low,
state='disabled',
).grid(row=1, column=2, sticky=W)
def state_update(self):
self.entry_low.config(state="normal") #THIS OBVIOUSLY DOES NOT WORK
root = Tk()
root.title("Lottery")
app = App(root)
root.mainloop()
I'm beginner, so I'd be especially grateful for simple solutions.
Save a reference to the entry widget, then call the configure method. To make things easy, give your checkbutton the values for the states. That isn't strictly necessary, you can use a boolean and then translate that to the appropriate state.
def create_checkbox(self):
self.limit = StringVar(value="normal")
checkbutton = Checkbutton(..., onvalue="normal", offvalue="disabled", ...)
checkbutton.grid(...)
def create_entry(self):
self.entry_low = StringVar()
self.entry = Entry(self,
width=6,
textvariable=self.entry_low,
state='disabled',
)
self.entry.grid(row=1, column=2, sticky=W)
def state_update(self):
self.entry.config(state="normal") #THIS OBVIOUSLY DOES NOT WORK
Note: you need to call grid in a second step. grid(...) (as well as place) returns None. If you do x=Entry(...).grid(...), x will always be None.
I have been trying to desing a form in tkinter using python, but unfortunately I am stuck again, but now putting a form into the notebook I already have. I have run them separately and they work perfect, the problem starts when I try to put them together.
For example, if I write in "componentComb= ttk.Combobox(frameOne, width="19")" instead of frameOne I put firstStep which is what I want to do (merge them), I have an error like this:
componentComb= ttk.Combobox(firstStep, width="19")
NameError: name 'firstStep' is not defined
Which I don't understand, I have already defined, but probably wrong!!! Can you help me with this problem?
Below you have the code I have been "fighting" with, and I hope you can help me!
Thanks in advance
Here is my code:
#Starts
import Tkinter
from Tkinter import *
from ttk import *
import tkMessageBox
import ttk
# start of GUI code
root = Tk()
root.title("Model A")
root.minsize(1000, 150)
root.maxsize(1100, 200)
notebook = ttk.Notebook(root)
notebook.pack(fill='both', expand='yes')
notebook.pressed_index = None
# Child Frame
frameOne = Tkinter.Frame(notebook, bg='white')
frameOne.pack(fill='both', expand=True)
frameTwo = Tkinter.Frame(notebook, bg='white')
frameTwo.pack(fill='both', expand=True)
frameThree= Tkinter.Frame(notebook, bg='white')
frameThree.pack(fill='both', expand=True)
frameFour= Tkinter.Frame(notebook, bg='white')
frameFour.pack(fill='both', expand=True)
# Pages
notebook.add(frameOne, text='Standard')
notebook.add(frameTwo, text='TID')
notebook.add(frameThree, text='MEE')
notebook.add(frameFour, text='Final')
# Merging form and notebook
def defocus(event):
event.widget.master.focus_set()
if __name__ == '__main__':
firstStep = Tkinter.Label(notebook, text=" 1. Enter Main Details: ", font=("fixedsys", "16","bold italic"))
firstStep.grid(row=2, columnspan=7, sticky='W', \
padx=5, pady=5, ipadx=5, ipady=5)
#Main Selection
componentComb= ttk.Combobox(frameOne, width="19")
componentComb = Combobox(frameOne, state="readonly", values=("TGB", "RST", "CCPa"))
componentComb.grid(column=4, row=0, columnspan="5", sticky="nswe")
componentComb.set("Main Selection")
#Temperature Selection
tempComb = ttk.Combobox(frameOne, width="14")
tempComb = Combobox(frameOne, state="readonly", values=("-40", "-30", "-20","-10", "0", "10","20", "30"))
tempComb.grid(column=0, row=2, columnspan="2", sticky="w")
tempComb.set("Temperature Selection")
#Device Type Selection
DeviceTypeComb = ttk.Combobox(frameOne, width="14")
DeviceTypeComb = Combobox(frameOne, state="readonly", values=("QML", "Non-QML"))
DeviceTypeComb.grid(column=3, row=2, columnspan="2", sticky="w")
DeviceTypeComb.set("Device Type Selection")
#Junction Temperature Selection
JunctionTempComb = ttk.Combobox(frameOne, width="16")
JunctionTempComb = Combobox(frameOne, state="readonly", values=("-40", "-30", "-20","-10", "0", "10","20", "30"))
JunctionTempComb.grid(column=5, row=2, columnspan="2", sticky="w")
JunctionTempComb.set("Junction Temp Selection")
#Chip Area in Cm2 Selection
ChipAreaComb = ttk.Combobox(frameOne, width="12")
ChipAreaComb = Combobox(frameOne, state="readonly", values=("0.001","0.002","0.003","0.004","0.005","0.006"))
ChipAreaComb.grid(column=7, row=2, columnspan="2", sticky="e")
ChipAreaComb.set("Chip Area Selection")
#Time of Exposure
TimeOfExpoComb = ttk.Combobox(frameOne, width="12")
TimeOfExpoComb = Combobox(frameOne, state="readonly", values=("1", "2", "3","4", "5"))
TimeOfExpoComb.grid(column=9, row=2, columnspan="2", sticky="w")
TimeOfExpoComb.set("Time of Exposure")
root.mainloop()
firstStep is a local variable in the defocus function, and you are trying to access it in a global scope.
You have to define it outside of the function so that it has a meaning in the global scope. But note that because you are assigning it within the function, you need to use the global keyword so that it doesn't think you're introducing a new local variable with the same name.
This should work:
firstStep = None #define the variable globally
def defocus(event):
event.widget.master.focus_set()
if __name__ == '__main__':
global firstStep # we want to reassign the global version of this variable
firstStep = Tkinter.Label(notebook, text=" 1. Enter Main Details: ", font=("fixedsys", "16","bold italic"))
firstStep.grid(row=2, columnspan=7, sticky='W', \
padx=5, pady=5, ipadx=5, ipady=5)
componentComb= ttk.Combobox(firstStep, width="19")