2

I'm trying to pack (or place) a widget which is child of the root toplevel . inside another toplevel, that is a child of . itself. That is,

% toplevel .tl
.tl
% frame .f
.f
% pack .f -in .tl
can't pack .f inside .tl

However, I've found this code almost works:

% frame .tl
.tl
% frame .f
.f
% pack .f -in .tl
% wm manage .tl

I said almost, because .f is not visible. It's a bit strange, because if I put a button inside .f, such as

button .f.b -text FooBar
pack .f.b

I see the empty space reserved by the geometry manager, but no widget is visible.

I'm sure I'm doing something wrong, but I don't know what and why, and the pack, grid and place man pages don't help.


Edit: some details about what I'm doing

I'm trying to build a snit widget which automates some toplevel creation stuff. One thing I usually do is putting a ttk::frame inside every toplevel I create, and managing it using a pack ... -fill both -expand true command.

My snidget should always do it, but I'd like to hide it from the user perspective, so that any change to the implementation wouldn't break existing code.

The simple way is this

snit::widget Toplevel {
    hulltype toplevel
    component f

    constructor {args} {
        set f [ttk::frame $self.f -padding 2]
        pack $f -fill both -expand 1

        $self configurelist $args
    }
}

but the user must know about the f component, and create other widgets as children of it.

So, I tried another solution: I use a ttk::frame widget as hull type, then build a sibling toplevel of the hull, and try to put the hull inside the toplevel.

The code I tried is similar to the following:

snit::widget Toplevel {
    hulltype ttk::frame
    component tl

    constructor {args} {
        set segments [split $self .]
        set wname [join [lreplace $segments end end _[lindex $segments end]] .]

        set tl [frame $wname -width 100 -height 100]
        pack $self -in $tl -fill both -expand 1
        wm manage $tl

        $self configurelist $args
    }
}

If it would work as expected, the user could write something like this:

% Toplevel .t
.t
% button .t.b -text Foobar
.t.b
% pack .t.b

and would get a button inside the toplevel .t build using the snidget.

Marco Pallante
  • 3,923
  • 1
  • 21
  • 26

2 Answers2

3

Widgets except for toplevels are arranged in a strict hierarchy of containment; the widget .foo contains the widget .foo.bar, which in turn contains .foo.bar.grill, etc. Toplevel widgets are actually all children of the root window.

There are exactly two things you can do to change this.

  1. You can use wm manage (requires 8.5 or later) to turn a standard frame widget into a toplevel, and wm forget to reverse this (you'll have to also then re-pack/re-grid it too). (I don't think this is supported on OSX/Aqua.)

    frame .foo
    # Define its contents...
    wm manage .foo
    
    # Later...
    wm forget .foo
    pack .foo
    

    This is a useful pair of operations for making torn-off windows like toolbars. There's even a demo in the Tk widget demo.

  2. You can turn a frame (or toplevel) into a container widget at creation time (set the -container option to true in the creation options; it can't be done later). You can then get the ID of the window widget (with winfo id) and tell a toplevel at creation time to use that widget ID as its parent via the -use option.

    frame .foo -container 1
    set id [winfo id .foo]
    toplevel .bar -use $id
    

    Note that in this case, on Unix/X11 those IDs can be passed to other processes and used, and need not actually refer to windows created by Tk. (Some applications can also do the reverse, embedding themselves inside a window given by ID that was defined by Tk.) On Windows and OSX/Aqua, the IDs are only guaranteed to work within a single process.

What I don't know is whether the operation you want to perform can be done by either of these two possible operations; you're not quite clear about the higher-level perspective of what you're trying to do. They don't mix particularly well, as -container and -use are creation-time-only options, and are subsequently read-only, and you can't change the name hierarchy (that's totally fixed after creation). Really sophisticated stuff may require you to step back and formally define a model that you have multiple widget layouts being views of.

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
1

What you're trying to do isn't possible, Marco. All widgets must be children of their top-level containers.

This isn't my real name
  • 4,869
  • 3
  • 17
  • 30
  • Are you sure about that? I know I can reparent a toplevel inside a frame using the `-use` and `-container` options and the `winfo id` command; moreover, using the `-in` option of the `pack`/`place` geometry managers I can put a widget inside a sibling, and no parent-child relation is involved. E.g. `% frame .f1` `% frame .f2 ;# this is a sibling of .f1` `% pack .f1` `% pack .f2 -in .f1` works. – Marco Pallante Aug 07 '13 at 17:16
  • Yes, I'm quite sure. Any widget that isn't itself a toplevel widget is only instantiated within the context of its top-level parent. You can only `pack .f2 -in .f1` when they both have the same toplevel window. – This isn't my real name Aug 07 '13 at 17:37