47

I have a Grid inside a Canvas defined like this:

<Canvas x:Name="outerCanvas">
    <Grid Grid.Row="1" Name="cGrid" ShowGridLines="True" Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type Canvas}}}" Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource AncestorType={x:Type Canvas}}}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition  />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Rectangle Name="rectangle1" Stroke="Black" Fill="AntiqueWhite" />
        <Rectangle Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="1" Grid.RowSpan="1" Name="rectangle2" Stroke="Black" Fill="AliceBlue" />
        <Rectangle Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="1" Grid.RowSpan="1" Name="rectangle3" Stroke="Black" Fill="Aqua" />
        <Rectangle Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Grid.RowSpan="1" Name="rectangle4" Stroke="Black" Fill="DarkViolet" />
    </Grid>
</Canvas>

My problem is that, on the Window constructor, after InitializeComponents() either Grid.ColumnDefinitions[0].ActualWidth or "any rectangle".ActualWidth are all set to 0.0 (the same for heights). I'm not figuring out what to do to get this information. Any help?

Observations:

  1. I'm not defining the outer canvas width and height but if I do, it doesn't solve my problem.
  2. At runtime I can see that this Canvas/Grid occupies the entire window space, so every rectangle inside it has ActualWidths and ActualHeights
  3. The grid's width/height is bound to the canvas but I tried removing this binding and my problem still persists.
Sibbo
  • 3,796
  • 2
  • 23
  • 41
Andre Pena
  • 56,650
  • 48
  • 196
  • 243

3 Answers3

83

ActualHeight and ActualWidth are not set until the control is measured and arranged. Usually there is nothing in InitializeComponent() that causes a measure, so when it returns these will still be zero.

You can force these to be computed earlier by simply calling the window's Measure() and Arrange() methods manually after the window's InitializeComponent() returns.

If you are sizing to content:

window.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
window.Arrange(new Rect(0, 0, window.DesiredSize.Width, window.DesiredSize.Height));

If you are using an explicit window size:

window.Measure(new Size(Width, Height));
window.Arrange(new Rect(0, 0, window.DesiredSize.Width, window.DesiredSize.Height));
Jim Balter
  • 16,163
  • 3
  • 43
  • 66
Ray Burns
  • 62,163
  • 12
  • 140
  • 141
  • 1
    Thanks! Just a little correction: window.Measure returns void. It changes the DesiredWidth/DesiredHeight properties :) But your idea is right. – Andre Pena Nov 08 '09 at 18:10
  • 1
    That's what happens when you type code off the top of your head without double-checking the APIs. Thanks for the correction. MeasureOverride returns the size, but Measure just stores them. Answer corrected. – Ray Burns Nov 08 '09 at 22:27
70

Ray is correct (+1) that this is due to the fact that the measure and arrange pass has not executed yet. However, rather than force another layout pass (expensive), you can just wait until your control has loaded before accessing the ActualXxx properties:

public MyWindow()
{
    Loaded += delegate
    {
        // access ActualWidth and ActualHeight here
    };

}
Kent Boogaart
  • 175,602
  • 35
  • 392
  • 393
  • 2
    Agree. Waiting for Loaded is the better approach IMHO. – Drew Marsh Nov 08 '09 at 14:46
  • Thanks! Just curious... Why there's no an OnLoaded method on Window? – Andre Pena Nov 08 '09 at 18:11
  • 4
    Waiting for Loaded is a good idea (+1), and will work in most cases. But note that when you never connect to a PresentationSource (ie. you never show the window), Loaded will never be called. I've run into this before, for example when deciding whether or not to include a control on a page. So I would say using Loaded is better when it is possible and covers all cases, otherwise using Measure/Arrange is better. That said, in 90% of the cases best practice will be to stay away from ActualWidth/ActualHeight completely. (eg. more/beter panels, binding to height/width, MeasureOverride) – Ray Burns Nov 08 '09 at 22:34
  • Tried both, WAAAAAAYYYYYYYY better. – Lee Louviere Jun 11 '12 at 18:45
  • @RayBurns, my loaded is not called, as I ran into the 10%. Is there a way to call it (beats re-writing in IMO). – ericosg Jun 04 '15 at 15:30
  • Outstanding answer. Thanks for sharing! – DelusionX Dec 04 '20 at 08:57
3

In our case the solution was simple, as everybody said ActualWidth and ActualHeight needed to get called after the Loaded even completes, So we just wrapped the code in a dispatcher and set the priority to Loaded as below:

Dispatcher.Invoke(new Action(() =>
{
   graphHeight = ActualHeight;
   graphWidth = ActualWidth;
}), DispatcherPriority.Loaded);
Gabriel
  • 595
  • 7
  • 10
  • this solutions do help me. I get the actual size from same thread and it return zero and NaN. I take this code and place it after the thread, and it show the results accordingly. – Luiey Mar 26 '19 at 08:41