(1 item) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(6 items) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(4 items) |
|
(2 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(2 items) |
|
(5 items) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(1 item) |
|
(3 items) |
|
(1 item) |
|
(1 item) |
|
(2 items) |
|
(8 items) |
|
(2 items) |
|
(7 items) |
|
(2 items) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(1 item) |
|
(2 items) |
|
(4 items) |
|
(1 item) |
|
(5 items) |
|
(1 item) |
|
(3 items) |
|
(2 items) |
|
(2 items) |
|
(8 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(6 items) |
|
(1 item) |
|
(2 items) |
|
(5 items) |
|
(5 items) |
|
(7 items) |
|
(3 items) |
|
(7 items) |
|
(16 items) |
|
(10 items) |
|
(27 items) |
|
(15 items) |
|
(15 items) |
|
(13 items) |
|
(16 items) |
|
(15 items) |
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.
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.)
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:
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:
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:
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.)
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? :)]