Bind to Method within a DataTemplate
Posted
Tuesday, June 12, 2007 2:35 PM
by
cromwellryan
One of my play projects with WPF is a photo viewer for the pictures we put out on http://cromwellhaus.com. One of the views is a montage of the latest photos with a random RotateTransform Angle applied to each image's RenderTransform. Getting the view itself set up as cake, but when I attempted to apply the random angle to each image, it wasn't so random.
Here's what happened and how to actually accomplish such a task:
I started out with an ItemsControl, rather than a ListView as most examples show, using a UniformGrid as the ItemsPanel.
<ItemsControl ItemTemplate="{StaticResource photoItem}" ItemsSource="{Binding Source={StaticResource dpPhotos}, XPath=/rss/channel/item}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I bound the ItemsSource to an XmlDataProvider pointing to the photos RSS feed for Ryan Jr. pictures.
<XmlDataProvider x:Key="dpPhotos" Source="http://cromwellhaus.com/photos/baby/pictures_rss.aspx?Tags=Ryan+Jr&AndTags=1" XmlNamespaceManager="{StaticResource rssMapping}"/>
I run our site using Community Server 2007 Express Edition which provides RSS meta extensions for more detailed feeds. This required that I apply an XmlNamespaceManager to define these extension namespaces. That's what this is for:
<XmlNamespaceMappingCollection x:Key="rssMapping">
<XmlNamespaceMapping Uri="http://search.yahoo.com/mrss" Prefix="media" />
</XmlNamespaceMappingCollection>
Here is the DataTemplate used by the ItemsControl to display/render each image. You'll see this referred to in above as {StaticResource photoItem}:
<DataTemplate x:Key="photoItem">
<Image Margin="12,12,12,12" Source="{Binding Mode=Default, XPath=media:thumbnail/@url}" ToolTip="{Binding XPath=title}" Width="{Binding Mode=Default, XPath=media:thumbnail/@width}" Height="{Binding Mode=Default, XPath=media:thumbnail/@height}" />
</DataTemplate>
You can see the use of the XmlNamespaceMapping in the XPath=media:thumbnail/@url. That had me stumped for a few seconds, but opening the project up in Expression Blend and re-adding the XmlDataProvider created the Mappings for me. (Sidebar: VS 2008 does do this for you - thank goodness)
At this point, we are displaying thumbnails and we are certainly aware that I make poor color choices, but we're on our way.
Applying the RotateTransform is easy...
<Image Margin="12,12,12,12" Source="{Binding Mode=Default, XPath=media:thumbnail/@url}" ToolTip="{Binding XPath=title}" Width="{Binding Mode=Default, XPath=media:thumbnail/@width}" Height="{Binding Mode=Default, XPath=media:thumbnail/@height}">
<Image.RenderTransform>
<RotateTransform Angle="10" />
</Image.RenderTransform>
</Image>
...and it's almost as easy to use the System.Random class to generate the angle. Just add the following ObjectDataProvider as a Window or Application Resource and bind the Angle property to it:
<ObjectDataProvider x:Key="randomAngle" ObjectType="{x:Type system:Random}" MethodName="Next">
<ObjectDataProvider.MethodParameters>
<system:Int32>-12</system:Int32>
<system:Int32>12</system:Int32>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
...
<RotateTransform Angle="{Binding Source={StaticResource randomAngle}" />
Well when you do this, you'll find that you get the same Angle for every image. This is because you are actually binding to a single instance of the ObjectDataProvider. To resolve this we actually have to embed the ODP in the transform itself as so:
<RotateTransform>
<RotateTransform.Angle>
<Binding>
<Binding.Source>
<ObjectDataProvider ObjectType="{x:Type system:Random}" MethodName="Next">
<ObjectDataProvider.MethodParameters>
<system:Int32>-12</system:Int32>
<system:Int32>12</system:Int32>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Binding.Source>
</Binding>
</RotateTransform.Angle>
</RotateTransform>
Tada!
Now you will find that your angle's aren't terribly "Random". This is because you are actually asking for a new instance of the Random class each time. You can tell the ODP within the DataTemplate to use the same instance each time by adding this to the Window Resources:
<ObjectDataProvider x:Key="randomAngle" ObjectType="{x:Type system:Random}"/>
and modifying the Angle binding to the following, telling the transform ODP to use a specific resource instance rather than just giving it a type to instantiate each time.
<ObjectDataProvider ObjectInstance="{StaticResource randomAngle}" MethodName="Next">
<ObjectDataProvider.MethodParameters>
<system:Int32>-12</system:Int32>
<system:Int32>12</system:Int32>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
You can download a mini-version used to write this post
here.