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>
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.
I think your program could really use a tab navigation with a subnavigation beneath it. And maybe use the colors red and blue all over the place.
Just a suggestion…
That had been my initial thought. I’m glad I’m not alone in it.