IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

April (2018)

(1 item)

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

WPF Attached Properties and Complexity

Friday 10 March, 2006, 01:14 PM

I've been working through the backlog (backblog?) of about 4000 unread items in my RSS reader. (I've been busy lately...) That's why I only just came across this post from Rob Relyea.

He was responding to a question someone had posted on the WPF Forum. The original question can roughly be paraphrased as:

If I want to set the X position of a rectangle, why can't I just write myRec.X = 42;? Why do I have to write Canvas.SetLeft(myRec, 42);?

Rob provides a thorough and complete answer. However, I'd like to provide a different style of answer. Just to be clear, I don't think there's anything wrong with Rob's answer. It's just not how I'd have answered it. I'd like to give a less comprehensive (although probably more verbose) answer. :-) I hope it will offer a useful alternative point of view on the problem.

Suppose It Did Work That Way...

Imagine that WPF granted the wish of the person who asked the question. Here's exactly what he asked for, as he stated it in the forum:

Why can't it be simple as myRectangle.X = value.

In a hypothetical new world where WPF offers an X property on all its elements, here's the problem:

The property wouldn't do anything in most scenarios.

Why? Because most of the time in WPF we don't use absolute positioning. WPF's layout system is designed to adapt dynamically, taking into account both the content being presented, and the available space. It works out how large to make your elements and where to put them. If you really want to defeat this feature and use absolute positioning, you need to nest your elements inside of a special panel, Canvas. The idea of a prescribed X and Y position (or Left and Top as they're actually called) only makes sense within this particular panel - it's the only panel that lets you do this.

Imagine how much confusion would be caused if these X and Y properties existed - developers all over the world would try to set these properties in contexts where they wouldn't work. Imagine the time wasted, first in discovering why the properties are being ignored, and then in cursing Microsoft for adding properties that don't work most of the time. So in practice, it makes perfect sense that the absolute positioning properties are defined as attached properties, provided by the only panel in which you can use them.

(Of course there will still be a great deal of time expended across the world as developers come to terms with why they can't just provide explicit positions for everything. However, groking the WPF layout system is a valuable learning experience. Indeed, if you've come from a more traditional Windows development background, this is one of the Big Differences you need to get your head around in order to be productive with WPF. Until you've learned this lesson, you won't be getting the best out of the platform. So I don't see it as a waste of time, unlike the exercises in futility that would be the inevitable result of adding X and Y properties to everything.)

Use Margin Instead

Even though the idea of absolute positioning is only supported in one specific scenario, WPF does offer a facility that usually lets you achieve what you want, but with a little more flexibility: all elements offer a Margin property. This lets you specify how much space you want around the edges of an element. This of course gives you some control over its position. Not the absolute power that absolute positioning provides, but in practice, it often offers exactly what you want. And in return for relinquishing a small degree of control, we get to use a powerful, extensible, automatic layout system.

Unlike Canvas.Left and its friends, Margin is not an attached property. It's an ordinary property, so it can be set using the normal property syntax of your language of choice.

The big difference between Margin and absolute positioning is that Margin is something that can be accommodated by a wide range of layout styles. While it means the same thing in all scenarios - it indicates how much space the element wants around itself - each panel accommodates the margin in its own way. In the case of a Grid it lets you control the position precisely. For example:

<Grid Background="Yellow">
  <Rectangle Fill="Red" Margin="20,10,0,0" Width="50" Height="30"
     HorizontalAlignment="Left" VerticalAlignment="Top" />
  <Rectangle Stroke="Green" Margin="30,15,0,0" Width="20" Height="30"
     HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>

It looks like this:

Two explicitly positioned rectangles

So by using Margin, we obtained precise control over position thanks to how Grid chooses to deal with margins - they are always relative to the boundary of a grid cell. (Incidentally, this is how Expression Interactive Designer, aka Sparkle, and Cider, let you control position. When you drag a UI element around to set its position, you're actually editing the Margin property.)

Because margin is a less rigid notion than absolute positioning, other panels can implement their margin handling slightly differently.

In a StackPanel margin lets you control the position absolutely in one dimension (the X position for a vertical stack panel, and the Y position in the case of a horizontal stack panel). In the other dimension, you merely get to control the amount of space between one item and the next. Here's an example:

<StackPanel Background="Yellow" Orientation="Vertical">
  <Rectangle Fill="Red" Margin="20,0,0,0" Width="50" Height="10"
             HorizontalAlignment="Left" VerticalAlignment="Top" />
  <Rectangle Stroke="Green" Margin="30,0,0,0" Width="20" Height="10"
             HorizontalAlignment="Left" VerticalAlignment="Top" />
  <Rectangle Fill="Blue" Margin="10,10,0,10" Width="40" Height="30"
             HorizontalAlignment="Left" VerticalAlignment="Top" />
  <Rectangle Fill="Brown" Margin="20,0,0,0" Width="50" Height="10"
             HorizontalAlignment="Left" VerticalAlignment="Top" />
</StackPanel> 

It looks like this:

Two explicitly positioned rectangles

All the rectangles control their exact X position through their margin. They don't have exact control over their vertical position, because the whole point of using a vertical stack panel is to save yourself the effort of specifying vertical positions manually. But note that the third rectangle down has some space above and below it. This is because it has a top and bottom margin of 10. So even when we're using a layout style where we cannot precisely control the vertical position, Margin lets us influence it.

When used in flow-style layout, such as that offered by WrapPanel, the Margin doesn't have any control over the exact position, but allows you to control the position of an element relative to its neighbours. Of course in flow layout, absolute positioning wouldn't make any sense anyway, since the whole idea is to have all the elements positioned dynamically to fill whatever space is available. The ability to tweak the position relative to where the element appears in the flow is about as much as we can expect. Here's an example:

<WrapPanel Background="Yellow">
  <Rectangle Fill="Red" Margin="20,0,0,0" Width="50" Height="10"
            VerticalAlignment="Top" />
  <Rectangle Stroke="Green" Margin="30,0,0,0" Width="20" Height="10"
            VerticalAlignment="Top" />
  <Rectangle Fill="Blue" Margin="10,10,0,10" Width="40" Height="30"
            VerticalAlignment="Top" />
  <Rectangle Fill="Brown" Margin="20,0,0,0" Width="50" Height="10"
            VerticalAlignment="Top" />
</WrapPanel>

It looks like this:

Two explicitly positioned rectangles

So even here, Margin offers some control. (For more advanced use of flow-based layouts, you could reasonably want to position some elements out of the flow and have the rest of the content flow around those elements. for example, consider text flowing around a figure. These more advanced flow-based layout scenarios are supported by the FlowDocument class. It offers a lot of control and flexibility, so simple X and Y properties wouldn't be any use there either.)

Not only does Margin render hypothetical X and Y properties unnecessary, it also offers another trick: automatic resizing. (This is similar to what the Windows Forms Anchor property lets you do.) If you don't specify an explicit Width or Height, and you set the HorizontalAlignment or VerticalAlignment properties to Stretch (which is the default), the element will be made large enough that the distance between its edges and its parent's edges is always equal to the specified margin. Here's an example using a rectangle in a grid:

<Grid Background="Yellow">
  <Rectangle Fill="Red" Margin="20,20,20,20" />
</Grid> 

In this case, we've not specified any of width, height, or alignment, so it will default to using vertical and horizontal stretching. This means the rectangle will be automatically resized so that its edges are always 20 logical pixels away from the edge of the grid, no matter what size the grid is. (Unless the grid shrinks to 40x40 pixels or smaller of course, in which case the rectangle will simply have zero size.)

No Need for X and Y

For panels where absolute positioning makes sense, Margin lets you do everything that absolute positioning properties could do, so there's no need for them. And for panels where absolute positioning doesn't make sense, there would be no use for properties to set the position. Disabling of automatic layout is a special case supported only by the Canvas. That's why there are no X and Y position properties on WPF elements.

(One last observation: given all this, why does Canvas offer these attached properties at all? Why don't we use Margin there too? I think the answer is that Canvas.SetLeft and friends are all working at a lower level of abstraction than Margin, so it's appropriate that they look different.)

[Update, later that day: Thanks to Shawn Van Ness for pointing out to me that Canvas does actually support Margin despite what I thought I'd found through experimentation... He would also like me to mention that InkCanvas also offers the same absolute positioning model as Canvas, but as a bonus it lets you scribble all over it. Is that better Shawn? :)]

Copyright © 2002-2024, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk