Layout


After all this talk of collecting and ordering controls, you may be wondering how to arrange them, especially in the face of the needs of different users with respect to system font size and the size of the data being entered.

Form Auto-Scaling

For example, if you lay out a form with system font size set to Normal (96 dots per inch, or dpi) in the Display control panel, what happens when your users are using Large (120 dpi) or one of the custom settings? You'd certainly prefer that a form such as that in Figure 2.15 show correctly at all font sizes.

Figure 2.15. A Sample Form at Normal Size Fonts

If you're the curious type, you might even attempt to simulate this form for Large fonts by changing the form's font size from 8.2 points (the default) to 10 points, preserving approximately the same proportion as between 96 dpi and 120 dpi. Unfortunately, this wouldn't leave you feeling confident because this change yields a form that looks like the one in Figure 2.16.

Figure 2.16. Increasing the Form's Font Size at Normal Size Fonts

Notice that increasing the font size increases the height of the TextBox control, but not the size of the form overall, to maintain the same proportional sizing and spacing. However, if you perform the actual test by changing from Normal to Large fonts in the Advanced settings of the Display control panel (which will likely require a restart of Windows), you may be pleased to notice that showing your form at this new font size looks like Figure 2.17 without the need to recompile your application.

Figure 2.17. The Sample Form at Large Size Fonts

The secret to making this work is a form property called AutoScale. When a form is first loaded, if AutoScale is set to true (the default), it uses another property called AutoScaleBaseSize. This property is actually set by the Designer and specifies the average width and height of characters in the form's font. The default font ”8.25-point MS Sans Serif under Windows XP Normal fonts ”has an average width and height of 5x13. This information will be encoded into the InitializeComponent function:

 this.AutoScaleBaseSize = new Size(5, 13); 

Under Large fonts, the default font will be 7.8-point MS Sans Serif, but the average width and height of the font has now increased to 6x15 (that's why they call it "Large" fonts). At load time, the form calls Form.GetAutoScaleSize and notices the difference between the scale it was designed with and the current scale, and the form adjusts its height and width and those of its controls along with the positions of the controls. This keeps the "feel" of the form roughly the same, no matter what the system font settings are.

In our sample, the form's client area width increased from 296 to 378 (~27%) as the width of the font went from 5 to 6 (~20%). Similarly, the height increased from 54 to 66 (~22%) as the height of the font went from 13 to 16 (~23%). Rounding errors make the scaling imperfect, and it seems that WinForms uses a little fudge factor to make sure that things are big enough. But in general, the auto-scaling should yield forms that look pretty good given the amount of work you had to do to achieve the effect (~0%).

Anchoring

Scaling for system font settings is not all the work that needs to be done to make your form adjust itself to your users' whims. For example, to enter a long string into a text box, users may attempt to widen the form, as shown in Figure 2.18.

Figure 2.18. All Controls Anchored Top, Left

Unfortunately, the user isn't likely to be happy with this. The form gets bigger, but the contained controls do not. Ideally, we'd like the text box to expand as the form expands, something that can be achieved manually:

 int delta = 0; void Form1_Load(object sender, EventArgs e) {  delta = ClientRectangle.Width - textBox1.Width;  } void Form1_SizeChanged(object sender, EventArgs e) {  textBox1.Width = ClientRectangle.Width - delta;  } 

During the form's Load event, this code captures the delta between the width of the text box and the width of the client rectangle so that when the form's size is changed, we can reset the width of the text box to maintain the difference in width as a constant. Keeping this difference constant means keeping the distance between the right edge of the text box a fixed number of pixels from the right edge of the form.

Keeping an edge of a control a constant distance away from its container edge is called anchoring . By default, all controls are anchored to the top and left edges of their containers. We're accustomed to Windows moving our child controls to keep this anchoring intact as the container's left or top edge changes. However, Windows does only so much. It doesn't resize our controls to anchor them to other edges. Fortunately, WinForms does, without requiring you to write the manual anchoring code just shown.

You can change the edges that a control is anchored to by changing the Anchor property to any bitwise combination of the values in the AnchorStyles enumeration:

 enum AnchorStyles {   None,   Left, // default   Top, // default   Bottom,   Right, } 

Getting our text box to resize as the form is resized is a matter of changing the Anchor property to include the right edge as well as the left and the top edges. Using the Property Browser, you even get a fancy drop-down editor, as shown in Figure 2.19.

Figure 2.19. Setting the Anchor Property in the Property Browser

Even though Windows provides built-in support for anchoring to the top and left edges, anchoring does not have to include the left or top edges at all. For example, it's common to anchor a modal dialog's OK and Cancel buttons to only the bottom and right edges so that these buttons stay at the bottom-right corner as the dialog is resized, but aren't resized themselves . Resizing of a control happens if you have two opposing edges selected. If you don't have either of the opposing edges selected, neither left nor right, then the control will not be resized in that dimension but will maintain the same proportion of space between the opposing edges. The middle square in Figures 2.20 and 2.21 shows this behavior as well as several other anchoring combinations.

Figure 2.20. Anchoring Settings before Widening

Figure 2.21. Anchoring Settings after Widening

Docking

As powerful as anchoring is, it doesn't do everything. For example, if you wanted to build a text editor, you'd probably like to have a menu, a toolbar, and a status bar along with a text box that takes up the rest of the client area not occupied by the menu, the toolbar, and the status bar. Anchoring would be tricky in this case, because some controls need more or less space depending on the run-time environment they find themselves in (recall what happened to the text box when we increased the font size earlier). Because anchoring depends on keeping a control a fixed number of pixels away from a form's edge, we'd have to do some programming at run-time to figure out how high the status bar was, for example, and then set that as the distance to anchor the text box away from the edge. Instead, it would be much easier to tell the form that the text box should simply take whatever space remains in the client area. For that, we have docking.

Docking is a way to specify a specific edge that we'd like to have a control "stick" itself to. For example, Figure 2.22 shows a form with three controls, all docked. The menu is docked to the top edge, the status bar is docked to the bottom edge, and the text box is docked to fill the rest.

Figure 2.22. A Docking Example

You implement the docking behavior by setting each control's Dock property to one of the values in the DockStyle enumeration (exposed nicely in the Property Browser, as shown in Figure 2.23):

 enum DockStyle {   None, // default   Left,   Top,   Right,   Bottom,   Fill, } 
Figure 2.23. Setting the Dock Property in the Property Browser

Docking and Z-Order

As the form resizes, the docking settings keep the controls along their designated edges (or the rest of the space, as determined by the Fill DockStyle). It's even possible to have multiple controls docked to the same edge, as shown in Figure 2.24.

Figure 2.24. Two Status Bars Docked to the Bottom Edge

Although I don't recommend docking two status bars to the same edge, it's certainly possible. Docking is done in reverse z-order priority. In other words, for statusBar1 to be closest to the bottom edge, it must be further down in the z-order than statusBar2. The following AddRange call gives statusBar1 edge priority over statusBar2:

 this.Controls.AddRange(   new System.Windows.Forms.Control[] {  this.statusBar2, // z-order 0, lowest bottom edge priority   this.textBox1, // z-order 1   this.statusBar1}); // z-order 2, highest bottom edge priority  

Given the drag-and-drop Designer model, which inserts each new control with a z-order of 0, it makes sense that docking priority is the reverse of z-order. However, as you add new controls on the form and need to adjust the z-order, you may find a conflict between controls along a certain edge and those set to fill. In that case, the fill control needs to have the lowest edge priority on the form or else it will dock all the way to an edge set to be used by another control. Figure 2.25 shows an example.

Figure 2.25. TextBox Whose DockStyle.Fill Has Higher Docking Priority than StatusBar

Notice that the text box has a scroll bar, but the bottom part of it is cut off by the status bar along the bottom edge. This indicates that the status bar has a lower docking priority than the text box. However, docking priority isn't set directly in the Designer. Instead, you set the z-order. In our example, right-clicking on the text box in the Designer and choosing Bring To Front will push the text box to the top of the z-order but to the bottom of the docking priority, letting the status bar own the bottom edge and removing it from the client area that the text box is allowed to fill. As a rule, whenever you see a visual anomaly like this on your form, you can generally resolve the problem by bringing to the front the control set to DockStyle.Fill.

Splitting

Often, when docking is used, you'd like the user to have the ability to resize some of the controls independently of the size of the form itself. For example, Windows Explorer splits the space between the toolbar and the status bar, with a tree view on the left and a list view on the right. To resize these controls, Explorer provides a splitter , which is a bar separating two controls. The user can drag the bar to change the proportion of the space shared between the controls. Figure 2.26 shows a simple example of a Splitter control between a TreeView control docking to the left edge and a ListView control docked to fill.

Figure 2.26. An Example of Splitting (with Pointer Indicating a Potential Drag)

Splitter controls are available in the Toolbox. The splitter manages the size of the preceding control in the z-order (or the empty space, if there is no preceding control). For example, the relative z-orders of the tree view, splitter, and list view shown in Figure 2.26 are set this way:

 this.Controls.AddRange(   new System.Windows.Forms.Control[] {  this.listView1, // z-order 0, size set directly by splitter   this.splitter1, // z-order 1, splitter   this.treeView1, // z-order 2, size set indirectly by splitter  this.statusBar1}); 

You can split controls vertically by setting the Dock property to DockStyle.Left (the default) or split them horizontally by setting the Dock property to DockStyle.Top. An example of horizontal splitting is shown in Figure 2.27, where the group box has a z-order of zero, followed by a splitter with the Dock property set to DockStyle.Top.

Figure 2.27. Horizontal Splitting

Grouping

To achieve advanced layout effects, it's often necessary to break the problem into groups. For example, imagine a form showing a list of people on the left and a list of details about the current selection on the right, as shown in Figure 2.28.

Figure 2.28. Grouping, Docking, and Anchoring

You can't tell by looking at this single picture, but as the group boxes in Figure 2.28 change size, the controls inside the group boxes also change size. This happens because of two attributes of group boxes. The first is that group boxes are container controls , meaning that they act as a parent for child controls, just as a form does. The list box on the left is a child of the group box on the left and not directly a child of the form. Similarly, the label and text box controls on the right are children of the group box on the right.

The second important attribute of container controls is that they share the same layout characteristics of forms, in that child controls can be anchored or docked. As a result, the anchoring and docking settings of a control are relative, not to the edges of the form, but rather to the edges of the container. For example, the list box in Figure 2.28 is actually set to DockStyle.Fill to take up the entire client area of the group box. Similarly, the anchoring properties of the text boxes on the right are anchored top, left, and right; therefore, as the group box changes width or changes position relative to the parent form, the text boxes act as you would expect relative to the group box.

The GroupBox control is one of three container controls WinForms provides; the other two are the Panel control and TabControl. The Panel control is just like a group box except that there is no label and no frame. A panel is handy if you'd like something that looks and acts like a subform , or a form within a form. TabControl is a container of one or more TabPage controls, each of which is a container control with a tab at the top, as shown in Figure 2.29.

Figure 2.29. A TabControl with Two TabPage Controls

Custom Layout

The combination of anchoring, docking, splitting, and grouping solves a large majority of common layout problems. However, it doesn't solve them all. For example, these techniques don't let you automatically spread controls proportionally across a client area in a table or gridlike manner, as shown in Figure 2.30.

Figure 2.30. Custom Layout Example

The following Layout event handler arranges the nine button controls of Figure 2.30 proportionally as the form is resized. (This is also a very good use of the SuspendLayout and ResumeLayout methods to suspend layout, and of the SetClientSizeCore method to set the client area based on an even multiple of the width and the height.)

 void GridForm_Layout(object sender, LayoutEventArgs e) {   // Suspend layout until we're finished moving things   this.SuspendLayout();   // Arrange the buttons in a grid on the form   Button[] buttons = new Button[] { button1, ..., };   int cx = ClientRectangle.Width/3;   int cy = ClientRectangle.Height/3;   for( int row = 0; row != 3; ++row ) {     for( int col = 0; col != 3; ++col ) {       Button button = buttons[col * 3 + row];       button.SetBounds(cx * row, cy * col, cx, cy);     }   }   // Set form client size to be multiple of width/height   SetClientSizeCore(cx * 3, cy * 3);   // Resume the layout   this.ResumeLayout(); } 

Although you can use the Layout event to handle all the layout needs of a form, it's much easier to use anchoring, docking, and grouping wherever possible and fall back on the Layout event only to handle special cases.



Windows Forms Programming in C#
Windows Forms Programming in C#
ISBN: 0321116208
EAN: 2147483647
Year: 2003
Pages: 136
Authors: Chris Sells

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net