TIP 518: Virtual Event when Last Child is not Managed any more

Login
Author:         Harald Oehlmann <oehhar@users.sourceforge.net>
State:          Final
Type:           Project
Vote:           Done
Created:        22-Sep-2018
Post-History:   
Keywords:       Tk
Tcl-Version:    8.7
Tk-Branch:      tip518-event-last-child-unmanaged
Vote-Summary:   Accepted 6/0/0
Votes-For:      DKF, BG, KBK, JN, FV, SL
Votes-Against:  none
Votes-Present:  none

Abstract

A frame-like widget has 1x1 required size if created. If children are added by pack/grid and the last children is unpacked/grid, the frame-like widget does not return to the 1x1 required size. Instead, it keeps the size of the last packed item. It should automatically or under control resize to the initial requested size of 1x1.

Rationale

A frame keeping a size without reason just feels like a bug and mostly leads to unwanted results.

Mostly, it looks just ugly, but there are critical use-cases, specially in scrolled frames.

When the BWidget autoscroll package is used, which displays scrollbars on demand, the scrollbars do not disappear if the contents is gone. And there is nothing, a programmer can do, as the Configure event does not fire on the scrolled frame widget.

Another example is the scrolledwindow example by Emiliano in ticket 2863003fff, where the solution 2 specific part may be removed (or is ignored).

A typical workaround is to configure the width/height manually after the last children was unmapped. Unfortunately, this fact may not be determined for example by scrolling widgets etc. An eventual Configure binding is not firing.

Within this TIP, a new virtual event <<NoManagedChild>> is fired to inform about no remaining childs.

Example

Here is an example to ilustrate the issue. It consisting of a simple scrolling megawidget. The megawidget exposes a frame where a user may pack or grid other widgets and the scrollbar is adjusted following the changing content. This works well when widgets are added or removed. Only removing the last client will not update the scrollbar. With the proposed patch applied, it will update the scrollbar also when the last user widget is removed.

Please paste the code below to a wish console or execute it. On startup it shows on the console:

requested frame height: 1

Then press the "+" button to add a user widget. The console output is:

+
requested frame height: 100

Technically, the frame ".c.f.i1" was packed into the client frame ".c.f". The client frame ".c.f" changes its requested size to hold the new child, which invokes the Convigure event and adjustes the scrolling region of the canvas. The new scrolling region is shown graphically by the scrollbar.

Then press the "-" button to remove the user widget. The console output is:

-

So, the child widget ".c.f.i1" is destroyed, but the frame ".c.f" does not rechange its requested size to 1x1 (initial value) but stays at 100x100 showing an empty plane. The scrollbar is not updated and the megawidget has no possibility to adjust that (expect additional user action to inform that the last child was removed).

One may also try to add two childs and to remove them. It gets clear, that the widget is resized on removel if it is not the last widget.

With the proposed patch applied, an additional event is fired the removal of the last widget would restore the initial frame size of 1x1 which would invoke the Configure event and the scrollbar would be adjusted.

wm geometry . 90x90
# Button to add box on scrolling canvas
set itemNo 0
pack [button .b1 -command newBox -text +] -side left -fill y
proc newBox {} {
    puts +
    incr ::itemNo
    pack  [frame .c.f.i$::itemNo -borderwidth 4 -relief raised -bg red -width 100 -height 100] -side top
}
# Button to remove box on scrolling canvas
pack [button .b2 -command removeBox -text -] -side left -fill y
proc removeBox {} {
    puts -
    if {$::itemNo == 0} {return}
    destroy .c.f.i$::itemNo
    incr ::itemNo -1
}

# This is the scrolling megawidget which exposes frame .c.f for users to pack or grid clients
# It has no knowledge, when the user adds or removes clients, e.g. when +/- is pressed
pack [scrollbar .s -command {.c yview}] -side right -fill y
pack [canvas .c -borderwidth 0 -yscrollcommand {.s set}] -side left -fill both
frame .c.f
.c create window 0 0 -window .c.f -anchor nw -tags win
proc frameConfigure {} {
    set y [winfo reqheight .c.f]
    puts "requested frame height: $y"
    .c configure -scrollregion [list 0 0 100 $y]
}
frameConfigure
bind .c.f <Configure> frameConfigure

Proposal

The proposal is to fire the new virtual event <<NoManagedChild>> if the last children is unpacked/ungridded/destroyed. A managing widget may bind to this event and do the resize of the widget or other appropriate action.

Here are additional lines for the example above.

proc frameNoChild {} {
    .c.f configure -height 1
    .c.f configure -height 0
    }
bind .c.f <<NoManagedChild>> frameNoChild

Reference Implementation

Emiliano has provided a ticket 2863003fff with the implementation in branch bug-d6b95ce492.

This solution is now continued with the tag "tip518-event-last-child-unmanaged".

Koen Dankart and Francois Vogel have worked on the solution of the similar tip474, which is available in branch tip-454. This information may eventually also be relevant.

Rejected Proposal

TIP 454 has proposed to set the size of the widget automatically to 1x1 (the initial size if no widget packed/gridded).

Please read the discussion within the TIP which led to withdraw the proposal.

Discussion

Why not use the event name <<Configure>> ?

This is a logical and elegant proposal.

TIP 454 was withdrawn due to:

  1. if the geometry manager is changed (ex: pack to grid), there is a flickering introduced.
  2. an additional Configure event was introduced which breaks present script.

To invoke <<Configure>> instead <<NoManagedChild>> would trigger incompatibility 2 of TIP454. Due to that, a new virtual event is proposed.

Copyright

This document has been placed in the public domain.