Is there a way to gray out (disable) a tkinter Frame? - python

I want to create a GUI in tkinter with two Frames, and have the bottom Frame grayed out until some event happens.
Below is some example code:
from tkinter import *
from tkinter import ttk
def enable():
frame2.state(statespec='enabled') #Causes error
root = Tk()
#Creates top frame
frame1 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame1.grid(column=0, row=0, padx=10, pady=10)
button2 = ttk.Button(frame1, text="This enables bottom frame", command=enable)
button2.pack()
#Creates bottom frame
frame2 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame2.grid(column=0, row=1, padx=10, pady=10)
frame2.state(statespec='disabled') #Causes error
entry = ttk.Entry(frame2)
entry.pack()
button2 = ttk.Button(frame2, text="button")
button2.pack()
root.mainloop()
Is this possible without having to individually gray out all of the frame2's widgets?
I'm using Tkinter 8.5 and Python 3.3.

Not sure how elegant it is, but I found a solution by adding
for child in frame2.winfo_children():
child.configure(state='disable')
which loops through and disables each of frame2's children, and by changing enable() to essentially reverse this with
def enable(childList):
for child in childList:
child.configure(state='enable')
Furthermore, I removed frame2.state(statespec='disabled') as this doesn't do what I need and throws an error besides.
Here's the complete code:
from tkinter import *
from tkinter import ttk
def enable(childList):
for child in childList:
child.configure(state='enable')
root = Tk()
#Creates top frame
frame1 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame1.grid(column=0, row=0, padx=10, pady=10)
button2 = ttk.Button(frame1, text="This enables bottom frame",
command=lambda: enable(frame2.winfo_children()))
button2.pack()
#Creates bottom frame
frame2 = ttk.LabelFrame(root, padding=(10,10,10,10))
frame2.grid(column=0, row=1, padx=10, pady=10)
entry = ttk.Entry(frame2)
entry.pack()
button2 = ttk.Button(frame2, text="button")
button2.pack()
for child in frame2.winfo_children():
child.configure(state='disable')
root.mainloop()

Based on #big Sharpie solution here are 2 generic functions that can disable and enable back a hierarchy of widget (frames "included"). Frame do not support the state setter.
def disableChildren(parent):
for child in parent.winfo_children():
wtype = child.winfo_class()
if wtype not in ('Frame','Labelframe','TFrame','TLabelframe'):
child.configure(state='disable')
else:
disableChildren(child)
def enableChildren(parent):
for child in parent.winfo_children():
wtype = child.winfo_class()
print (wtype)
if wtype not in ('Frame','Labelframe','TFrame','TLabelframe'):
child.configure(state='normal')
else:
enableChildren(child)

I think you can simply hide the whole frame at once.
If used grid
frame2.grid_forget()
If used pack
frame2.pack_forget()
In your case the function would be
def disable():
frame2.pack_forget()
To enable again
def enable():
frame2.pack()
grid_forget() or pack_forget() can be used for almost all tkinter widgets
this is a simple way and reduces the length of your code, I'm sure it works

Related

Tkinter: Too much space between label and button frame

I'm pretty new to Tkinter and I build a little window with different widgets.
My Code looks like this:
import tkinter as tk
from tkinter import ttk
class Application(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.master = master
self.master.geometry("800x600")
self.master.title("Tkinter Sandbox")
self.master.grid_rowconfigure(0, weight=1)
self.master.grid_columnconfigure(1, weight=1)
self._create_left_frame()
self._create_button_bar()
self._create_label_frame()
def _create_left_frame(self):
frame = tk.Frame(self.master, bg="red")
tree_view = ttk.Treeview(frame)
tree_view.column("#0", stretch=tk.NO)
tree_view.heading("#0", text="Treeview")
tree_view.pack(fill=tk.Y, expand=1)
frame.grid(row=0, column=0, rowspan=2, sticky=tk.N + tk.S)
def _create_button_bar(self):
frame = tk.Frame(self.master, bg="blue")
button_run_single = tk.Button(frame, text="Button 1")
button_run_all = tk.Button(frame, text="Button 2")
button_details = tk.Button(frame, text="Button 3")
button_run_single.grid(row=0, column=0)
button_run_all.grid(row=0, column=1, padx=(35, 35))
button_details.grid(row=0, column=2)
frame.grid(row=0, column=1, sticky=tk.N)
def _create_label_frame(self):
frame = tk.Frame(self.master, bg="blue")
name_label = tk.Label(frame, text="Label 1")
performance_label = tk.Label(frame, text="Label 2")
name_entry = tk.Entry(frame)
performance_entry = tk.Entry(frame)
name_label.grid(row=0, column=0)
name_entry.grid(row=0, column=1)
performance_label.grid(row=1, column=0)
performance_entry.grid(row=1, column=1)
frame.grid(row=1, column=1)
if __name__ == '__main__':
root = tk.Tk()
app = Application(root)
app.mainloop()
Between the three buttons and the label + entry frame is a huge space. I want the button and label + entry frame right under each other, without the huge space but the treeview should also expand vertically over the whole application window.
I think the problem might be my row and column configuration but I don't know how to solve this problem.
The way you've structured your code makes it hard to see the problem. As a good general rule of thumb, all calls to grid or pack for widgets within a single parent should be in one place. Otherwise, you create dependencies between functions that are hard to see and understand.
I recommend having each of your helper functions return the frame rather than calling grid on the frame. That way you give control to Application.__init__ for the layout of the main sections of the window.
For example:
left_frame = self._create_left_frame()
button_bar = self._create_button_bar()
label_frame = self._create_label_frame()
left_frame.pack(side="left", fill="y")
button_bar.pack(side="top", fill="x")
label_frame.pack(side="top", fill="both", expand=True)
I used pack here because it requires less code than grid for this type of layout. However, if you choose to switch to grid, or wish to add more widgets to the root window later, you only have to modify this one function rather than modify the grid calls in multiple functions.
Note: this requires that your functions each do return frame to pass the frame back to the __init__ method. You also need to remove frame.grid from each of your helper functions.
With just that simple change you end up with the button bar and label/entry combinations at the top of the section on the right. In the following screenshot I changed the background of the button_bar to green so you can see that it fills the top of the right side of the UI.
You need to change line
self.master.grid_rowconfigure(0, weight=1)
to
self.master.grid_rowconfigure(1, weight=1)
so that the second row takes all the space. Then you need to stick widgets from the label frame to its top by adding sticky parameter to the grid call in _create_label_frame:
frame.grid(row=1, column=1, sticky=tk.N)
I prefer to use the Pack Function since it gives a more open window - its easy to configure. When you use Pack() you can use labels with no text and just spaces to create a spacer, by doing this you won't run into the problem your facing.

i want to align my code in a perfect order of widgets, so that ui look neat and clean

I want a neat and clean UI for my program it's working well but UI is terribly bad. Can anyone help me to fix this?
I am trying to use grid and place, but due to lack of knowledge of python. I am unable to do it.
#wap to demonstrate use of check button
import tkinter
from tkinter import messagebox
def pd():
if (var1.get())==1:
l1.config(text="You Have Selected Pendrive")
elif (var1.get())==0:
l1.config(text="")
def mcard():
if (var2.get())==1:
l2.config(text="You Have Selected Memory card")
elif (var2.get())==0:
l2.config(text="")
def hdd():
if (var3.get())==1:
l3.config(text="You Have Selected HDD")
elif (var3.get())==0:
l3.config(text="")
def per():
print("Successfully compiled")
msgbox=tkinter.messagebox.askquestion("Closing program","Are you sure?",)
if msgbox=="yes":
win.destroy()
else:
tkinter.messagebox.showinfo('Return','You will now return to the application screen')
win=tkinter.Tk()
var1=tkinter.IntVar()
var2=tkinter.IntVar()
var3=tkinter.IntVar()
win.geometry("500x500+0+0")
cb1=tkinter.Checkbutton(text="Pendrive",height=2,width=15,variable=var1,font=5,cursor="dot",bg="grey",anchor="w",command=pd)
cb2=tkinter.Checkbutton(text="Memory Card",height=2,width=15,variable=var2,font=5,cursor="dot",bg="grey",anchor="w",command=mcard)
cb3=tkinter.Checkbutton(text="HDD",height=2,width=15,variable=var3,font=5,cursor="dot",bg="grey",anchor="w",command=hdd)
b1=tkinter.Button(text="Submit",height=2,width=15,command=per)
# from this line problem begins with UI.
l1=tkinter.Label(height=2,width=30)
l2=tkinter.Label(height=2,width=30)
l3=tkinter.Label(height=2,width=30)
cb1.grid(column=0,row=1)
cb2.grid(column=0,row=2)
cb3.grid(column=0,row=3)
l1.place(x=40,y=300)
l2.place(x=40,y=350)
l3.place(x=40,y=400)
b1.place(x=125,y=450)
win.mainloop()
Actual results are different than I expected it to be. the widgets are not aligned well(I want them to centered)and there isn't enough padding between them( there should be enough space so that it won't look messy ).
If you want better aligned then rather don't use width, height, place() but pack() and grid() and its options. If you use wrong option in pack(), grid() then you will see error message with all available options for pack or grid. pack and grid use different options.
More in documentation: place(), pack(), grid()
import tkinter as tk
from tkinter import messagebox
def pd():
if var1.get():
l1.config(text="You Have Selected Pendrive")
else:
l1.config(text="")
def mcard():
if var2.get():
l2.config(text="You Have Selected Memory card")
else:
l2.config(text="")
def hdd():
if var3.get():
l3.config(text="You Have Selected HDD")
else:
l3.config(text="")
def per():
print("Successfully compiled")
msgbox = messagebox.askquestion("Closing program","Are you sure?",)
if msgbox == "yes":
win.destroy()
else:
messagebox.showinfo('Return', 'You will now return to the application screen')
win = tk.Tk()
win.geometry("500x500+0+0")
var1 = tk.IntVar()
var2 = tk.IntVar()
var3 = tk.IntVar()
cb1 = tk.Checkbutton(win, text="Pendrive", variable=var1, font=5, cursor="dot", bg="grey", anchor="w", command=pd)
cb2 = tk.Checkbutton(win, text="Memory Card", variable=var2, font=5, cursor="dot",bg="grey", anchor="w", command=mcard)
cb3 = tk.Checkbutton(win, text="HDD", variable=var3, font=5, cursor="dot", bg="grey", anchor="w", command=hdd)
cb1.pack(ipadx=10, ipady=10, fill='both', expand=True)
cb2.pack(ipadx=10, ipady=10, fill='both', expand=True)
cb3.pack(ipadx=10, ipady=10, fill='both', expand=True)
l1 = tk.Label(win, background='#bbbbbb')
l2 = tk.Label(win, background='#cccccc')
l3 = tk.Label(win, background='#bbbbbb')
l1.pack(ipadx=10, ipady=10, fill='both', expand=True)
l2.pack(ipadx=10, ipady=10, fill='both', expand=True)
l3.pack(ipadx=10, ipady=10, fill='both', expand=True)
b1 = tk.Button(win, text="Submit", command=per)
b1.pack(side='bottom', ipadx=10, ipady=10, fill='both', expand=True)
win.mainloop()

Checkbox always reads 0 in popup window - Tkinter

I have a GUI using Tkinter, it has a main screen and then when you press a button a popup window appears, where you select a checkbutton and then a email will get sent to you.
Not matter what I do, I cannot read the value of the checkbutton as 1 or True it always = 0 or False.
This is my code:
import tkinter as tk
from tkinter import *
import time
root = tk.Tk()
root.title('Status')
CheckVar1 = IntVar()
def email():
class PopUp(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
popup = tk.Toplevel(self, background='gray20')
popup.wm_title("EMAIL")
self.withdraw()
popup.tkraise(self)
topframe = Frame(popup, background='gray20')
topframe.grid(column=0, row=0)
bottomframe = Frame(popup, background='gray20')
bottomframe.grid(column=0, row=1)
self.c1 = tk.Checkbutton(topframe, text="Current", variable=CheckVar1, onvalue=1, offvalue=0, height=2, width=15, background='gray20', foreground='snow', selectcolor='gray35', activebackground='gray23', activeforeground='snow')
self.c1.pack(side="left", fill="x", anchor=NW)
label = tk.Label(bottomframe, text="Please Enter Email Address", background='gray20', foreground='snow')
label.pack(side="left", anchor=SW, fill="x", pady=10, padx=10)
self.entry = tk.Entry(bottomframe, bd=5, width=35, background='gray35', foreground='snow')
self.entry.pack(side="left", anchor=S, fill="x", pady=10, padx=10)
self.button = tk.Button(bottomframe, text="OK", command=self.on_button, background='gray20', foreground='snow')
self.button.pack(side="left", anchor=SE, padx=10, pady=10, fill="x")
def on_button(self):
address = self.entry.get()
print(address)
state = CheckVar1.get()
print (state)
time.sleep(2)
self.destroy()
app = PopUp()
app.update()
tk.Button(root,
text="EMAIL",
command=email,
background='gray15',
foreground='snow').pack(side=tk.BOTTOM, fill="both", anchor=N)
screen = tk.Canvas(root, width=400, height=475, background='gray15')
screen.pack(side = tk.BOTTOM, fill="both", expand=True)
def latest():
#Other code
root.after(300000, latest)
root.mainloop()
The popup works perfectly, and the email will print when entered but the value of checkbox is always 0.
I have tried:
CheckVar1 = tk.IntVar() - No success
self.CheckVar1 & self.CheckVar1.get() - No success
Removing self.withdraw() - No success
I only have one root.mainloop() in the script, I am using app.update() for the popup window because without this it will not open.
I have checked these existing questions for solution and none have helped:
Self.withdraw - Can't make tkinter checkbutton work normally when running as script
Self.CheckVar1 - TKInter checkbox variable is always 0
Only one instance of mainloop() - Python tkinter checkbutton value always equal to 0
I have also checked very similar questions but I wasn't going to post them all.
Any help is appreciated.
The problem is that you have two root windows. Each root window gets its own internal tcl interpreter, and the widgets and tkinter variables in one are completely invisible to the other. You're creating the IntVar in the first root window, and then trying to associate it with a checkbutton in a second root window. This cannot work. You should always only have a single instance of Tk in a tkinter program.
because of variable scope
try to put CheckVar1 = IntVar() inside the class
use it with self like this
self.CheckVar1 = tk.IntVar() # object of int
self.CheckVar1.set(1) # set value
variable=self.CheckVar1 # passing to the checkbutton as parameter
state = self.CheckVar1.get() # getting value

Indeterminate Progress bar while function is running

I have a GUI which has two buttons and a progressbar stacked on a single column. Each button calls a different function which takes some time to execute. I want the progress bar to move when someone clicks any of the two buttons and keep moving (indeterminately) until the function finishes and then stop. I know I need to use multi-threading but I can't seem to get the code right!
Code
from tkinter import Tk
import time
from tkinter import *
from tkinter import Button
from tkinter import Frame
from tkinter import ttk
import threading
def sample_function():
time.sleep(2) # SAMPLE FUNCTION BEING CALLED
def prepare_clicked():
sample_function()
def social_clicked():
sample_function()
def anomaly_clicked():
sample_function()
window = Toplevel() # Tried using Tk but I am using image to design each buttons through the button config in my actual code and tk throws error
topFrame = Frame(window)
topFrame.pack()
prepare_btn = Button(topFrame, command=prepare_clicked,text='Button1')
anomaly_btn = Button(topFrame,command=anomaly_clicked,text='Button2')
social_btn = Button(topFrame, command=social_clicked,text='Button3')
processing_bar = ttk.Progressbar(topFrame, orient='horizontal', mode='indeterminate')
window.rowconfigure((0,1), weight=1) # make buttons stretch when
window.columnconfigure((0,3), weight=1) # when window is resized
prepare_btn.grid(row=0, column=1, columnspan=1, sticky='EWNS')
anomaly_btn.grid(row=1, column=1, columnspan=1, sticky='EWNS')
social_btn.grid(row=2, column=1, columnspan=1, sticky='EWNS')
processing_bar.grid(row=3, column=1, columnspan=1, sticky='EWNS')
window.mainloop()
I've added threading to your code. I assume you don't want any of the buttons to be pressable while a function is in progress. If you don't need that, just get rid of the for loops in run_function that change btn['state']. I've also fixed the row & column configuration code so that the widgets expand & contract when the user resizes the window. And I got rid of the evil "star" import.
import tkinter as tk
from tkinter import ttk
import time
from threading import Thread
def sample_function():
time.sleep(2)
def run_function(name, func):
# Disable all buttons
for btn in buttons:
btn['state'] = 'disabled'
processing_bar.start(interval=10)
print(name, 'started')
func()
processing_bar.stop()
print(name, 'stopped')
# Enable all buttons
for btn in buttons:
btn['state'] = 'normal'
def run_thread(name, func):
Thread(target=run_function, args=(name, func)).start()
def prepare_clicked():
run_thread('prepare', sample_function)
def social_clicked():
run_thread('social', sample_function)
def anomaly_clicked():
run_thread('anomaly', sample_function)
window = tk.Tk()
#window = tk.Toplevel()
topFrame = tk.Frame(window)
# Tell the Frame to fill the whole window
topFrame.pack(fill=tk.BOTH, expand=1)
# Make the Frame grid contents expand & contract with the window
topFrame.columnconfigure(0, weight=1)
for i in range(4):
topFrame.rowconfigure(i, weight=1)
prepare_btn = tk.Button(topFrame, command=prepare_clicked, text='Button1')
anomaly_btn = tk.Button(topFrame,command=anomaly_clicked, text='Button2')
social_btn = tk.Button(topFrame, command=social_clicked, text='Button3')
buttons = [prepare_btn, anomaly_btn, social_btn]
processing_bar = ttk.Progressbar(topFrame, orient='horizontal', mode='indeterminate')
prepare_btn.grid(row=0, column=0, columnspan=1, sticky='EWNS')
anomaly_btn.grid(row=1, column=0, columnspan=1, sticky='EWNS')
social_btn.grid(row=2, column=0, columnspan=1, sticky='EWNS')
processing_bar.grid(row=3, column=0, columnspan=1, sticky='EWNS')
window.mainloop()
Update
Here's the new improved version, with an 'All' button that runs all the functions, in order. Enjoy!
import tkinter as tk
from tkinter import ttk
import time
from threading import Thread
def prepare_func():
print('prepare started')
time.sleep(2)
print('prepare stopped')
def anomaly_func():
print('anomaly started')
time.sleep(2)
print('anomaly stopped')
def social_func():
print('social started')
time.sleep(2)
print('social stopped')
def all_func():
print('all started')
show_and_run(prepare_func, buttons['Prepare'])
show_and_run(anomaly_func, buttons['Anomaly'])
show_and_run(social_func, buttons['Social'])
print('all stopped')
def show_and_run(func, btn):
# Save current button color and change it to green
oldcolor = btn['bg']
btn['bg'] = 'green'
# Call the function
func()
# Restore original button color
btn['bg'] = oldcolor
def run_function(func, btn):
# Disable all buttons
for b in buttons.values():
b['state'] = 'disabled'
processing_bar.start(interval=10)
show_and_run(func, btn)
processing_bar.stop()
# Enable all buttons
for b in buttons.values():
b['state'] = 'normal'
def clicked(func, btn):
Thread(target=run_function, args=(func, btn)).start()
window = tk.Tk()
#window = tk.Toplevel()
topFrame = tk.Frame(window)
# Tell the Frame to fill the whole window
topFrame.pack(fill=tk.BOTH, expand=1)
# Make the Frame grid contents expand & contract with the window
topFrame.columnconfigure(0, weight=1)
for i in range(4):
topFrame.rowconfigure(i, weight=1)
button_data = (
('Prepare', prepare_func),
('Anomaly', anomaly_func),
('Social', social_func),
('All', all_func),
)
# Make all the buttons and save them in a dict
buttons = {}
for row, (name, func) in enumerate(button_data):
btn = tk.Button(topFrame, text=name)
btn.config(command=lambda f=func, b=btn: clicked(f, b))
btn.grid(row=row, column=0, columnspan=1, sticky='EWNS')
buttons[name] = btn
row += 1
processing_bar = ttk.Progressbar(topFrame,
orient='horizontal', mode='indeterminate')
processing_bar.grid(row=row, column=0, columnspan=1, sticky='EWNS')
window.mainloop()

Add an image to Tkinter Entry

Using tkinter, I am trying to display an image inside the border of an entry widget.
I tried to search in Google but I came with no success, someone have an idea how to do that?
There is no feature or attribute to allow an image inside the boundary of a Entry widget. However, you can simulate it pretty easily by putting an image and an entry widget inside a frame, remove the border from the entry widget, and make sure the entry widget and frame have the same background color.
Example:
import Tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, background="gray")
frame = tk.Frame(background="white", borderwidth=1, relief="sunken",
highlightthickness=1)
frame.pack(side="top", fill="x", padx=4, pady=4)
entry = tk.Entry(frame, borderwidth=0, highlightthickness=0, background="white")
entry.image = tk.PhotoImage(data=cancelImageData)
imageLabel = tk.Label(frame, image=entry.image)
imageLabel.pack(side="right", fill="y")
entry.pack(side="left", fill="both", expand=True)
cancelImageData = '''
R0lGODlhEAAQAPcAAAAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr
/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCq
mQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMA
MzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV
/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPV
mTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYr
M2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA
/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/
mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlV
M5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq
/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswA
mcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyA
M8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV
/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8r
mf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+q
M/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP//
/wAAAAAAAAAAAAAAACH5BAEAAPwALAAAAAAQABAAAAiWAPcJHEiwYEFpCBMiNLhP
WjZz4CB+A5dN2sGH2TJm+7ax4kCHEOlx3EgPHEeLDc1loydwokB6G1EJlEYRHMt6
+1hW/IaSpreN+/ThzIYq5kyKGffV07ePpzSeMzl+UypU6aunMhtSdCcwI0t606A2
3PjN3VVXK2NO+/iKIzZp0xB+Q4Xt4re7te4WZSgNVV+EfhkKLhgQADs=
'''
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()

Categories

Resources