which checkbutton has been checked - python-3.x

I have created a checkbox. I want the program prints 'python' when I tick it in created checkbox.
but it doesn't work...I don't get error but it doesn't print 'python'
please help.
this is my code
#!/usr/bin/python3
from tkinter import *
class Checkbar(Frame):
def __init__(self, parent=None, picks=[], side=LEFT, anchor=W):
Frame.__init__(self, parent)
self.vars = []
for pick in picks:
var = IntVar()
chk = Checkbutton(self, text=pick, variable=var)
chk.pack(side=side, anchor=anchor, expand=YES)
self.vars.append(var)
def state(self):
return map((lambda var: var.get()), self.vars)
def __getitem__(self,key):
return self.vars[key]
if __name__ == '__main__':
root = Tk()
lng = Checkbar(root, ['Python', 'Ruby', 'Perl', 'C++'])
tgl = Checkbar(root, ['English','German'])
lng.pack(side=TOP, fill=X)
tgl.pack(side=LEFT)
lng.config(relief=GROOVE, bd=2)
def allstates():
## print(lng[0])
if lng[0] == 1:
print ('python')
Button(root, text='Quit', command=root.quit).pack(side=RIGHT)
Button(root, text='Peek', command=allstates).pack(side=RIGHT)
root.mainloop()

lng[0] is an IntVar, not an int, so it's never equal to one.
You need to use the get method to compare the value of the variable to 1:
def allstates():
if lng[0].get() == 1:
print ('python')
Another solution is to change the __getitem__ method of the Checkbar so that it returns the value of the variable instead of the variable itself as I proposed in answer to Python Form: Using TKinter --> run script based on checked checkboxes in GUI
def __getitem__(self, key):
return self.vars[key].get()
In this case, you don't have to change the allstates function.

Related

Catch macos ⌘Q in python tkinter application

eWithin a tkinter application I am catching several events to gracefully shutdown some threads within the application, before the main thread terminates.
This is all working swiftly as long as I use a bound key combination or the
window control, the cross in the red circle.
On macos the application automatically gets a 'python' menu with a close function bound to key combination ⌘Q. This event is not handled properly. It seems to kill the main thread but other threads are not closed properly.
Following bindings are used to catch all closing events:
self.root.bind('<Control-x>', self.exitapp)
self.root.protocol("WM_DELETE_WINDOW", self.exitapp)
atexit.register(self.catch_atexit)
Recently found that the left and right ⌘ keys are repesented as Meta_L and Meta_R but cannot be combined with a second key, i.e. '<Meta_L-q>'.
Can anyone explain howto catch ⌘Q?
Please find code example below:
#!/usr/bin/env python3
import sys
from tkinter import *
from tkinter import ttk
import threading
import time
import atexit
class subthread():
def __init__(self):
self.thr = None
self.command = ''
self.proof = ""
def start(self):
if not self.thr or not self.thr.is_alive():
self.command = 'run'
self.thr = threading.Thread(target=self.loop)
self.thr.start()
else:
print('thread already running')
def stop(self):
self.command = 'stop'
if self.thr and self.thr.is_alive():
print('stopping thread')
else:
print('thread not running')
def running(self):
return True if self.thr and self.thr.is_alive() else False
def get_proof(self):
return self.proof
def loop(self):
while self.command == 'run':
time.sleep(0.5)
print('+', end='')
self.proof += '+'
if len(self.proof) > 30:
self.proof = ""
def __del__(self):
print('del instance subthread')
self.command = 'stop'
if self.thr and self.thr.is_alive():
self.thr.join(2)
class app():
def __init__(self, rootframe):
self.root = rootframe
self.gui = ttk.Frame(self.root)
self.gui.pack(fill=BOTH)
row = 0
self.checkvar = IntVar()
self.checkvar.trace('w', self.threadchange)
ttk.Label(self.gui, text="Use checkbox to start and stop thread").grid(row=row, column=0, columnspan=2)
ttk.Checkbutton(self.gui, text='thread', variable=self.checkvar).grid(row=1, column=0)
self.threadstatus = StringVar()
self.threadstatus.set('not running')
row += 1
ttk.Label(self.gui, textvariable=self.threadstatus).grid(row=row, column=1)
row += 1
self.alivestring = StringVar()
ttk.Entry(self.gui, textvariable=self.alivestring).grid(row=row, column=0, padx=10, sticky="ew",
columnspan=3)
row += 1
ttk.Separator(self.gui, orient="horizontal").grid(row=row, column=0, padx=10, sticky="ew",
columnspan=3)
row += 1
ttk.Label(self.gui, text="- Available options to close application: [ctrl]-x,"
" window-control-red, [CMD]-q").grid(row=row, column=0, padx=10, columnspan=3)
row += 1
ttk.Label(self.gui, text="1. Try all three without thread running").grid(row=row, column=0,
columnspan=3, sticky='w')
row += 1
ttk.Label(self.gui, text="2. Retry all three after first starting the thread").grid(row=row, column=0,
columnspan=3, sticky='w')
row += 1
ttk.Label(self.gui, text="3. Experience that only [CMD]-q fails").grid(row=row, column=0,
columnspan=3, sticky='w')
self.subt = subthread()
self.root.bind('<Control-x>', self.exitapp1)
self.root.protocol("WM_DELETE_WINDOW", self.exitapp2)
atexit.register(self.catch_atexit)
self.root.after(500, self.updategui)
def threadchange(self, a, b, c):
""" checkbox change handler """
try:
if self.checkvar.get() == 1:
self.subt.start()
else:
self.subt.stop()
except Exception as ex:
print('failed to control subt', str(ex))
def updategui(self):
""" retriggering timer handler to update status label gui """
try:
if self.subt.running():
self.threadstatus.set("thread is running")
else:
self.threadstatus.set("thread not running")
self.alivestring.set(self.subt.get_proof())
except:
pass
else:
self.root.after(500, self.updategui)
def __del__(self):
print('app del called')
def exitapp1(self, a):
print('exitapp1 called')
self.subt.stop()
sys.exit(0)
def exitapp2(self):
print('exitapp2 called')
self.subt.stop()
sys.exit(0)
def catch_atexit(self):
print('exitapp called')
self.subt.stop()
self.subt = None
sys.exit(0)
if __name__ == '__main__':
root = Tk()
dut = app(rootframe=root)
root.mainloop()
print('main exiting')
sys.exit(0)
You can catch ⌘Q with <Command-q>:
...
def action(event):
print("bind!")
root.bind_all("<Command-q>", action)
...
This worked for me on macOS HightSierra
#EddyHoogevorst Yes, it does not work on Big Sur.
The code that works is:
root.createcommand("::tk::mac::Quit", action).
NB: need to change function signature for action as event is not passed as an argument in this context.

Tkinter buttons not changing back to the correct color after state changing to active

I am making this PDF tool, and I want the buttons to be disabled until a file or files are successfully imported. This is what the app looks like at the launch:
Right after running the callback for the import files button, the active state looks like this:
I want the colors of the buttons to turn maroon instead of the original grey. They only turn back to maroon once you hover the mouse over them. Any thoughts for how to fix this? Here is the callback for the import button:
def import_callback():
no_files_selected = False
global files
files = []
try:
ocr_button['state'] = DISABLED
merge_button['state'] = DISABLED
status_label.pack_forget()
frame.pack_forget()
files = filedialog.askopenfilenames()
for f in files:
name, extension = os.path.splitext(f)
if extension != '.pdf':
raise
if not files:
no_files_selected = True
raise
if frame.winfo_children():
for label in frame.winfo_children():
label.destroy()
make_import_file_labels(files)
frame.pack()
ocr_button['state'] = ACTIVE
merge_button['state'] = ACTIVE
except:
if no_files_selected:
status_label.config(text='No files selected.', fg='blue')
else:
status_label.config(text='Error: One or more files is not a PDF.', fg='red')
status_label.pack(expand='yes')
import_button = Button(root, text='Import Files', width=scaled(20), bg='#5D1725', bd=0, fg='white', relief='groove',
command=import_callback)
import_button.pack(pady=scaled(50))
I know this was asked quite a while ago, so probably already solved for the user. But since I had the exact same problem and do not see the "simplest" answer here, I thought I would post:
Just change the state from "active" to "normal"
ocr_button['state'] = NORMAL
merge_button['state'] = NORMAL
I hope this helps future users!
As I understand you right you want something like:
...
ocr_button['state'] = DISABLED
ocr_button['background'] = '#*disabled background*'
ocr_button.bind('<Enter>', lambda e:ocr_button.configure(background='#...'))
ocr_button.bind('<Leave>', lambda e:ocr_button.configure(background='#...'))
merge_button['state'] = DISABLED
merge_button['background'] = '#*disabled background*'
merge_button.bind('<Enter>', lambda e:ocr_button.configure(background='#...'))
merge_button.bind('<Leave>', lambda e:ocr_button.configure(background='#...'))
...
...
ocr_button['state'] = ACTIVE
ocr_button['background'] = '#*active background*'
ocr_button.unbind('<Enter>')
ocr_button.unbind('<Leave>')
merge_button['state'] = ACTIVE
merge_button['background'] = '#*active background*'
merge_button.unbind('<Enter>')
merge_button.unbind('<Leave>')
...
If there are any errors, since I wrote it out of my mind or something isnt clear, let me know.
Update
the following code reproduces the behavior as you stated. The reason why this happens is how tkinter designed the standart behavior. You will have a better understanding of it if you consider style of ttk widgets. So I would recommand to dont use the automatically design by state rather write a few lines of code to configure your buttons how you like, add and delete the commands and change the background how you like. If you dont want to write this few lines you would be forced to use ttk.Button and map a behavior you do like
import tkinter as tk
root = tk.Tk()
def func_b1():
print('func of b1 is running')
def disable_b1():
b1.configure(bg='grey', command='')
def activate_b1():
b1.configure(bg='red', command=func_b1)
b1 = tk.Button(root,text='B1', bg='red',command=func_b1)
b2 = tk.Button(root,text='disable', command=disable_b1)
b3 = tk.Button(root,text='activate',command=activate_b1)
b1.pack()
b2.pack()
b3.pack()
root.mainloop()
I've wrote this simple app that I think could help all to reproduce the problem.
Notice that the state of the button when you click is Active.
#!/usr/bin/python3
import sys
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Main(ttk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__()
self.parent = parent
self.init_ui()
def cols_configure(self, w):
w.columnconfigure(0, weight=0, minsize=100)
w.columnconfigure(1, weight=0)
w.rowconfigure(0, weight=0, minsize=50)
w.rowconfigure(1, weight=0,)
def get_init_ui(self, container):
w = ttk.Frame(container, padding=5)
self.cols_configure(w)
w.grid(row=0, column=0, sticky=tk.N+tk.W+tk.S+tk.E)
return w
def init_ui(self):
w = self.get_init_ui(self.parent)
r = 0
c = 0
b = ttk.LabelFrame(self.parent, text="", relief=tk.GROOVE, padding=5)
self.btn_import = tk.Button(b,
text="Import Files",
underline=1,
command = self.on_import,
bg='#5D1725',
bd=0,
fg='white')
self.btn_import.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
self.parent.bind("<Alt-i>", self.switch)
r +=1
self.btn_ocr = tk.Button(b,
text="OCR FIles",
underline=0,
command = self.on_ocr,
bg='#5D1725',
bd=0,
fg='white')
self.btn_ocr["state"] = tk.DISABLED
self.btn_ocr.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
r +=1
self.btn_merge = tk.Button(b,
text="Merge Files",
underline=0,
command = self.on_merge,
bg='#5D1725',
bd=0,
fg='white')
self.btn_merge["state"] = tk.DISABLED
self.btn_merge.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
r +=1
self.btn_reset = tk.Button(b,
text="Reset",
underline=0,
command = self.switch,
bg='#5D1725',
bd=0,
fg='white')
self.btn_reset.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
b.grid(row=0, column=1, sticky=tk.N+tk.W+tk.S+tk.E)
def on_import(self, evt=None):
self.switch()
#simulate some import
self.after(5000, self.switch())
def switch(self,):
state = self.btn_import["state"]
if state == tk.ACTIVE:
self.btn_import["state"] = tk.DISABLED
self.btn_ocr["state"] = tk.NORMAL
self.btn_merge["state"] = tk.NORMAL
else:
self.btn_import["state"] = tk.NORMAL
self.btn_ocr["state"] = tk.DISABLED
self.btn_merge["state"] = tk.DISABLED
def on_ocr(self, evt=None):
state = self.btn_ocr["state"]
print ("ocr button state is {0}".format(state))
def on_merge(self, evt=None):
state = self.btn_merge["state"]
print ("merge button state is {0}".format(state))
def on_close(self, evt=None):
self.parent.on_exit()
class App(tk.Tk):
"""Main Application start here"""
def __init__(self, *args, **kwargs):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_style()
self.set_title(kwargs['title'])
Main(self, *args, **kwargs)
def set_style(self):
self.style = ttk.Style()
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
self.style.theme_use("clam")
def set_title(self, title):
s = "{0}".format('Simple App')
self.title(s)
def on_exit(self):
"""Close all"""
if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self):
self.destroy()
def main():
args = []
for i in sys.argv:
args.append(i)
kwargs = {"style":"clam", "title":"Simple App",}
app = App(*args, **kwargs)
app.mainloop()
if __name__ == '__main__':
main()

How to retrieve value from selected radiobutton after root.mainloop()?

I am looking to write a pop-up window which asks the user to select a specific option, and if the option does not exist, to add it. However, I am having trouble retrieving the value of the selected option (that is, the key from the dict). My code --summarized-- so far:
import tkinter as tk
class Category():
def __init__(self):
self.categories = {1:"Coffee",2: "Tesco"}
def index(...):
# code ... #
root = tk.Tk()
v = tk.IntVar()
# I was thinking this would help:
def quit():
global irow
irow = v.get()
print("Irow is",irow)
root.quit()
tk.Label(root, text="Choose category:").pack()
for key, cat in self.categories.items():
tk.Radiobutton(root, text=cat, variable=v, value=key).pack()
tk.Radiobutton(root, text="Other", variable=v, value=key+1).pack()
# I want to add a text box here so user can add the "Other"
tk.Button(root, text="Close", command=quit)
irow = v.get()
print(irow)
root.mainloop()
print(irow)
# code which uses irow #
Executing this code yields:
0
Irow is 0
0
regardless of what button I select. I expect irow to be 2 is I were to select Tesco or 1 if I selected coffee (or 3 if I selected other). Any guidance would be very much appreciated.
Typically, mainloop only exits after all of the widgets have been destroyed. Therefore, you can't directly get values from the widgets at this point. The simplest solution is to save the value to a global variable, or an instance variable if you're using classes.
For example, in your case you could do this:
def quit():
self.irow = v.get()
root.quit()
Then, after mainloop exists you can access self.irow
...
root.mainloop()
print(self.irow)
Thank you to Bryan Oakley for the answer. I have made 4 changes so it runs on the Linux terminal: 1) I changed the class from Tk() to Toplevel();
2) I put the dialog into a class for cleanliness' sake; 3) I changed self.quit() to self.destroy(); & 4) I changed mainloop() to wait_window().
class AddCategory(tk.Toplevel):
def __init__(self, parent):
tk.Toplevel.__init__(self)
self.v = tk.IntVar()
self.parent = parent
tk.Label(self, text="Choose category:").pack()
for key, cat in self.parent.categories.items():
tk.Radiobutton(self, text=cat, variable=self.v, value=key).pack()
tk.Radiobutton(self, text="Other", variable=self.v, value=key+1).pack()
self.e = tk.Entry(self,text="")
self.e.pack()
tk.Button(self, text="Close", command=self.quit).pack()
def quit(self):
self.parent.key = self.v.get()
self.parent.cat = self.e.get()
print(self.v.get())
print(self.e.get())
self.destroy()
Note that parent is the class from which I am executing the "AddCategory" dialogue. I invoke it as follows:
class Category():
def __init__(self):
self.cat = self.key = self.desc = []
self.categories = {1:"Coffee",2: "Tesco"}
self.descriptions = {}
def index(...):
# code ... #
dialog = AddCategory(self) # I pass the parent onto the dialog
dialog.wait_window()
print(self.key)
print(self.cat)
This works because self.parent inside of AddCategory is a soft copy of parent.

Automatically switching checkboxes on tkinter

I would like to switch on the checkboxes (0, 2, 4) automatically with the click of a button. I have the following code. For some reason it dont work. Please help me.
from tkinter import *
class Error(Frame):
def Widgets(self):
for i in range(len(self.X)):
self.X[i] = Checkbutton(self, text="%d"%(i,))
self.X[i].grid(row=i, sticky=W)
self.X[i].configure(variable = ("var_%d"%(i,)))
self.button = Button(self, text = "set", command = self.test)
self.button.grid(row=5, sticky=W)
def test(self):
for i in range(len(self.X)):
if i == 0 or i == 2 or i == 4:
set (("var_%d"%(i,))) == 1
def __init__(self,initial):
super(Error,self).__init__(initial)
self.X = [{},{},{},{},{}]
self.grid()
self.Widgets()
Window = Tk()
Tool = Error(Window)
Window.mainloop()
The way to handle checkboxes is to associate each box with a variable which reflects wether the box is checked or not.
For an array of checkboxes it is convenient to store these variables in a list. The way I would do it is to create an empty list and then append variables as I go along.
In the function test() I use enumerate in the for-loop as this is the recommended way to generate an index of the list.
from tkinter import *
class Error(Frame):
def __init__(self, master):
super(Error,self).__init__(master)
self.box_list = [] # List to holld checbox variables
self.grid()
self.Widgets()
def Widgets(self):
for i in range(5):
var = BooleanVar() # Create variable to associate with box
cb = Checkbutton(self, text="%d"%(i,))
cb.grid(row=i, sticky=W)
cb.configure(variable=var)
self.box_list.append(var) # Append checkbox variable to list
self.button = Button(self, text = "set", command = self.test)
self.button.grid(row=5, sticky=W)
def test(self):
for i, var in enumerate(self.box_list):
if i == 0 or i == 2 or i == 4:
var.set(True)
Window = Tk()
Tool = Error(Window)
Window.mainloop()

python tkinter.Radiobutton can't get value

I'm writing a python code with tkinter (python3) but I have some problems. I have two classes _MainScreen and _RegisterScreen (this last is nested in _MainScreen). In _RegisterScreen I had implemented a simple question with tkinter.Radiobutton (choose your sex: male, female). The idea is to catch the user selection, but when I run the script, the value assigned to the variable is empty (""). However, if I run the class _RegisterScreen alone, it works. I hope you can show me where is my error. Thanks in advance.
Here is an abstraction (32 lines) of my code (250 lines):
import tkinter
class _MainScreen(tkinter.Frame):
def __init__(self):
self.root = tkinter.Tk()
self.new_account(self.root)
self.root.mainloop()
def new_account(self, frame):
tkinter.Button(frame, text="Create new account",
command=self.create_new_account).pack(anchor="center", pady=(0,15))
def create_new_account(self):
_RegisterScreen()
class _RegisterScreen(tkinter.Frame):
def __init__(self):
self.root = tkinter.Tk()
tkinter.Label(self.root, text="Sex").grid(row=1, padx=(0,10), sticky="w")
self.sex_option = tkinter.StringVar()
tkinter.Radiobutton(self.root, text="Male", variable=self.sex_option,
value="Male", command=self._selected).grid(row=1, column=1)
tkinter.Radiobutton(self.root, text="Female", variable=self.sex_option,
value="Female", command=self._selected).grid(row=1, column=2)
tkinter.Button(self.root, text="Submit",
command=self._login_btn_clickked).grid(row=3, columnspan=4, pady=20)
self.root.mainloop()
def _login_btn_clickked(self):
sex = self._selected()
print(sex)
def _selected(self):
return self.sex_option.get()
_MainScreen()
#_RegisterScreen() # comment the above line and uncomment this line
# to test the _RegisterScreen object alone.
After doing some research on how tkinter's RadioButton widget works, I believe I have a solution to your problem:
Here's your new _RegisterScreen function:
class _RegisterScreen(tkinter.Frame):
def __init__(self):
self.gender = "NA" #Variable to be changed upon user selection
self.root = tkinter.Tk()
tkinter.Label(self.root, text="Sex").grid(row=1, padx=(0,10), sticky="w")
self.sex_option = tkinter.StringVar()
#This Radiobutton runs the setMale function when pressed
tkinter.Radiobutton(self.root, text="Male", variable=self.sex_option,
value="Male", command=self.setMale).grid(row=1, column=1)
#This Radiobutton runs the setFemale function when pressed
tkinter.Radiobutton(self.root, text="Female", variable=self.sex_option,
value="Female", command=self.setFemale).grid(row=1, column=2)
tkinter.Button(self.root, text="Submit",
command=self._login_btn_clickked).grid(row=3, columnspan=4, pady=20)
self.root.mainloop()
def _login_btn_clickked(self):
sex = self.gender #gets the value stored in gender and assigns it to sex
print(sex)
def setMale(self):
self.gender="Male" #sets gender to Male
def setFemale(self):
self.gender="Female" #sets gender to Female
Ultimately, you want to run 2 separate functions for either RadioButton.
When the Male Radiobutton gets clicked, it runs the setMale function.
When the Female Radiobutton gets clicked, it runs the setFemale function.
I believe you were confused about what RadioButton's variable and value attributes actually are (as was I before looking further into this).
I learned more about what those those do in this video: https://www.youtube.com/watch?v=XNF-y0QFNcM
I hope this helps! ~Gunner

Resources