Get ActualWidth and ActualHeight in Silverlight

October 17, 2008 07:27 by wjchristenson2

You may often encounter situations where you need to acquire the size of a Silverlight object and the Width, Height, ActualWidth, ActualHeight property values are not set.  This can occur if the Width & Height are set to Auto, if the object being referenced is in the process of being added dynamically, or it can occur in a myriad of other circumstances.

Let's assume that we have a Canvas object as our root element for our Silverlight application.  It's Width and Height are set to Auto.  As its parent is resized, the Canvas object will grow & shrink accordingly.  Now if we want to acquire the Canvas size to do some placement logic of Canvas child elements, we run into our problem.

My first approach was to investigate why ActualWidth and ActualHeight were not telling me (the developer) the ... uh... actual width and actual height.  You'd think the property names would say it all, but they don't.  They actually "get or set the rendered width/height of a FrameworkElement."  Ok, that's easy enough.  We'll just wait until the objects are rendered before we position them.  So, I figured I'd hook into my Silverlight's Loaded event.  At this point all objects should be "loaded" and I can acquire the actual size of my Canvas.  Wrong!  ActualWidth and ActualHeight are calculated based on the Width/Height property values and the layout system.  There is no guarantee as to when these values will be "calculated".  So I've found 2 ways to workaround this issue.

Dispatcher.BeginInvoke()
This approach executes a specified delegate asynchronously on the thread the Dispatcher is associated with.  Measurement and layout passes run in Silverlight asynchronously.  Therefore our ActualWidth and ActualHeight object properties can be set AFTER we actually need them.  Using BeginInvoke() ensures that our ActualWidth and ActualHeight calculations are done as we programmatically access them.  Here is a sample use of .BeginInvoke() and the delegate.

'ActualWidth and ActualHeight are calculated values and may not be set yet
'therefore, execute GetLayoutRootActualSize() asynchronously on the thread the Dispatcher is associated with
Me.Dispatcher.BeginInvoke(AddressOf GetLayoutRootActualSize)

Private Sub GetLayoutRootActualSize()
    Me.tbxInvoke.Text = Me.LayoutRoot.ActualWidth.ToString() & ", " & Me.LayoutRoot.ActualHeight.ToString()
End Sub

_SizeChanged Event
My preferred method of accessing ActualWidth and ActualHeight in these situations is by hooking into the FrameworkElement's SizeChanged event.  This event is fired when the ActualWidth and ActualHeight values change for a FrameworkElement.  So let's say we have an element which we need to position based on the size of it's parent.  We can hook into the parent's SizeChanged event and position the child element on the fly.  The ActualWidth and ActualHeight properties of the parent would be present at this time.  Here is a snipped of SizeChanged event handling.

Private Sub LayoutRoot_SizeChanged(ByVal sender As Object, ByVal e As System.Windows.SizeChangedEventArgs) Handles LayoutRoot.SizeChanged
    Me.tbxSizeChanged.Text = Me.LayoutRoot.ActualWidth.ToString() & ", " & Me.LayoutRoot.ActualHeight.ToString()
End Sub

I've uploaded an example using my 3 tests (page Loaded, BeginInvoke(), and _SizeChanged).  Hope it helps!


ActualWidthHeight_Soln.zip (586.38 kb)

Bookmark and Share