Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Book Excerpt: The Silverlight Layout System

Pete Brown - 26 March 2010

I'm on the road to finishing up Silverlight in Action, Revised edition. This is really turning out to be the definitive Silverlight book. I hope you all enjoy reading it as much as I have enjoyed writing it.

One interesting thing that has happened is that the book has almost doubled in size from the previous edition. I keep running across things that I just think are really important for Silverlight developers to understand. While the book doesn't cover every little nook and cranny in Silverlight, I do try and make sure there aren't too many shadowy corners.

One topic that I felt deserved brief, but good, treatment, is the layout system in Silverlight. Tonight, I'm wrapping up the text for chapter 6 which, among other things (frameworkelement, uielement, panels, transforms), covers that topic. Below is an (unreviewed and unedited) excerpt from that chapter.

6.2 The Layout System

Layout systems across different technologies vary greatly in complexity. Take, for example, the Windows Forms layout system. Fundamentally, that layout system involves absolute x and y coordinate pairs, and an explicit or implicit z-order. Controls can overlap each other, get clipped on the edge of the window, or even get obscured completely. The algorithm is pretty simple - sort by zorder and then blit the bits to the screen.

For another example, look to HTML and CSS. HTML and CSS support elements that must size to content and page constraints (tables, divs), as well as support absolute positioning, overlapping etc. It's more of a fluid approach, where the size and position of one element can affect the size and position of another. Therefore the layout system for HTML and CSS is significantly more complex than that for something like Windows Forms.

Silverlight and WPF support both types of layout: content that self-sizes based on constraints, and content that is simply positioned by way of an x and y coordinate pair. Depending on the container in use, it can even handle laying elements out on curves or radially from a central point. The complexity that makes that possible deserves a deeper look.

6.2.1 Multi-Pass Layout - Measuring and Arranging

Layout in Silverlight and WPF involves two primary passes: the Measure pass and the Arrange pass. In the Measure pass, the layout system asks each element to provide its dimensions given a provided available size. In the Arrange step, the layout system tells each element its final size and requests that it lay out itself and its child elements. A full run of measuring and arranging is called a Layout Pass.

In this section we'll go through the layout system in more detail, especially around these two key steps and their implications for performance and design. If you're curious about layout, or you've ever been confused by something like Height and Width vs. ActualHeight and ActualWidth, read on.

The Measure Pass

Whenever elements need to be rendered to screen due to having just been added, made visible, or changed in size, the layout system is invoked for an asynchronous layout pass. The first step of layout is to measure the elements. On a FrameworkElement, the Measure pass is implemented inside the virtual MeasureOverride function, called recursively on the visual tree:

protected virtual Size MeasureOverride(Size availableSize)

The availableSize parameter contains the size available for this object can give to its self and child objects. If the FrameworkElement is to size to whatever content it has, the availableSize will be double.PositiveInfinity.

The function returns the size the element requires, based on any constraints or sizes of child objects.

Height and Width vs. ActualHeight and ActualWidth

If you don't explicitly set the height and width properties of a control, the ActualHeight and ActualWidth properties may be zero. Why is that? Due to the asynchronous nature of the layout pass, ActualHeight and ActualWidth may not be set at any specific point in time from run to run, or more importantly, may actually change their values over time as the result of layout operations.

ActualHeight and ActualWidth are set after the rendering pass, and may also be affected by layout rounding settings or content.

In short, check them, and if they are zero, they haven't been set. If you want a single place where you can guarantee they'll have a value, subscribe to the LayoutUpdated event on the element, and check them there.

The Arrange Pass

The second pass of layout is to arrange the elements given their final sizes. On a FrameworkElement, the Arrange is implemented inside the virtual ArrangeOverride function, also called recursively:

protected virtual Size ArrangeOverride(Size finalSize)

The finalSize parameter contains the size (area within the parent) this object should use to arrange its self and child objects. The returned size must be the size actually used by the element, and may be smaller than the finalSize passed in.

At the end of the arrange pass, Silverlight has everything it needs to properly position and size each element in the tree. However, it doesn't have everything it needs to actually display the element, as its render position or size could be affected by a render transform, as covered in the previous section.

Layout Completed

Despite the name, the LayoutCompleted event isn't technically part of the Layout pass. Instead, it is fired as the last event before an element is ready to accept input. LayoutCompleted is the safe location for inspecting the actual size and position of the element, or otherwise responding to changes in same.

Do not do anything in LayoutCompleted that would cause another layout pass. For example, don't change the size or position of an element, modify its contents, change its layout rounding, or otherwise manipulate properties that could change the size of the elements bounding box.

6.2.2 The LayoutInformation Class

The LayoutInfomation class in System.Windows.Controls.Primatives contains a few methods that are useful to folks implementing their own MeasureOverride and ArrangeOverride code. Specifically, GetLayoutSlot and GetLayoutClip are helpful when hosting child elements in a custom panel.

GetLayoutSlot

Regardless of its actual shape, each visual element in Silverlight can be represented by a bounding box, or layout slot. This is a rectangular shape that takes into account the elements size and any margins and padding or constraints in effect. Figure 6.n shows the relationship between a layout slot and the child element hosted in a panel.

clip_image002

Figure 6.n The relationship between the layout slot and the child element, for an element smaller than the slot

The layout slot is the maximum size to be used when displaying an element. Portions of the element that fall outside the slot will be clipped. To see the layout slot for an element, you can call the static function GetLayoutSlot

public static Rect GetlayoutSlot(FrameworkElement element)

The returned rect will contain the bounding box or layout slot for that element. This return value can be useful when creating a custom panel, or when debugging layout issues.

GetLayoutClip

Sometimes elements may be larger than their layout slots, even after measuring and arranging have attempted to fit them. When that happens, you have a layout clip that represents the intersection of the child element's size and the layout slot.

Figure 6.n shows the relationship between the layout slot, the child element and the layout clip for that child element, in an instance where the child element is too large for its slot.

clip_image004

Figure 6.n The relationship between the layout clip and the layout slot for a child element too large for its slot

The function GetLayoutClip returns the intersection that represents the layout clip. In this case, the function returns an actual geometry object, useful for setting the clip geometry for an element should you need to.

public static Geometry GetLayoutClip(FrameworkElement element)

The returned Geometry contains the intersection, or null if the element was not clipped. It should be noted that in WPF, the GetLayoutClip method has a counterpart by the same name that actually resides on the UIElement, and takes in a slot size, and returns clip geometry.

6.2.3 Performance Considerations

Layout is a recursive process; trigging layout on an element will trigger layout for all the children of that element, and their children and so on. For that reason, you should try and avoid triggering layout for large visual trees as much as possible. In addition, when implementing your own MeasureOverride or ArrangeOverride code, make sure it is as efficient as possible.

Virtualization

An example of this has to do with large collections of children in controls like lists and grids. Drawing the elements takes a certain amount of time, but that only happens for elements that are on-screen. Creation of the CLR objects representing the items also takes a certain amount of time. Most importantly for us, Measure and Layout happen for all children, regardless of their potential position on screen or off. Therefore, if you have a thousand elements in a listbox, MeasureOverride and ArrangeOverride will be called for each of them. More importantly, if those elements contain children (as is often the case with item templates), you'll have even more calls in the layout passes.

One solution to this is Virtualization. A subset of the built-in controls (such as the DataGrid) support UI virtualization. For those, pre-created elements are re-used with new data. The end result is a reduction in the number of in-memory elements, as well as a reduction of MeasureOverride and ArrangeOverride calls.

Sizing and Positioning

Another performance consideration has to do with sizing and positioning elements. For example, if you change the margin of an element, or modify its width or height, you will trigger a layout pass. However, if you instead call a render transform to either move or resize that element, you will not trigger a pass.

While a render transform won't always do what you want it to (after all, it doesn't affect layout), it can be a real performance plus when used with animation.

Understanding the layout system helps take some of the mystery out of what happens when you size elements in Silverlight, and they don't quite do what you might have expected them to do. It's also a key concept to understand if you plan to implement your own panels/container controls.

WPF has the concept of a layout transform. This type of transform is parallel to a render transform, but triggers a layout pass. As we've seen here, trigging a layout pass can be an expensive operation, especially if done inside an animation. For performance considerations, and due to their relatively low adoption, layout transforms were omitted from Silverlight.

The render transforms provided by Silverlight, however, are almost always adequate to solve problems we used to solve with layout transforms - and often superior. Let's take a look at them next.

 

 

There, I hope that was useful to folks hoping to get a little deeper insight into Silverlight. The book will be published this summer, but MEAP subscribers get access to the chapters as soon as they are released for tech editing. There are several chapters up already.

       
posted by Pete Brown on Friday, March 26, 2010
filed under:        

3 comments for “Book Excerpt: The Silverlight Layout System”

  1. Joesays:
    Hi Pete,

    Excellent information. Thank you for publishing the MEAP link, I just signed up to get what was there and look forward to the other chapters as they are published. (Oddly enough, I like the idea of this since I can read the book in smaller bite-sized pieces.)

    When I initially started working with Silverlight (on a live project), I had existing Winforms components that I needed to re-write using the new WPF/E paradigm. Instead of learning XAML proper, I ended up writing a bunch of extension methods that simulated Winforms drawing calls that would create instances of the appropriate objects and add them to the child of the LayoutRoot. (i.e. Lines, Rectangles, etc.) During the exercise, I ran into a lot of issues where I had to "Invalidate" the layout and redraw everything (this is probably making you and every other WPF developer cringe) and ran into several cases where items had not yet been measured and other elements depended upon their Width and Height properties (or the actual versions of those) being set and causing rendering exceptions. Thankfully, learning XAML proper helped a lot.

    (Off Topic: In section 3.2 of the book where you mention the removed Silverlight ASP.NET Silverlight control, you may want to add a small blurb about how to do this with a ported application. Basically removing the existing .aspx and .html test pages from the project, removing the Silverlight application from the Web project, then re-adding the Silverlight application and responding affirmatively when asked to generate test pages. You then get a new .aspx page with use of the object tag.)

    Thank you again for providing such a great resource to the Silverlight Community.

    Regards,

    Joe
  2. Chris Bordemansays:
    That layout system seems to preclude forcing a child to size to the container's actual size, because you can't know the container's size beforehand.

    Here's a scenario I can't seem to figure out. DataGrids, when placed in a ScrollViewer, tend to grow as large as they want, so if there are a hundred rows the datagrid will simply grow to show all. A similarly crazy thing happens horizontally if you use star sizing: the datagrid grows to infinite width (though SilverLight wisely cuts it off at 10,000).

    So I've tried creating a derived DataGrid control, a derived ContentControl, even tried reimplementing ScrollViewer (cuz it's sealed). Nothing seems to work right (I get strange behaviors, failure to redraws, disappearing controls).

    Any idea on how to fix this particular problem? You seem to be the layout master. If you can't solve it, I don't see it getting solved!

    Thanks!

Comment on this Post

Remember me