5

According to some folks, the actual width is obtained using ActualWidth attribute as shown in the example below. It makes sense but I seem to experience contradicting behavior.

<Canvas Width="{Binding ActualWidth,ElementName=Expy}">
  <Expander x:Name="Expy"
                    HorizontalAlignment="Left"
                    Margin="0,0,0,0"
                    VerticalAlignment="Top" ...>
  ...
  </Expander>
</Canvas>

In the setup above, the behavior is consistent with the expectations and, although tightly squeezed together, the next element in the panel containing the canvas is not overlapped by it's predecessor.

However, if I change the margins to a bit wider, I can clearly see that the canvas intrude on the next element, estimatingly by the same number of pixies that I requested in the margin attribute. So it'd appear that the ActualWidth isn't the actual width but the width without the margin.

  1. Am I confusing something here and if so, what?
  2. How to obtain and bind to the actaully actual, rendered width?

enter image description here

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
  • Use a suitable layout panel, e.g. a Grid instead of a Canvas, and remove the Width binding. – Clemens Feb 06 '15 at 09:14
  • I agree with Clemens. I tend to use the Grid layout making use of RowDefinitions and ColumnDefinitions. Most of the time I don't set a width on anything, but just allow it to be sized relative to the window. – Mike Eason Feb 06 '15 at 09:35
  • 1
    Well, in my opinion it only makes sense that margins are not part of element's width/height. That's what distinguishes them from paddings. – Grx70 Feb 06 '15 at 10:31
  • @Clemens Based on what the OP was trying to achieve, there is nothing wrong with using a `Canvas` in this instance (see [this SO question](http://stackoverflow.com/q/28331013/4265041)). – Steven Rands Feb 06 '15 at 10:42
  • 1
    @MikeEason There are other considerations that limit me to the usage of canvas/expander. However, that might be changed if needed, of course, so I welcome the suggestion. However, I'm still curious as of the second part of the question - how would one go about if required to bind to the width while respecting the margins set? – Konrad Viltersten Feb 06 '15 at 10:56
  • @KonradViltersten Please see my updated answer for how you can do this while still using a canvas. – Steven Rands Feb 06 '15 at 10:57
  • @Clemens As for #1, it might be a good idea, as you suggested. However, I'm still curious of #2. How do I get to bind to the **actually** actual width, i.e. respecting the margins as well? One way I can think of (at least in theory) is to put in a panel there (so that the **margin** of the expander is, kind of, the **padding** of it) and relate to that. It seems cumbersome, though... – Konrad Viltersten Feb 06 '15 at 10:58

3 Answers3

8

The linked answer says:

ActualWidth accounts for padding and margins ...

This is incorrect. The ActualWidth includes only the padding, not the margin (same with ActualHeight).

A comment that has been left on that answer by somebody else provides the appropriate correction.


This XAML code illustrates the issue:

<Window x:Class="..."
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel>
        <TextBlock x:Name="First" Text="Some text" Padding="10" Margin="0,0"
            HorizontalAlignment="Left" Background="Yellow" />
        <TextBlock Text="{Binding ActualWidth, ElementName=First}" />

        <TextBlock x:Name="Second" Text="Some text" Padding="10" Margin="10,0"
            HorizontalAlignment="Left" Background="LimeGreen" />
        <TextBlock Text="{Binding ActualWidth, ElementName=Second}" />
    </StackPanel>
</Window>

If you run this you will see that both of the "Some text" text blocks have the same ActualWidth value despite the second one having horizontal margins applied to it.


You asked how you could take this into account. There is no way of doing this through binding because the ActualWidth property doesn't include the margin as already stated. What you can do is apply the margin to the parent element (the canvas) instead of applying it to the expander.

In other words, instead of doing this:

<Canvas Width="{Binding ActualWidth,ElementName=Expy}">
    <Expander x:Name="Expy" ... Margin="10" ... >
        ...
    </Expander>
</Canvas>

do this:

<Canvas Width="{Binding ActualWidth,ElementName=Expy}" Margin="10">
    <Expander x:Name="Expy" ... >
        ...
    </Expander>
</Canvas>
Steven Rands
  • 5,160
  • 3
  • 27
  • 56
  • Oh, +1 for the suggested work-around. I was almost going to play with additional panels and such, as described in my comment to Clemens. Your hint seems much less effort-consuming. – Konrad Viltersten Feb 06 '15 at 11:01
5

Yes Konrad. You are confusing.

Whenever we mean Actual(Height/Width), it is the rendered one. You were correct in that. However, Actual(Height/Width) values gets initialized after the WPF Layout process which includes Measure and Arrange stages and that is something you need to understand first to get to the real cause of the problem.

At first, Binding anything with Actual values will never give you desired results because by doing this you are violating WPF Layout chain. As per WPF Layout stages, in Measure stage WPF gets the specified size (for e.g. values specified in height and width) for each control in the layout and then in the Arrange stage it actually allocates controls to the layout in the best possible way. The size specified is subject to vary after the Arrange stage.

Also, it should be noted that Actual parameters include rendered size plus padding value (but not margin). In your example, I guess the other panel next to the Expander control is the reason behind the problem you reported. I can confirm only when I see the entire layout.

But as a precautionary measure, you can always stop using Actual parameters for bindings. You can definitely get it worked out using Width and Height values for binding.

  • Cool. I actually prefer not to use *ActualWidth*. I just followed a hint that was suggested in a reply to another question of mine. Perhaps I should've been more skeptic but I trusted the advice. Thanks. (Also, I think you mean that I'm confusing **something**, not that **I am** confusing, hehe.) – Konrad Viltersten Feb 06 '15 at 17:20
  • 2
    @KonradViltersten There is nothing wrong with binding to `ActualWidth` (or `ActualHeight`) in certain situations. A number of people seem to be saying that you can do what you want without binding to these properties. However, nobody has shown any code that backs-up those arguments. I could be proven wrong but I suspect you can't get the layout you want without either complicating the XAML with an outer grid element *or* explicitly setting the width of your expander control. – Steven Rands Feb 09 '15 at 10:31
1

You cannot include the margin to your ActualWidth. A solution for this would be to use DesiredSize.Width or DesiredSize.Height. This will take into account the Margin. But it's a UIElement.

TT7
  • 31
  • 10