13—
Toplevel Widgets
Any Perl/Tk application includes at least one toplevel widget. When you call the new method from the MainWindow class, you are creating a toplevel widget without even knowing it. You can create other toplevel widgets to be used in your application in additionto the MainWindow toplevel widget. The MainWindow is special because it automatically displays when you call MainLoop(). Other toplevel widgets in your program must be explicitly displayed somewhere in the code.
Here are some examples of how you can use toplevel widgets:
• Display informational text with a Close button.*
• Provide data gathering that is triggered by something the user does (for example, clicking a button).
All toplevel widgets have the same behavior: Each has decoration that is consistent with the system on which your application is run. Each toplevel can contain other widgets and/or multiple groups of widgets (for example, they can be grouped in a frame widget).
The rest of the chapter will cover how to use toplevel widgets and what options allow you to change their behavior.
Creating a Toplevel Widget
To create a toplevel, call Toplevel from the desired parent widget, usually the MainWindow widget (you already know that to create a main window, you must
* Look at Tk::Dialog. It is designed to do this and uses a toplevel widget.
use MainWindow->new()). The returned item is a reference to the toplevel widget; the reference allows you to configure the widget, call methods on it, and place items within it. Here is a simple example:
use Tk;
$mw = MainWindow->new;
$mw->title("MainWindow");
$mw->Button(-text => "Toplevel", -command => \&do_toplevel)->pack();

MainLoop;
sub do_toplevel {
  if (! Exists ($tl)) {
    $tl = $mw->Toplevel();
    $tl->title("Toplevel");
    $tl->Button(-text => "Close",
                -command => sub { $tl->withdraw })->pack;
  } else {
    $tl->deiconify();
    $tl->raise();
  }
}
When you run this program, clicking on the Toplevel button in the main window creates the toplevel widget (if it needs to) and displays it. Clicking Close hides the toplevel from view. You need to test for the existence of the toplevel before you show it because you don't want to re-create it if it already exists and you don't want to try to show something that doesn't exist.
When the Close button is clicked, the toplevel is withdrawn. It still exists; it is just not visible to the user. This saves time the next time around by redisplaying the same window. You can also use withdraw if you don't want to show the toplevel while you are filling it with widgets. Simply use the withdraw method, place the interior widgets, and then redisplay the widget by using deiconify and raise.
These options can be specified in the call to Toplevel or by using the configure method.
-background => color
Sets the background color of the toplevel widget. Note that the background may be hidden by widgets placed in the toplevel if the toplevel is completely covered by widgets.
-borderwidth => amount
Sets the width of the border around the toplevel. Default is 0.
-class => classname
Sets the classname used with the option database for this toplevel widget.
-colormap => "new" | $window
Specifies whether to use a new colormap or share one with another widget in the application. Default is undef.
-container => 0 | 1
Tk8.0 only. If true, this window will contain an embedded application (see the -use option).
-cursor => cursorname
Sets the type of cursor used over the toplevel widget.
-height => amount
Sets the height of the toplevel.
-highlightbackground => color
Sets the color the highlight rectangle should be when the toplevel does not have focus.
-highlightcolor => color
Sets the color the highlight rectangle should be when the toplevel does have focus.
-highlightthickness => amount
Sets the thickness of the highlight rectangle. Default is 0.
-menu => $menu
Tk8.0 only. Indicates that the toplevel uses the menu in $menu across the top of the window.
-relief => 'flat' | 'groove' | 'raised' | 'ridge' | 'sunken' | 'solid'
Changes the appearance of the edges of the toplevel. Default is 'flat'.
-screen => screenname
Sets the screen on which to place the toplevel. Cannot be changed by configure method.
-takefocus => 0 | 1 | undef
Determines if toplevel can have keyboard focus. Default is 0, meaning it cannot have keyboard focus.
-use => $windowid
Tk8.0 only. $windowid must contain a hex string of the window to embed in the toplevel. The -container option must have the value 1 to use this option.
-visual => "type #''
When used on an X Window System, changes the depth of colors available to your application. Does nothing on Win32 systems.
-width => amount
Sets the desired width of the toplevel.
Toplevel Methods
The methods available with the toplevel widget are listed and explained in the following sections (it is important to note that all of these methods apply to a MainWindow as well; a MainWindow is just a specialized toplevel widget). You haven't seen many of them before because toplevel is a different sort of widget than the others covered so far in this book. Also keep in mind that a lot of these methods were designed originally for use with a Unix windowing environment, and quite a few of them will state "No effect in Win32 system." Many of these functions serve no useful purpose to the typical ordinary Perl/Tk application, but I'll document them here for thoroughness.
Several of the methods here alter window manager properties, which often look like WM_PROPERTY_THING. These properties are also traditionally associated with the X Window system on Unix, but some still apply in Win32 systems as well. If a specific method doesn't say anything about which system it applies to, it will apply to both. If it only applies to one or the other (or only half-works in one system), this will be mentioned as well.
Configuring a Toplevel
Both cget and configure methods are used to set and get option values for a toplevel widget. See Appendix A for more detailed information on how to use these methods.
Sizing a Toplevel
You can use the geometry method to define or retrieve a geometry string. A geometry string determines the size and placement of a window on the screen. The geometry string is a concept that originated on Unix systems, and at first glance, it is a bit cryptic. Here is a regular expression that describes a complete geometry string:
^=?(\d+x\d+)?([+-]\d+[+-]\d+)?$
The equal sign can be omitted completely (and usually is). The first portion (\d+x\d+) is the width and height (in that order) separated by an x. Both width and height are specified in pixels by default and in grid units if the window is gridded with the grid method (described later). The last portion of the geometry string represents the x and y coordinates of the location in which the toplevel should be placed on the screen. Both x and y are always in pixels. Here are a few examples of what some geometry strings look like:
300×300       # width and height both = 300
300×450       # width = 300, height = 450
300×450+0+0   # width = 300, height = 450 placed in upper left corner
300×450-0-0   # width = 300, height = 450 placed in lower right corner
 
300×450+10+10 # width = 300, height = 450
              # placed 10 pixels out from upper left corner
+0+0          # window is 'natural' size, placed in upper left corner
When geometry is called with no arguments, the current geometry string is returned. You can also specify a new geometry by using geometry with the new geometry string as the argument. To set the size and position of the window immediately, you would do this:
$mw = MainWindow->new();
$mw->geometry("300×450+0+0");
If you specify only the width and height, the placement of the window is determined by the window manager. If you specify only the positioning, then the size of the window will be determined by the widgets placed within the toplevel, but the window will be placed at those x and y coordinates.
You can force the window back to its natural size by calling geometry () with an empty geometry string:
$toplevel->geometry("");
Maximum Size
You can use maxsize to restrict the largest size of the window. It takes two integers as arguments, as follows:
$toplevel->maxsize(300,300)
If you call maxsize without any arguments, you'll get an empty string or a list with two items in it representing the current values. Calling maxsize with two empty strings cancels the limitation.
Minimum Size
You can also restrict the smallest size of the window by using minsize. The window will always be at least the size specified:
$toplevel->minsize(100,100);
Calling minsize without arguments will return an empty string or a list containing the width and height respectively. Calling minsize with two empty strings will eliminate the minimum size restriction.
Limiting Resizing
You can control whether a window can be resized in width and/or height by using resizable:
$toplevel->resizable(1, 0)
($canwidth, $canheight) = $toplevel->resizable();
Specifying 1 means it is resizable, and 0 means it is nonresizable in the specified direction. If you don't specify any arguments, resizable returns a list with two items. The first item is a 1 or 0 and indicates whether if the width is resizable. The second item is a 1 or 0 and indicates whether if the height is resizable. By default, a window is resizable in both directions.
Using a Size Aspect
You can use the aspect method to force the window to stay a certain width and height:
$toplevel->aspect( [ minN, minD, maxN, maxD ]);
The aspect method does some very subtle things, and you'll probably never use it. If you do, play around with different values (starting with the example below) to get the effect you want
When you use the aspect method with no arguments, it returns either an empty string (if there are no constraints to the aspect of the window) or an array containing four elements:
($minN, $minD, $maxN, $maxD) = $toplevel->aspect;
Using these values, you can see how aspect controls the window:
($minN/$minD) < width/height < ($maxN/$maxD)
You can also send four empty strings to unset the aspect restrictions on the window. Try using $toplevel->aspect(1,2,3,1); the effect is subtle.
Setting the Title
You can change the text across the top of the window by using the title method:
$toplevel->title("This will be the title");
Pass a string in with title and the new title will appear immediately in the window, assuming the window is currently visible. If you don't pass an argument with title, the current title string is returned. For the X Window System, the default title of a window is the name used to run the program, and the first character of the name is uppercase. For Microsoft Windows, the title always starts out as Toplevel.
Showing the Toplevel
The deiconify method causes the toplevel to be displayed noniconified or deiconifies it immediately if the window has already been displayed once. If the window
has been withdrawn, a $toplevel->raise() must also be done to correctly display the window.
The raise method brings the toplevel to the front of all the other toplevel windows in the application if you call it with no arguments:
$toplevel->raise();
You can also put the toplevel in front of another toplevel:
$toplevel->raise($other_toplevel);
It is sometimes necessary to use both deiconify and raise to get the window to show up on the screen.
Withdrawing the Toplevel
When you create a window, it is a good idea to make it invisible while you fill it with widgets. You can do so by using the withdraw method:
$toplevel->withdraw();
If the window is already visible, withdraw will make the window manager forget about the window until it has been deiconified.
Iconifying the Toplevel
The iconify method forces the toplevel into iconified form:
$toplevel->iconify();
Iconifying is not the same as withdrawing the window; withdrawing the window will not show an icon on the desktop.
Specifying the Icon Bitmap
In the Unix X Window System, when you iconify your application, it is represented on the screen with a bitmap. You use the iconbitmap method to specify this bitmap:
$toplevel->iconbitmap();
$toplevel->iconbitmap("bitmap");
It takes a bitmap in the same form the -bitmap option supported by the button widget (see Chapter 3, The Basic Button). Calling iconbitmap with no arguments returns the current bitmap or an empty string. Calling iconbitmap with an empty string removes the current bitmap.
On Win32 systems, the application is kept in the Start taskbar with an unchangeable Tk icon and the name of the application. Using the iconbitmap method on a Win32 system does nothing.
Specifying the Icon Mask
A mask for the icon bitmap can be specified by using the iconmask method (remember, this will only work with X Window Systems). It also takes a bitmap specified from a file or a default bitmap name (see -bitmap documentation in Chapter 3). Where the bitmap mask has zeroes, no part of the normal icon bitmap will be displayed. Where the mask has ones, normal icon bitmaps will be displayed.
Calling iconmask with no arguments returns the current bitmap mask or an empty string if no bitmap is being used. Calling iconmask with an empty string unsets the mask:
$currentmask = $toplevel->iconmask();  # get the mask
$toplevel->iconmask("bitmapname");     # set the mask
$toplevel->iconmask("");               # unset the mask
Setting the Name of the Icon
The iconname method sets or returns the current text associated with the icon that is displayed when the application is iconified. You can pass in a new string or an empty string:
$toplevel->iconname("newname");
$current_name = $toplevel->iconname();
If you don't specify an argument at all, iconname returns the current iconname or an empty string. You can query and set the iconname on a Win32 system, but it doesn't do anything. This is a method that is used on the X Window System only.
Setting the Icon Position
The iconposition method suggests to the X Window Systems manager where the icon should be placed on the desktop when the application is iconified:
($x, $y) = $toplevel->iconposition();
$toplevel->iconposition($x, $y);
If x and y aren't specified, a list is returned containing only two items, the current x and y. If you call iconposition with two empty strings (one for each x and y), the suggestion to the window manager is cancelled.
Using a Window Instead of an Icon
Some systems (not Win32) support the idea of using a widget (or window) instead of a bitmap for an icon. Specify the widget by using the iconwindow method. To find out what the current widget is, call iconwindow with no arguments (an empty
string is returned if there is no associated $widget). You can specify an empty string instead of $widget to cancel by using a widget for the icon:
$currentwindow = $toplevel->iconwindow(); # get
$toplevel->iconwindow($window);           # set
$toplevel->iconwindow("");                # unset
Determining the State
The state method returns one of three strings: "normal", "iconic", or "withdrawn".
$state = $toplevel->state();
The string indicates the state of the window when state is called.
Assigning an Application Name
The client method returns an empty string if your application doesn't have a name assigned to it.
$name = $toplevel->client( );
$toplevel->client("name");
To assign a name, send a string to the client method after you create your toplevel widget. You can use this in an .Xdefaults file in the X Window System to assign colors to your application.
Window Properties
The protocol method controls the following window properties: WM_DELETE_ WINDOW, WM_SAVE_YOURSELF, and WM_TAKE_FOCUS. The callback (if any) associated with each property will be invoked when the window manager recognizes the event associated with the property:
$toplevel->protocol ( [ property_name] [, callback ] );
The WM_DELETE_WINDOW property callback is invoked when the window has been deleted by the window manager. By default, there is a callback assigned by Perl/Tk that destroys the window. If you assign a new callback, your callback will be invoked instead of the default callback. If you need to save data associated with that window, do so in the callback and then invoke $toplevel->destroy() to mimic the correct behavior afterward.
The other two properties, WM_SAVE_YOURSELF and WM_TAKE_FOCUS, are much less commonly used. For instance, WM_TAKE_FOCUS is used in Unix systems but not in Win32. The presence of these properties is dependent on the window system you are running. If your application will be running on multiple systems, don't expect these properties to always be available. To find out if they
are available, assign each one a callback that does a print and then run the application to see if the print is ever invoked.
If you leave out the callback when you use protocol, the current callback assigned to that property will be returned (or an empty string if there isn't a current callback assigned). You can remove the callback by sending an empty string instead of the callback. If neither argument is specified, the method returns a list of all properties that have callbacks assigned to them.
Colormap Property
The colormapwindows method affects the WM_COLORMAP_WINDOWS property. This property is used to talk to the window manager about windows that have private colormaps. Using colormapwindows with no arguments returns a list of windows. The list contains windows (in order of priority) that have a different colormap than their parents:
@list = $toplevel->colormapwindows();
You can pass a list of windows to colormapwindows as well:
$toplevel->colormapwindows(@list);
If you don't use this function at all, Perl/Tk will take care of everything for you, although the order of the windows might be different.
The Command Property
The command method (not to be confused with the -command option used with most of the widgets) controls the WM_COMMAND property. When used with no arguments, command returns a list reference:
$listref = $toplevel->command();
The list holds the words of the command used to start the application. Use this bit of code to determine what your application command was (which is sometimes nothing):
$listptr = $mw->command();
foreach (@$listptr) {
  print "$_\n";
}
You can unset the WM_COMMAND property by sending an empty string:
$toplevel->command("");
The Focus Model
The focusmodel method controls whether or not the toplevel widget will give up the keyboard focus when another application or window should have it:
$toplevel->focusmodel( [ "active" | "passive" ] );
The default is "passive", meaning it will give up the keyboard focus. The changes present in your application depend completely on the type of window manager you are running your application under. My testing revealed no changes under Win32 or the X Window System.
Getting the Parent of the Toplevel
The frame method returns a hexadecimal string that is the "ID" of the parent of the toplevel widget:
$id = $toplevel->frame();
You can use $widget->id() to get the same ID from any widget in your application.
The Application Grid
There are a few complications with the grid method. Remember way back in Chapter 2 there was a grid there also which controlled geometry management. To resolve this little problem, we have to call this grid method in a funny way:
$mw->wm('grid', ... );
We must use the Wm (stands for window manager) method to invoke grid indirectly.
Now that we have that cleared up, we can get into what wm('grid', ...) does. When you tell the window to grid, you are restricting the size it can be. The size must always snap to the grid as defined in grid. We have to remember the listbox widget and the -setgrid option back in Chapter 7, The Listbox Widget. Once you use -setgrid => 1 on a listbox, you can use @list = $toplevel-> wm('grid'); to determine the values used in the grid. The values I got on my system were 10, 10, 7, and 17. This means the base width and height were each 10 pixels and each grid unit incremented by 7 pixels in width and 17 pixels in height. You can change the grid size and increments by calling wm('grid', ...) with new values if you desire, but if you don't, Tk manages everything quite nicely for any of the gridded widgets.
You should also know that you can unset the grid values by using empty strings for each instead of new values.
Being the Leader
This is another method you'll never use, but it's good to know what you're not using it for. The group method makes a widget the group leader of related windows. For each toplevel that you want to be in $widget's group, call $toplevel->group($widget). If $widget isn't specified, it will return the current group leader of $toplevel, or it will return an empty string if $toplevel isn't part of a group.
You can send an empty string to cancel toplevel's association with that group. That is, to remove a toplevel from the group, call $toplevel->group(" ").
Removing Decorations
To make a window with none of the normal window decorations (titlebar, borders, and so on) you can use the overrideredirect method with a true value:
$toplevel->overrideredirect(1); # Remove all decorations
Be careful though; you won't be able to move the window on the screen once it is drawn. If you forgot to put an exit button on it, you won't be able to quit the application gracefully (doing a CTRL-C in the window that started the script will kill it).
This is a way to make a splash screen-a screen that shows up as your application is loading. Remember that you must call MainLoop for it to show up at all.
Calling overrideredirect with no argument returns the current value (1 or 0):
$current_value = $toplevel->overrideredirect();
Calling overrideredirect again with a 0 value will not turn decorations back on once the window has been displayed.
Who Placed the Window?
When the toplevel widget is placed on the window, either the window manager tells the program where to be or the program tells the window manager where it wants to be. In some cases, the user positions the window manually when it comes up.
$who = $toplevel->positionfrom();
$toplevel->positionfrom("program");  # Try and force it
When called without argument, the positionfrom method returns information on which one happened. If it returns the string "program", an empty string, or a $widget, it means either the window manager or the program requested the position. If positionfrom returns the string "user", the user manually placed the window when it was created.
You can try to force which will happen by calling positionfrom with the "program" or "user" string, but it will only work if your window manager agrees with you.
Who Sized It?
The sizefrom method does the same thing positionfrom does except it returns information regarding the size of the window.
$who = $toplevel->sizefrom(); # "program" or "user"?
$toplevel->sizefrom("user");  # Try and force it
Not a Real Window
A transient window is one that isn't quite a real window (such as a pull-down menu). You can indicate to the window manager that the toplevel (for example, the pulldown menu) is related to its master (the window in which it is displayed) by using the transient method:
$mymaster = $toplevel->transient();
$toplevel->transient($master);
If you don't use any arguments with transient, it returns either the current master or an empty string.
Review
It is a good idea to use another toplevel widget instead of the MainWindow if there is too much information to fit in one window. Using toplevels to group information is also sometimes a good idea. When to use an additional toplevel is a design decision that you'll have to make. You don't want to have too many windows for the user to navigate, but a well-designed application might be able to make use of one or two. For instance, the Tk module comes with a Tk::Dialog module that lets you easily display messages to the user. Check out the documentation included with the Dialog.pm file for more information on how to use it.
Fun Things to Try
Take the Dynamic Document List example from the last chapter and make it create a new toplevel every time the user hits the New Document button. (Advanced: actually create or load a file.)