Originally published on my old Charteris blog

I am blogging this for my own benefit as much as anyone else's because I am always forgetting this relatively simple thing.

Often in XAML you will need to specify a property by class and member such as "Image.IsMouseOver". This simple syntax is straight forward, easy to remember, and known as PropertyPath syntax. What is not so obvious is that you can move down the object hierarchy to access child properties. I needed to do this today and it is this that prompted my post.

On the Loaded event of an Image, in a DataTemplate, I wanted to rotate the image using a RotateTransform that was set on the Image's RenderTransform. It is best explained with XAML

<Image Source="{Binding Path=ImagePath}" Height="75" Width="75" StackPanel.ZIndex="2">

  <Image.RenderTransform>

    <RotateTransform Angle="0" CenterX="40" CenterY="40"/>

  </Image.RenderTransform>

  <Image.Triggers>

    <EventTrigger RoutedEvent="Image.Loaded">

      <BeginStoryboard>

        <Storyboard Duration="0:0:5">

          <DoubleAnimation Storyboard.TargetProperty="PROPERTYPATH GOES HERE"

                          To="720" AutoReverse="True">

          </DoubleAnimation>

        </Storyboard>

      </BeginStoryboard>

    </EventTrigger>

  </Image.Triggers>

</Image>

It is the Storyboard.TargetProperty that requires the path to the Angle property. As with most things these days there is more than one correct approach. The following all give the same result, in this example:

  • (Image.RenderTransform).(RotateTransform.Angle)
  • (RenderTransform).(Angle)
  • RenderTransform.Angle

Whilst 3 is concise I prefer the verbosity of 1, it also is closer to the approach needed when drilling deeper down a tree of objects. Get the syntax wrong and you will soon start seeing this error (alot!)

"Cannot resolve all property references in the property path 'Invald.PropertyPath'. Verify that applicable objects support the properties."

When there is only one transform in the Image's RenderTransform it is equal to the instance of that one transform. If there were multiple transforms the RenderTransform would be a TransformGroup with the RotateTransform being a child of that collection. If I add a SkewTransform into the mix we can see the new syntax is very close to 1. above.

<Image Source="{Binding Path=ImagePath}" Height="75" Width="75" StackPanel.ZIndex="2">

  <Image.RenderTransform>

    <TransformGroup>

      <SkewTransform AngleX="5" AngleY="5"/>

      <RotateTransform Angle="0" CenterX="40" CenterY="40"/>

    </TransformGroup>

  </Image.RenderTransform>

  <Image.Triggers>

    <EventTrigger RoutedEvent="Image.Loaded">

      <BeginStoryboard>

        <Storyboard Duration="0:0:5">

          <DoubleAnimation

            Storyboard.TargetProperty="(Image.RenderTransform).(TransformGroup.Children)[1].(RotateTransform.Angle)"

            To="720" AutoReverse="True">

          </DoubleAnimation>

        </Storyboard>

      </BeginStoryboard>

    </EventTrigger>

  </Image.Triggers>

</Image>

From my experience this is the way to specify the PropertyPath in this situation.

If you are still reading AND have some WPF experience you may well be wondering why I did not just name the RotateTransform and target it directly. Ok, I put my hands up, this is a contrived example but this situation is very real. Sometimes you cannot name all the individual parts of the visual tree you want to access. Personally I think that naming every single element you want to access to is overkill. However for those still awake here is the named target version

<Image Source="{Binding Path=ImagePath}" Height="75" Width="75" StackPanel.ZIndex="2">

  <Image.RenderTransform>

    <TransformGroup>

      <SkewTransform AngleX="5" AngleY="5"/>

      <RotateTransform Angle="0" CenterX="40" CenterY="40" x:Name="ImageRotation"/>

    </TransformGroup>

  </Image.RenderTransform>

  <Image.Triggers>

    <EventTrigger RoutedEvent="Image.Loaded">

      <BeginStoryboard>

        <Storyboard Duration="0:0:5">

          <DoubleAnimation Storyboard.TargetName="ImageRotation"

                          Storyboard.TargetProperty="Angle"

                          To="720" AutoReverse="True">

          </DoubleAnimation>

        </Storyboard>

      </BeginStoryboard>

    </EventTrigger>

  </Image.Triggers>

</Image>

See that I have named the RotateTransform "ImageRotation"and am using Storyboard.TargetName to target that element.