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 event and use that to notify the process that the window has been configured and that it is 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? Peter Newman 24 April 2004: See [Total Window Geometry]. ---- # [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 {center_the_toplevel %W} return } proc center_the_toplevel { w } { # Callback on the 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 so that this procedure is # not called again when the window manager finishes # centering the window bind $w {} } return } grid [button .b -text "Test" -command make_the_window] \ [button .q -text "Quit" -command exit] ''Sergey Vlasov, 12-Jun-2005:'' The code above does not work at least in Linux with Tcl/Tk 8.4.8 - apparently when the '''''' event finally arrives, it is too late to call '''wm geometry'''. However, using after idle {center_the_toplevel .t} instead of binding to the '''''' event seems to work. ---- [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... ---- [WJG] 25-FEB-07. Centered window with a specific size. #----------------------------------------- # centre the window, with specified height and width, defaults to 640 x 480 #----------------------------------------- proc centre_window {w {width 640} {height 480} } { set x [expr { ( [winfo vrootwidth $w] - $width ) / 2 }] set y [expr { ( [winfo vrootheight $w] - $height ) / 2 }] wm geometry $w ${width}x${height}+${x}+${y} } ---- [MHo] 2007, Sep. 23: Here's my try, a kind of combination of what I've read here and there: #------------------------------------------------------------------------------- # - takes the path of a window as the only optional arg # - returns 6 elements which are either 0 or the following, in that order: # winOffSetX winOffsetY borderWidth borderHeight taskbarWidth taskbarHeight # proc taskbar {{w .taskBarSz_tmp}} { catch {destroy $w} toplevel $w # wm overrideredirect $w 1; # this ignores the TaskBar! wm state $w zoomed update foreach {x y oX oY} [list [winfo x $w] [winfo y $w] 0 0] {break} # Width of the ver. border must be added to width: set wAdd [expr {[winfo rootx $w] - $x}] # Height of the hor. border incl. window title bar must be added to height: set hAdd [expr {[winfo rooty $w] - $y}] # TaskBar Width (if on the LEFT or RIGHT side): set tW [expr {[winfo screenwidth $w] - ([winfo width $w] + $wAdd)}];# or 'reqwidth'? # TaskBar Height (if at the TOP or BOTTOM): set tH [expr {[winfo screenheight $w] - ([winfo height $w] + $hAdd)}];# or 'reqheight'? if {$y < $x} { set oX $tW; # TaskBar on the LEFT } elseif {$x < $y} { set oY $tH; # TaskBar on the RIGHT } destroy $w return [list $oX $oY $wAdd $hAdd $tW $tH] } #------------------------------------------------------------------------------- # centers the given window on screen, respecting TaskBar size and position # proc center_window {w} { wm withdraw $w update idletasks foreach {oX oY wAdd hAdd tW tH} [taskbar] {break} set workAreaWidth [expr {[winfo screenwidth $w] - $tW}] set workAreaHeight [expr {[winfo screenheight $w] - $tH}] set windowWidth [expr {[winfo reqwidth $w] + $wAdd}] set windowHeight [expr {[winfo reqheight $w] + $hAdd}] set x [expr {($workAreaWidth - $windowWidth) / 2 + $oX}] set y [expr {($workAreaHeight - $windowHeight) / 2 + $oY}] wm geom $w +$x+$y wm deiconify $w # ::tk::PlaceWindow $w; # aus tk.tcl. Does not respect the taskbar. } # Remark: the two routines above, and PlaceWindow, could and should be combined. ---- [MHo] 2008-02-11: reworked and simplified the code from above: #------------------------------------------------------------------------------- # centers the given window on screen, respecting TaskBar size and position # proc center_window {w} { set tw ${w}_temp_ catch {destroy $tw} toplevel $tw wm attributes $tw -alpha 0.0 ; # avoid flicker through wm ... zoomed wm state $tw zoomed update set areaW [winfo width $tw] ; # border thickness not relevant (?) set areaH [winfo height $tw] ; # titlebar not relevant set offsX [winfo x $tw] set offsY [winfo y $tw] destroy $tw set winW [winfo width $w] set winH [winfo height $w] wm withdraw $w set x [expr { (($areaW - $winW) / 2) + $offsX }] set y [expr { (($areaH - $winH) / 2) + $offsY }] wm geom $w +$x+$y wm deiconify $w update } I don't ''really'' know why, but on my system it works.... ---- [MHo] 2008-02-13: Found (more) bugs and simplified code again, see [Centering a window on windows]. ---- [Category GUI]