Version 12 of Centering a window

Updated 2002-07-02 15:27:42

A recurring question on comp.lang.tcl is, ``How can I center a toplevel window on the screen?''

The usual answer is to use the update command to wait for the window to be configured, and then use the [wm] command to adjust its geometry.

There is an alternative way to do it that doesn't involve the many pitfalls of update: bind to the <Configure> event and use that to notify the process that the window has been configured and it's time to compute the geometry. The code below gives the idea:


# KBK - 12 February 2001

    proc make_the_window {} {

        # Create the toplevel window

        toplevel .t
        grid [label .t.l -text "This window\nshould be centered."]
        grid [button .t.d -text Dismiss -command {destroy .t}]

        # Set things up to position the window when it is
        # configured.

        bind .t <Configure> {center_the_toplevel %W}

        return

    }

    proc center_the_toplevel { w } {

        # Callback on the <Configure> event for a toplevel
        # that should be centered on the screen

        # Make sure that we aren't configuring a child window

        if { [string equal $w [winfo toplevel $w]] } {

            # Calculate the desired geometry

            set width [winfo reqwidth $w]
            set height [winfo reqheight $w]    
            set x [expr { ( [winfo vrootwidth  $w] - $width  ) / 2 }]
            set y [expr { ( [winfo vrootheight $w] - $height ) / 2 }]

            # Hand the geometry off to the window manager

            wm geometry $w ${width}x${height}+${x}+${y}

            # Unbind <Configure> so that this procedure is
            # not called again when the window manager finishes
            # centering the window

            bind $w <Configure> {}

        }

        return
    }

    grid [button .b -text "Test" -command make_the_window] \
        [button .q -text "Quit" -command exit]






tclguy writes, "If you have 8.3, the ideal proc is defined in $tk_library/tk.tcl as ::tk::PlaceWindow. This is a private function, but you can copy it for your needs (or expect that it exists at least for 8.3)."


W. Rösler: I used the following, taken from the dialog.tcl file from the Tk distribution, which seems to work fine:

  proc center_window {w} {
    wm withdraw $w
    update idletasks
    set x [expr [winfo screenwidth $w]/2 - [winfo reqwidth $w]/2 \
          - [winfo vrootx [winfo parent $w]]]
    set y [expr [winfo screenheight $w]/2 - [winfo reqheight $w]/2 \
          - [winfo vrooty [winfo parent $w]]]
    wm geom $w +$x+$y
    wm deiconify $w
  }

Arjen Markus The following code is based partly on the above. It will bring up a "transient" window for displaying a logo, name whatever that eventually disappears. While the transient is visible, the main toplevel widget (.) is not.

  # Show a transient window, withdraw the usual window while that is visible
  #
  proc center_transient_window { w } {
     set width [winfo reqwidth $w]
     set height [winfo reqheight $w]
     set x [expr { ( [winfo vrootwidth  $w] - $width  ) / 2 }]
     set y [expr { ( [winfo vrootheight $w] - $height ) / 2 }]

     # Hand the geometry off to the window manager

     wm geometry $w ${width}x${height}+${x}+${y}
  }

  wm withdraw .
  toplevel .transient
  wm overrideredirect .transient 1
  wm transient        .transient
  center_transient_window .transient

  canvas   .transient.c
  pack     .transient.c -fill both
  .transient.c create rectangle 10 10 40 40 -fill green

  after 3000 {destroy .transient ; wm deiconify .}

Note: because the window is transient, it does not receive Configure events.


Arjen Markus Below is a full-featured proc for displaying a picture in the transient window (it does not delete the image though):

 # tkmisc.tcl --
 #    Package that implements various small Tk utilities
 #

 # tkmisc --
 #    Namespace for the commands
 #
 namespace eval ::tkmisc {
    namespace export showTransientWindow
 }

 # showTransientWindow
 #    Show a transient window, possibly with a bitmap (at start-up for
 #    instance)
 #
 # Arguments:
 #    time        Time it remains visible in seconds
 #    pictfile    Name of a picture file (may be empty)
 #    script      Script to be executed after the window has been created
 #                (optional)
 #
 # Return value:
 #    Widget name of the canvas created inside
 #
 # Note:
 #    If the name of the picture file is empty, the window is drawn at
 #    default size
 #    If a script is given, it should take "w" to mean the canvas in the
 #    transient window, for instance:
 #       showTransientWindow 3 {} {
 #          $w create text 10 10 -text "Hello World"
 #       }
 #
 proc ::tkmisc::showTransientWindow { time pictfile {script {}} } { 

    #
    # Withdraw the default toplevel window, create a transient one
    # (centred) with a default size or determined from the picture
    #
    set t .transient
    set w ${t}.c 

    wm withdraw .
    toplevel $t
    wm overrideredirect $t 1
    wm transient        $t

    if { $pictfile != "" } {
       set img [image create photo -file $pictfile]
       set height [image height $img]
       set width  [image width  $img]
       canvas $w -width $width -height $height
       $w create image 0 0 -anchor nw -image $img
    } else {
       canvas $w
       set width  [winfo reqwidth  $t]
       set height [winfo reqheight $t]
    }

    #
    # Centre the toplevel window
    #
    set x [expr { ( [winfo vrootwidth  $t] - $width  ) / 2 }]
    set y [expr { ( [winfo vrootheight $t] - $height ) / 2 }]

    # Hand the geometry off to the window manager

    wm geometry $t ${width}x${height}+${x}+${y}

    pack $w -fill both

    if { $script != {} } {
       eval $script
    }

    #
    # Now make it disappear in time
    # Note:
    #    The [list] command does not work for some reason.
    #after [expr {$time*1000}] [list destroy $t ; wm deiconify .]
    after [expr {$time*1000}] "destroy $t ; wm deiconify ."
 }

 #
 # Test code
 #
 if { [file tail [info script]] == [file tail $::argv0] } {
    namespace import ::tkmisc::*
    showTransientWindow 3 {} {$w create rectangle 10 10 30 30 -fill  green}
    after 4000 {
       showTransientWindow 3 "logoMed.gif"
    }
 }

EMJ

    #    The [list] command does not work for some reason.
    #after [expr {$time*1000}] [list destroy $t ; wm deiconify .]
    after [expr {$time*1000}] "destroy $t ; wm deiconify ."

But

    after [expr {$time*1000}] [list destroy $t \; wm deiconify .]

does work - "Think like the interpreter" (quote from somebody, can't find it right now).

Arjen Markus Yes, but the effect was rather surprising - there was no error message but the command (and seemingly) some earlier commands did not work.


DGP Uhhhh... if

    after [expr {$time*1000}] [list destroy $t \; wm deiconify .]

works, then you've got a buggy [list] in your Tcl library. [list] should quote the semicolon so that it isn't special to [eval]. I can't find such a buggy Tcl. What version of Tcl are you using?

AM I am using for instance Tcl 8.3.1 on Solaris, but also Tcl 8.3.4 from ActiveTcl on Windows, they all give (when typed into the console):

   % set a [list b ; c]                 

ambiguous command name "c": case catch cd clock close concat continue

Isn't it the interpreter that sees ; as a command separator, before [list] can deal with it?


DGP

   % set a [list b ; c]                 
   ambiguous command name "c": case catch cd clock close concat continue

In that example, the [list] command is ended by the semicolon, and the next command [c] is evalauted, raising the error seen. The [set a] never gets a chance to run because of errors during command substitution of its arguments.

Now look at:

   % set a [list b \; c]
   b {;} c

No error, but the semi-colon is quoted. If I pass that list to [eval] it will see the command [b] with arguments ; and c. It will not see the two commands [b] and [c] separated by a semi-colon.

Finally consider:

   % set a "b ; c"
   b ; c

That's what you need to pass to [eval] to get two commands evaluated in sequence.

Clear?

From the Tcl chatroom:

suchenwi: If you want to use list to keep whitespacy words together, you could use it locally:

 after [expr {$time*1000}] "destroy [list $t] ; wm deiconify ." 

list on a non-whitespacy argument doesn't hurt (or add braces); but if $t is ".my Toplevel", it would come as "destroy {.my Toplevel};" in the after script.

dgp: or,

 [after $delay "[list destroy $t]; [list wm deiconify .]"]

DGP One final note. While the discussion above may be useful for understanding the fine points of Tcl substitution rules, the right answer to the problem posed is to define a [proc].