Version 15 of Centering a window

Updated 2003-03-18 21:46:10

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:


Martin Lemburg July 20th, 2002:

Every solution on this page for centering a window is nice, but they do not work completely satisfying. On windows all solutions don't respect the border and the title of the window. So the window is not really centered, but nearly.

Can anybody give me a hint how to fix this?

I don't want to ask the registry for its toplevel borderwidth and title height (display properties), because the values in the key HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics are strange. They are mostly negative, eg.:

  • BorderWidth - REG_SZ - -12
  • CaptionWidth - REG_SZ - -276

The CaptionWidth and 2*BorderWidth must be added to the normal height of the window to be centered. The BorderWidth must be added twice to the normal width of the window to be centered.

Are there other ways?


# 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].


DKF notes: The expected behaviour of

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

is probably quite surprising to newbies. What it will do is it will kill Tk completely (in the current interpreter) after $time seconds. Probably not what was desired!

This is because the list invokation forms a single command (which is destroy) with 5 arguments, two of which happen to look like widget names (those are $t and ., presumably.) Now destroy is a helpful command, which just ignores requests to destroy widgets that don't exist; hence it ignores the \; wm deiconify and destroys $t and then .

Destroying . stops the current interpreter from being Tk-enabled...