Is there a way to gray out (disable) a tkinter Frame?
Asked Answered
H

4

13

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.

Halogen answered 24/7, 2014 at 19:52 Comment(0)
H
20

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()
Halogen answered 24/7, 2014 at 20:22 Comment(4)
Your answer would be better if you described what you did. Otherwise we have to read line-by-line looking for differences.Alfons
@BryanOakley Fair point. My answer is now updated to make it clearer.Halogen
I had to change state='enable' to state='normal' in order to get it to work.Sherleysherline
@Big Sharpie thanks for this. very helpful... just a question... what if one the children also has children, is there a quick code to get all of them? thank youKriskrischer
D
9

Based on @big Sharpie's solution here are two generic functions that can disable and enable a hierarchy of widget (frames "included"). Frames 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)
Dinnage answered 16/4, 2020 at 14:56 Comment(2)
If using ttk you may want to include "TFrame" and "TLabelframe" when checking for wtype.Anthropomorphosis
@Anthropomorphosis Thank for the subjection. I updated the answerDinnage
N
0

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

Ng answered 10/12, 2020 at 13:44 Comment(2)
In my case, I have 3 successive frames that are loaded using pack into the root frame. If I do pack_forget() on the middle one and then pack() later. The middle frame ends up at the end.Blanca
This will not stop the frame from receiving input events, and if the user focuses on any element of the frame, they will still be able to "click" on that element with their keyboard once it's hidden.Calcutta
O
0

Based on @Jean-Marc Volle solution: I merged his two functions in a single function that takes a boolean argument, and created a function to enable a single widget that calls the first if the widget is a frame:

def enable_children(parent, enabled=True):
    for child in parent.winfo_children():
        wtype = child.winfo_class()
        print(wtype)
        if wtype not in ('Frame', 'Labelframe', 'TFrame', 'TLabelframe'):
            child.configure(state=tk.NORMAL if enabled else tk.DISABLED)
        else:
            enable_children(child, enabled)


def enable_widget(widget, enabled=True):
    wtype = widget.winfo_class()
    if wtype not in ('Frame', 'Labelframe', 'TFrame', 'TLabelframe'):
        widget.configure(state=tk.NORMAL if enabled else tk.DISABLED)
    else:
        enable_children(widget, enabled)

Use as follows: enable_widget(widget) to enable the widget enable_widget(widget, False) to disable it.

You might want to use it with a wrapper:

def enable_my_widget(enabled=True):
    enable_widget(my_widget, enabled)

An image is worth a thousand words: below are the picture of a single label, a frame and a frame containing two child frames:

disabled label

disabled frame

disabled frame with child frames

Orella answered 15/9, 2023 at 11:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.