Another Collapsible Label Frame

JHJL Here is a Snidget to create a collapsible label frame. It allows the user to click the frame's label to collapse/expand the frame (ie hide its contents and recover most of the space). The embedded child site frame can be floated into its own toplevel (you might want to use a key binding for this...)

 package require snit
 package require Tk
 package require Ttk

 snit::widget CollapsibleLabelFrame {

    #
    # We are basically wrapping a ttk::labelframe by attaching a
    # label and embedding a child site frame.
    #
    # The label is used so that we can bind to it to determine
    # when the user clicks it
    #
    # The embedded child site frame is a plain Tk frame (not ttk::frame)
    # because (for my application) one cannot use wm manage on a
    # ttk::frame
    #
    # Initial state 'open' or 'closed'
    #
    option -state \
                -type {
                    snit::enum -values {open closed}
                } -default open \
                -configuremethod CFG-ChangeState \
                -readonly 1
    
    #
    # Text to display
    #
    option -text  -configuremethod CFG-LabelText -default ""

    option -floattitle -default "" 
   
    component lframe
    component label
    component frame

    delegate option -labelfont to label as -font
    delegate option -labelfg to label as -foreground
    
    #
    # I'm using symbol characters within the font so that I don't
    # have to worry colour. The downside is that the developer
    # can choose a font that doesn't have these symbols...
    #
    # Because the attached label is a ttk::label, one could use a
    # compound label + text and just manage the icon, but then
    # what colour icon to use and what style for the current theme
    #
    
    variable open_symbol    "\u25bc"
    variable closed_symbol  "\u25ba"
    variable text           ""
    variable ginfo          [list]
    variable floatheight    100
    variable floatwidth     300
    
    constructor {args} {
        
        install lframe using ttk::labelframe $win.lframe
        install label using ttk::label $lframe.label \
            -textvariable [myvar text] \
            -font {arial 11 bold} \
            -foreground blue
        
        $lframe configure -labelwidget $label
 
        install frame using frame $lframe.frame -borderwidth 0

        bind $label <1> [mymethod toggle]
        
        grid $frame -sticky news
        grid columnconfigure $frame 0 -weight 1
        grid rowconfigure $frame 0 -weight 1

        grid $lframe -sticky news
        grid columnconfigure $lframe 0 -weight 1
        grid rowconfigure $lframe 0 -weight 1

        grid columnconfigure $win 0 -weight 1
        grid rowconfigure $win 0 -weight 1

        $self configurelist $args               
    }
    

    delegate method * to lframe
    delegate option * to lframe
    
    method CFG-LabelText {option value} {
        set options($option) $value
        $self UpdateLabel
    }

    method CFG-ChangeState {option value} {
        
        $self toggle
        set options($option) $value

    }

    method UpdateLabel {} {
        set sym [expr {$options(-state) eq "open"
                ? $open_symbol
                : $closed_symbol }]
        
        set text "$sym $options(-text)"
    }

    method getframe {} {

        # get the child site frame

        return $frame
    }
    
    method open {} {
        
        $self CFG-ChangeState -state open
    }
    
    method close {} {
        
        $self CFG-ChangeState -state closed
    }
    
    method isfloating {} {
        expr {$frame eq [winfo toplevel $frame]}
    }
    
    method toggle {} {
        #
        # Open or Close the frame
        #
        if {$options(-state) eq "open"} {
            
            #
            # grid remove remembers the slaves grid options :)
            #
            grid remove $frame
            #
            # Collapse the labelframe allowing a ltiile space for the
            # label
            #
            set lh [winfo height $label]
            $lframe configure -height [expr {$lh +  2}]
            
            set options(-state) closed
            
        } else {
            #
            # Don't bother if the frame has been floated into a toplevel
            #
            if {![$self isfloating]} {
                grid $frame            
                set options(-state) open
            }
        }
                
        $self UpdateLabel
    }
    
    method float {} {
        #
        # Float the embedded frame as a toplevel
        # or reparent back into its container
        #
    
        #
        # Check if frame is masquerading as a Toplevel
        # (wm manage'd frames keep their names
        #
        if { ![$self isfloating] } {
            #
            # Not currently a toplevel, remem
            #
            
            # I'm not sure whether wm manage does a 'remove' or a 'forget'
            # so I note the current grid options for the frame
            #
            set ginfo [grid info $frame]

            #
            # Remove from paned window and then get Window Manager
            # to manage the frame
            # 
            wm manage $frame
            wm title $frame $options(-floattitle)

            #
            # Make it the same size as the labelframe
            #
            set height [winfo height $lframe]
            set width [winfo width $lframe]
             
            wm geometry $frame ${width}x${height}
            
            #
            # Capture close event on new Toplevel and reparent
            #
            wm protocol $frame WM_DELETE_WINDOW [mymethod float]
            #
            # Collapse the labelframe to recover the space vertical
            #
            $self close
 
        } else {
            #
            # Reparent
            #
            wm forget $frame
            
            #
            # Open the containing labelframe
            # and manage using the saved settings
            #
            $self open
            
            grid $frame {*}$ginfo
        }
    }
 }

 if {0} {
    catch {destroy .clf}
    set clf [CollapsibleLabelFrame .clf \
        -text "Click Me" \
        -labelfg blue \
        -floattitle "Whoo Hoo" \
    ]
    
    pack $clf -fill both -expand 0
    
    set f [$clf getframe]
    set b [ttk::button $f.b -text "Float Me" -command [list $clf float]]
    pack $b
 }