Académique Documents
Professionnel Documents
Culture Documents
Layout for the purposes of this document applies to layout related changes to Windows Forms during the Whidbey product cycle. Some highly dependant content will be covered as a baseline for completeness. Layout for Whidbey translates to TableLayoutPanel, FlowLayoutPanel, AutoSize and AutoScale. About this document: This material should be neither be considered a complete coverage of Layout nor a deep dive into any one area. It simply tries to capture common talking points, interesting elements and answer common questions.
Contents
Whidbey Layout............................................................................................................1 1 Layout Overview..........................................................................................................2 2 TableLayoutPanel........................................................................................................8 3 FlowLayoutPanel........................................................................................................16 4 AutoSize..................................................................................................................18 5 AutoScale................................................................................................................23 6 Layout Perf..............................................................................................................26 7 AutoLayout...............................................................................................................29
Layout Overview
This section includes layout topics that are broadly applicable. The sections that follow contain control or technology specific content.
1.1
Terms
Assortment of layout related terms. AutoLayout - a foofy term used to describe the set of whidbey features which aid in localization of UI. A UI that exhibits AutoLayout does not require localizers to move the locations of controls after they have updated the text in a new language. This generally implies usage of AutoSize on controls, and usage of FlowLayoutPanel and TableLayoutPanel - although there are certain cases where this is not necessary. If the text of the control can grow by ~30% without clipping, you do not need to do anything special. AutoScale - a feature which (in V1 AutoScaleBaseSize, in V2 in AutoScaleDimensions) which grows/shrinks and shifts controls by a scaling factor when the font has changed for the form. (e.g. when customer switches DPI or moves to large fonts). This is the managed solution for dialog units. AutoSize - a boolean flag on control which automatically forces the size of the control to be the "preferred size" as specified by the protected virtual GetPreferredSize method. Bounds represents the total size of the control and the location of the control with respect to the controls parent. In the case where the control does not have a parent, (usually a form), the location represents the location on the screen. ClientRectangle represents the size of the control without the non-client border elements such as scrollbars, title bars, menus, and window borders. Since this rectangle is represented in the client coordinate system, by definition, the value returned by the ClientRectangle.Location will always be (0,0). ClientSize returns/sets ClientRectangle.Size. DisplayRectangle: the area in which to layout, which is also used in scrolling. This rectangle should be used as the frame of reference to layout child controls. Normally, the display rectangle is the same as the client rectangle. However for controls deriving from ScrollableControl, this rectangle can be larger than the client area. Unlike the ClientRectangle, the origin of the DisplayRectangle.Location property can be negative, representing how much the window is currently scrolled. The location of the DisplayRectangle corresponds to the AutoScrollPosition property of a ScrollableControl. In order to have proper scrolling support, is important to respect the location of the display rectangle when setting the bounds of a child control. E.g. Button1.Location = new Point(this.DisplayRectangle.Location.X + 20, this.DisplayRectangle.Location.Y + 20); FlowLayoutPanel - a panel which lays out its child controls in a flowing manner. Different flow directions, wrapping, and flow breaks are supported. GetPreferredSize - a method which controls override to specify how big they want to be. Containers/panels usually measure all their children, children usually measure their visual elements (text, image) and report back their suggested size. Location returns/sets Bounds.Location MinimumSize: The smallest Size a control is allowed to become. MaximumSize: The largest Size a control is allowed to become.
2
PreferredSize: This generally returns the ideal size for the control to show its contents correctly. This is implemented by calling GetPreferredSize(0,0). RightToLeftLayout - a flag that controls whether the control layout is right-to-left or left-toright when RightToLeft==Yes (uses WS_EX_LAYOUTRTL mirroring). Size returns/sets Bounds.Size TableLayoutPanel - a panel which lays out its child controls according to rows and columns. Row and column styles, row spanning and column spanning are supported.
1.2
What happens when I have Dock and Anchors that conflict? The simple answer is this is not possible. When you set an anchor, the dock style clears. And vise versa if you set the dock style. Under the covers, we use the same bit fields to represent dock and anchor, so it is not possible to get into a conflicting state. Anchor Scenarios These scenarios are in a form which employs a dock and anchor layout engine. Keeping a fixed distance from an edge If you have a control you want to remain a fixed distance from an edge of the parent.
Anchor Bottom|Right
Resizing a child control when the parent grows/shrinks If you have a control you want to resize when the parent resizes using opposite anchors (Left | Right) or (Top | Bottom) will help maintain the same distance from the edge as the parent resizes.
Anchor = Top|Bottom
Anchor = Left|Right
Dock Scenarios These scenarios are in a form which employs a dock and anchor layout engine. Always glued to one edge of the form - Dock Layout can generally be represented by combinations of Anchors, but its quite handy for things that always need to be glued to one edge of the parent control.
Splitter layouts through a combination of Dock styles, splitter style layouts can be achieved. This scenario is not as important in Whidbey, as we have provided a SplitContainer to do this layout magic for you. Dock Left Panel Dock Fill Panel
1.3
Depending on the implementation of GetPreferredSize for a control, changing the Padding property can inflate the control by the height and width specified in the Padding property.
1.4
1.5
// Test two - calling PerformLayout here calls Panel1_Layout // Child controls are not suspended when the parent is suspended. this.SuspendLayout(); panel1.PerformLayout(); this.ResumeLayout(false); // Test three, properly suspending layout this.SuspendLayout(); panel1.SuspendLayout(); panel1.PerformLayout(); // <--- Layout event on Panel NOT called panel1.ResumeLayout(false); this.ResumeLayout(false); panel1.Layout -= new LayoutEventHandler(Panel1_Layout); this.Layout -= new LayoutEventHandler(Form1_Layout); }
1.6
Additionally, anything that plays with the Control collection. These include but are not limited to
control.Controls.Add(..) control.Controls.Clear() control.Controls.Remove and RemoveAt control.BringToFront() control.SendToBack() control.Controls.SetChildIndex control.Parent
1.7
Selection
Once youve built up a form, it can be cumbersome to select various pieces of the UI. There are several ways to navigate your dialog in the designer. Document outline: While you are designing your dialog, use the Visual Studio Document Outline window to navigate from panel to panel. This shows a hierarchical view of the panels within your project. Typically this can be opened by using View->Other Windows->Document Outline. Escape key: When you select a control, you can get select its parent by pressing ESC key. ContextMenu: When you select a control, you can select its parent by right clicking and choosing Select <parent> from the context menu.
1.8
Other resources
Everett Layout material on MSDN Windows Forms Layout Introduction (for Everett)
TableLayoutPanel
Our primary weapon in our auto-layout arsenal is the Table Layout Panel (TLP). TLPs act similar to HTML tables in their ability to regulate space and flow on a form. The TableLayoutPanel has several key sources of information that it uses to determine the size/location of child controls, a collection of child controls to layout (in the Control collection), a number of rows and columns to generate, and a collection of row and column styles to determine the sizing characteristics for each row/column.
2.1
2.2
Username: Password:
AutoSize ColumnStyle
100% ColumnStyle
Proportionally distribute up space on resize rather than have the splitter model where only one side grows, as the dialog grows, grow both sides of the dialog equally. This was not something you could easily do with Anchor layout.
50%
50%
50%
50%
2.3
Adding Controls
Controls can be added to the TableLayoutPanel in a free-styled manner via the Add method on the Controls collection. The newly added control will be added in the next available cell, but generally column and row positions are assigned to the child controls:
tableLayoutPanel.Controls.Add(button1, 2,3)
2.4
Positioning Controls
Additionally the tableLayoutPanel provides several methods to change the position of controls that are already added to the table:
tableLayoutPanel.SetCellPosition(new TableLayoutPanelCellPosition(4,2))
NOTE: Via extender provider on the control itself the SetRow and SetColumn methods can be used. To determine where a control was place programmatically use the following:
public TableLayoutPanelCellPosition GetPositionFromControl(Control control);||
Absolutely position elements within a TableLayoutPanel The margin property of control can be used to adjust the distance from the edge of the cell. If you change the anchor to be Top|Left and change the Margin to be (20,3,0,0) the control will be 20 pixels from the left edge and 3 pixels from the top. Stretching and aligning elements within a TableLayoutPanel You can use the anchor property of the control to align to a particular side (e.g. Left will stick it to the Left side). Using opposing anchors, the control can be stretched. E.g. Anchor=Left|Right will stretch to fill the width. The Dock property works in a similar manner.
2.5
Example III: Exactly the same as above but the two percentage style columns are both 75%, the 80 pixels would be split equally between the two normalized columns: 1. 2. 3. 4. col1 ABS: 20 px col2 AUTO: whatever the preferred size is... say this measures to be 100px col3 75% (normalizes to 50%): (200 - 20 100) = 80px remaining space. 80px * .50 = 40px. col4 75% (normalizes to 50%): (200 - 20 100) = 80px remaining space. 80px * .50 = 40px.
Example IV: The last column covers the table layout panels underwear. So a 200px wide table with 20px absolute, an auto sized column and a 20px absolute would go like this:
10
1. col1 ABS: 20 px 2. col2 AUTO: whatever the preferred size is... say this measures to be 100px 3. col3 ABS: 20 px, not enough pixels so we stretch the last column to be (200 - 20 100) = 80px
2.6
2.7
Extender-provider properties
Rather than have properties on Cells, the table layout panel proffers properties on the controls within the cells via extender providers. Below is the list of those offered. CellPosition public void SetCellPosition(Control control, TableLayoutPanelCellPosition position); Specifies the where the column and row where a control should be placed. -1, -1 means place at next free position. Column public int GetColumn(Control control); public void SetColumn(Control control, int column); Specify the where the column where a control should be placed. -1, means place at next free position ColumnSpan public int GetColumnSpan(Control control); public void SetColumnSpan(Control control, int value); Specify how many columns this control should span. (default is 1) Row public int GetRow(Control control); public void SetRow(Control control, int row); Specify the where the row where a control should be placed. -1, means place at next free position.
2.8
Simply setting Button.AutoSize=true would cause the buttons to have unequal size, ala:
11
Instead, 1. place the controls within a TableLayoutPanel 2. One row and three columns for this example
3. Set each column to equal percentage column style 4. Anchor all buttons Left, Right (stretch) 5. Set TableLayoutPanel AutoSize to true
2.9
Simple setting the button to AutoSize (assuming the button is anchored Top, Left) would "push" the form bigger (assuming the Form was AutoSize=true), ala:
What you really want is for the form to remain its current size and the button to take up space from the textbox. To accomplish this: 1. Place both in a 2 column, 1 row TableLayoutPanel 2. Set the Button's column to be AutoSize 3. Set the TextBox's column to be 100%, and presto, you get:
12
2.10
2.11
2.12
Resizable Dialogs
Resizable dialogs require that controls are present that the user may want to resize to better use. In general, items such as labels and buttons will not need to be modified by the user. They will be covered by auto-layout. Controls that do need to be resizable are multi-line text boxes, data grids, etc...
13
To set up a resizable form, set the columns and rows that should grow to a percentage style. The TLP and the form should not be set to autosize. This will get us into problems with being able to resize larger without being able to resize the dialog smaller again. Default padding and margins around the borders will suffice. The TLP should be anchored top, bottom, left, right. The controls that need to grow will need to be anchored top, bottom, left, right. The other controls should be anchored however you want them to look on the form. Here are two screenshots of a resizable form before and after resizing.
14
2.13
TextWrapping
What are the the techniques for text wrapping? Wrapping radio button/checkbox http://blogs.msdn.com/jfoscoding/articles/478300.aspx Wrapping text http://blogs.msdn.com/jfoscoding/articles/478299.aspx
2.14
15
FlowLayoutPanel
Flowlayout panel is a much simpler control than TableLayoutPanel. It arranges items as a linear collection, supporting wrapping. It works well for menuing and list like scenarios and also supports textwrapping and some interesting alignment options. FlowLayoutPanel has a much smaller range of uses than TableLayoutPanel. Some scenarios in which it's interesting to consider FlowLayoutPanel include: Placing controls one after another without overlapping lists of links, such as explorer bars. Multi Column UI Portal an application such as money with lots of little panels of information that reflow/wrap when the window is resized. ToolStrip layout ToolStrips support flow layout by a property called LayoutStyle. By switching the LayoutStyle to Flow and casting the LayoutSettings property to FlowLayoutSettings, a ToolStrip can behave much like a FlowLayoutPanel. Toolbar-esque windows that can float and take on different orientations: if the contents of the FLP are properly laid out (e.g. integral heights/widths of one another), you can get a fair amount of rearrangement for free if the window switches between a set of fixed sizes. NOTE: FLP sample in the SDK provides a rudimentary demonstration of this.
3.1
Adding Controls
You simply add to the controls collection, but unlike the TLP, you cant specify row and column the controls are laid out in natural order from the collection. To move the controls, alter the position within the collection for that control.
3.2
Positioning Controls
Currently, the implementation of Anchoring and Docking with FLP, as illustrated in the picture below, is that a control is "box stretched" only as far as the extent of the longest/tallest control in the FLP. Dock and Anchor control how an individual control lines up with the other controls in the row. Diagram below.
16
How can I get the items in a flow layout panel to flow TopDown and stretch from edge to edge? The sample essentially sizes an item to hold the width to a certain size.
private void fLP1_Layout(object sender, LayoutEventArgs e) { fLP1.Controls[0].Dock = DockStyle.None; for (int i = 1; i < fLP1.Controls.Count; i++) { fLP1.Controls[i].Dock = DockStyle.Top; } fLP1.Controls[0].Width = fLP1.DisplayRectangle.Width fLP1.Controls[0].Margin.Horizontal; } // Where the flow layout is set up as this.fLP1.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; this.fLP1.Size = new System.Drawing.Size(175, 180); this.fLP1.Layout += new System.Windows.Forms.LayoutEventHandler(this.fLP1_Layout);
FlowBreak You can set FlowBreak on the control which is the last of that row/column. The following control should appear in the next column/row.
3.3
Properties
The following is a brief discussion of properties pertinent to the FLP. FlowDirection set the direction of flow to be LeftToRight, RightToLeft, TopDown or BottomUp. WrapContents specifies whether the flow wraps into multiple columns/rows when it runs out of space. Margin specify the separation between controls. E.g. if two buttons are in FlowDirection.TopDown with WrapContents = false, the spacing between button1 and button2 should be button1.Margin.Bottom + button2.Marigin.Top.
17
AutoSize
Autosize in its most elemental form means to both size and resize in an "intelligent" way for the particular control. The most common interpretation of this is to size the control the smallest size to fully display the contents.
4.1
AutoSizeMode
AutoSizes reign of terror is often throttled by the AutoSizeMode this has two settings: GrowAndShrink control can be sized freely to fully comply with its preferred size. GrowOnly control can only grow from the size explicitly set.
4.2
What direction do controls grow when they AutoSize? Controls "grow" (expand) opposite their anchors - so a button anchored Bottom, Right would grow leftwards if its string grew, and upwards if its font grew. Debugging tips: Want to know when a control's size is set to X,Y? Put a conditional breakpoint in Control.cs SetBoundsCore. http://blogs.msdn.com/jfoscoding/archive/2005/09/05/461096.aspx
18
4.3
Button
No
No
No
ButtonBase
No
No
No
CheckBox
No
ComboBox
No
No
No
ContainerControl
No
No
No
Subtracts the Padding from the constrainingSize, calls Padding, child LayoutEngine.GetPreferredSize(constrainingSize control layout ) then adds back on its Padding. returns SpecifiedBounds.Size* Uses the SpecifiedBounds.Width* and the PreferredHeight (height of measured text borders padding) changes to Size, Bounds, Width, Height Font, Padding
Control
No
No
No
DateTimePicker
No
No
No
DataGridView
No
No
No
changes to Measures column widths, row heights, tacks on column widths, room for scrollbars + padding heights, Padding Uses largest string in collection as width, height Text, Font, depends on font + bordersm, added with Padding Padding.Size Gets the PreferredSize from ContainerControl, Padding, child if padding is empty - adds a magic 9 pixels to control layout conform to UI guidelines Uses similar pattern to ContainerControl - asks Padding, child layout engine control layout Manages its own size in dock and anchor layout, tries to mush all text into one line. In other Font, Text, layouts, wraps at contstraining width. This can Padding be controlled by setting MaximumSize.Width at the point you want to wrap Forwards to Label Font, Text, Padding
DomainUpDown
No
No
No
Form GroupBox
No No
No No
No No
Label
Yes
LinkLabel
Yes
ListBox ListView
No No
item height uses MaxItemWidth, measures item heights in changes, PreferredHeight, adds space for system borders padding, border and Padding styles Padding not used
NumericUpDown Panel
No No
No No
No No
uses width of largest digit height of text system Text, Font, borders + padding Padding uses similar logic to ContainerControl border styles, padding, child control layout changes to image, border style, padding
PictureBox
No
No
No
RadioButton
No
Forwards to ButtonBase, unless it's FlatStyle.System, in which case we measure the changes to text, text and tack on extra space for win32 borders, padding, font Padding, and some extra space to bump it up to UI guidelines Forwards to TextBoxBase, tacking on room for scrollbars changes to text, font, padding, multiline, wordwrap
RichTextBox
No
No
No
TextBox TextBoxBase
No No
No No
No No
changes to text, Forwards to TextBoxBase, tacking on room for font, padding, scrollbars (typically sizes height to Font height multiline, in multiline=false) wordwrap Measures text, adds on borders and padding Similar to ContainerControl. In SplitStack, it measures all the room for the Overflow.AsNeeded and Overflow.Never items + room for overflow & grip. In all other layouts, refer to that layout engine's behavior changes to child element layout properties, font, padding
ToolStrip
No
Yes
No
ToolBar TrackBar
Yes Yes
Yes No
No No
Manages its own size according to widths of changes to buttons - calculates number of rows needed to button width display them all. Manages its own size - scrollbar height * 8
4.4
Simply setting Button.AutoSize=true on each button will break your alignment, since the localized strings are likely to be different lengths - so you'd get this:
The solution is to place the three (or however many) buttons (or whatever controls) in a vanilla, non-flow or table Panel* like this:
and anchor the buttons Left and Right. Then set the Panel to AutoSize, and when the text is changed, causing the buttons to adopt different lengths, the largest button "pushes" the Panel to the size it needs, and the anchors on the other (smaller) buttons "pulls" the smaller buttons to the same size, ala:
4.5
Alternately, if you do not want the dialog to be resizable, switch AutoSizeMode to GrowAndShrink.
4.6
AutoScale
AutoScale refers in general to recalculating sizes and positions for controls without reliance on an outer layout container or engine doing the calculation. This is done for environment changes like font, DPI etc. For Everett the default code spit was :
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
AutoScaleBase size attempts to store the size of the font rendered at the DPI of the machine. As is clearly apparent the Size type only stores integer values, from here the problem of conversion accuracy stems. After repeated changes, rounding errors can introduce noticable and at times, unacceptable loss of accuracy. For Whidbey, the default code spit is now
this.AutoScaleDimensions = new System.Drawing.SizeF(6.0F, 13.0F);
We now store the dimensions of the font in a SizeF which leads to much greater accuracy. In addition we offer three AutoScaleModes. Interestingly, both of these autoscale approaches coexist. The two members controlling these two systems are below AutoScale bool that enables/disables the Everett auto scaling system. AutoScaleMode enum including entry of None to map to AutoScale true. All other settings enable Whidbey auto scaling system. AutoScale AutoScaleMode Font DPI True n/a n/a False DEFAULT Whidbey scaling Whidbey scaling enabled, only dpi enabled, font AND changes are dpi changes are handled handled. n/a means that that property combination is not achievable. Property sets on one side set the property on the other. For AutoScale set to False, AutoScaleMode will be set to Font. None Everett scaling n/a
5.1
public Form1() { this.Font = SystemFonts.IconTitleFont; InitializeComponent(); SystemEvents.UserPreferenceChanged += new UserPreferenceChangedEventHandler(SystemEvents_UserPreferenceChanged); } void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) { if (e.Category == UserPreferenceCategory.Window) { this.Font = SystemFonts.IconTitleFont; } } protected override void Dispose(bool disposing) { if (disposing) { SystemEvents.UserPreferenceChanged -= new UserPreferenceChangedEventHandler(SystemEvents_UserPreferenceChanged); if (components != null) { components.Dispose(); } } base.Dispose(disposing); } private void InitializeComponent() { this.AutoScaleMode = AutoScaleMode.Font; this.AutoScaleDimensions = new System.Drawing.SizeF(5, 13); this.ClientSize = new System.Drawing.Size(292, 266); this.Name = "Form1"; this.Text = "Form1"; } } }
5.2
Comment
Default
UserControl location scaled size scaled UserControl Contents location scaled size scaled
UserControl.AutoSizeMode = None;
UserControl location scaled size scaled UserControl Contents Location NOT scaled size NOT scaled
UserControl location scaled size NOT scaled UserControl Contents location NOT scaled size NOT scaled
NOTE: The code used to enable fixed width fixed height user control:
6
6.1
Layout Perf
The section lists out common performance problems and suggested solutions.
6.2
Visual Inheritance
Problem: The code generated for visual inheritance (class Form2 : Form1) is actually not that efficient, as the constructor for the base form executes, then the constructor for the derived form executes. This essentially forces two layouts as ResumeLayout is called on the form twice. Visual Inheritance also has problems for localization, as the localization tools dont work well with this kind of situation. Solution: For perf reasons, if you can avoid using it, this is the best solution. If you must, you may want to consider using a base form and swapping in a panel with the derived controls as needed.
6.3
Rectangle bounds = this.ClientRectangle; bounds.Inflate(-10, -10); outerPanel.Bounds = bounds; this.Text = "Resized on RESIZE!"; }
6.4
Using nested AutoSized table layout panels more than two deep.
Problem: The tablelayoutpanel is a great control for automatically controlling layout, however it has its price performance wise. The simpler you can make the layout, the faster it will be. Solution: Keep the complexity of your layout to a minimum. If the flowlayoutpanel can achieve the same results for your UI, consider tatically using that in places. A flow is always mathematically cheaper to calculate than a table.
6.5
6.6
6.7
// As expected, the second one takes about 1/2 the time because there's half the conversation going on. // Trial 1 3717 // Trial 2 1865
6.8
AutoLayout
The following are notes primarily sourced from the autolayout work Jereme did. Prior to the Whidbey release, dialogs were created by developers, individually opened and modified by each localization team for each specific language. Because localization bugs tend to lag product development, localization bugs are particularly expensive (as they tend to occur closer to the end-game of a product cycle). The PLOC (pseudo localization) process has solved some of this by localizing the strings much earlier on in the game, however it does not solve the labor involved in individually opening and modifying each dialog for each locale. How do I know if my dialog is ready for localization? Each text field can grow by 30% without clipping The dialog can respond to High Contrast without clipping The dialog can respond to font changes without clipping The dialog can respond to DPI changes without clipping The dialog can be switched to RightToLeft mode without clipping (RightToLeft = true, RightToLeftLayout = true on the form).
7.1
Solution: Some controls in windows forms pick up their accessible names from the previous item in the tab order. E.g. a text box can pick up its accessible name from the label preceding it. If you have a label which describes a text box or combo box, you should set the TabIndex of the label to be one less than the TabIndex of the text box. Localization Considerations - Have you created a .resx file? Problem: Hand coding UI generally causes problems for localizers. If they have a .resx file, the dialog can be opened in a resource editor such as WinRes. Solution: Do not hand code UI, keep the YourForm.Designer.cs file around, hook up your events in a function outside of the YourForm.Designer.cs, so you can easily revist the file later using Visual Studio.
Shrinks in anchor: If the control doesnt shrink by default in anchor, and the preferred size is smaller than the current size of the control, DockAndAnchorLayout will not shrink the control. In order to shrink controls like this, turn AutoSize=true and set Size=0,0