Contents
This article is based on explaining the .NET 3.0 WPF application that was first published with the 3rd part of my LINQ series. There has been no change from the original code posted with the 3rd part of my LINQ series article. It's just that some folks may not understand the XAML/WPF implementation that was included there, so that is what this article is all about.
For those of you that have not read that article, or even that series I suggest you start there, as there is some fundamental information about LINQ, that you should be aware of first, such as the LINQ standard query operators, and the use of IEnumerable
. You can probably just read Part 1 and Part 3, to understand what is going on with the XLINQ stuff in this article. But if you do database development, Part 2 will certainly be of interest.
- Part 1 : was all about standard LINQ, which is used to query in memory data objects such as List, arrays etc
- Part 2 : was all about using DLINQ, which is LINQ for database data
- Part 3 : was all about using XLINQ, which is LINQ for XML data
The basic idea behind this application is that it is a WPF application that also uses XLINQ to request certain parameterized RSS Feeds from Flickr to obtain XML data, that is then used to bind to a WPF control. The application also demonstrates templates / resources / animations.
Before I start bombarding people with the inner mechanisms of the attached WPF application, we will have a quick look at the finished product.
The application is based on using Visual Studio 2005 with The Visual Studio Designer for WPF installed, or using Expression BLEND and Visual Studio 2005 combination, or WordPad if you prefer to write stuff in that.
Obviously as it is WPF, you will also need the .NET 3.0 framework, and for the XLINQ to work you will also need the May 2006 LINQ CTP which is available here.
This application although fairly simple demonstrates quite a few key WPF/XAML concepts, such as :
- Animations
- Data binding
- Templates
- Styles
- Resources
- XLINQ usage, of course
This application is not really that orientated to XLINQ, but it is something that I just wanted to try, so I thought, as I've done it, why not include it here.
- To run the code supplied with this article you will need to install the May 2006 LINQ CTP which is available here, there is a new March 2007 CTP available, but it's about 4GB for the full install (as its not just LINQ but the entire next generation of Visual Studio codenamed "Orcas") and quite fiddly, and probably going to change anyway, so the May 2006 LINQ CTP will be OK for the purpose of what this article is trying to demonstrate.
- The .NET 3.0 framework which is available for download here
There are 2 Windows that make up the attached demo application. One contains all the images retrieved from the Flickr RSS feed, and the other Window shows a single image that the user clicked on. The Window with the single image contains several controls that the user may use to alter the shown image.
In order to fully understand how each of the 2 Windows work, they will be explained one at a time.
Window1 (frmLoader.xaml)
Recall that the first Window (code file is frmLoader.xaml / frmLoader.xaml.cs) looks like the figure below:
So how did we end up with that sort of layout, well let's take another look at this screen shot, but with some annotations that might help explain it.
As you can see the application contains several StackPanel controls and several Grid controls. Panels and Grids serve a very important role in layout design within WPF, just as docking and anchoring do within Windows Forms applications. It is by using these panels that specialized layouts can be realized. They are fairly easy to use, a Grid has rows and columns. And a StackPanel either stacks controls vertically or horizontally. Of course there is much more to these controls than just this simple usage, I shall leave it as an exercise for the reader to further investigate these controls.
This layout is all done in markup (XAML), so let's have a look at that:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xml:lang="en-US"
xmlns:d="http://schemas.microsoft.com/expression/blend/2006"
x:Class="ImageTest.frmLoader"
x:Name="Window"
Title="Flickr-XLINQ-WPF Test"
WindowStartupLocation="CenterScreen"
Width="700" Height="600">
<Grid x:Name="MainGrid" Background="Black">
<Grid.Resources>
//omitted for later discussion
</Grid.Resources>
<Grid.RowDefinitions>
-->
<RowDefinition Height="Auto" />
-->
<RowDefinition Height="*" />
-->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
-->
<StackPanel Grid.Row="0" x:Name="pnlHeader" HorizontalAlignment="Left"
Height="130" Width="600" Orientation="Horizontal"
Background="#FF363636">
<Image Source="Resources/FlickrBig.png" Margin="0,0,0,0" Width="350"
Height="70" VerticalAlignment="Top" />
<StackPanel x:Name="pnlSearching" HorizontalAlignment="Left"
Height="Auto" Orientation="Vertical">
-->
<StackPanel HorizontalAlignment="Left" Height="Auto"
Orientation="Horizontal" Margin="0,10,0,0">
<Label HorizontalAlignment="Left" Margin="20,0,0,0"
VerticalAlignment="Center"
Content="Enter Search Type" FontSize="11" FontWeight="Bold"
Foreground="White"/>
<ComboBox x:Name="cmbSearchType"
xmlns:Margin=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Margin="4,5,0,0" Style="{DynamicResource cmbStyle}"
VerticalAlignment="Center" Width="150" Height="20"
Background="#FFFFFFFF" FontSize="11" Foreground="#FF000000"
IsSynchronizedWithCurrentItem="True">
<ComboBoxItem>Most Recent</ComboBoxItem>
<ComboBoxItem>Interesting</ComboBoxItem>
<ComboBoxItem>By Search Word</ComboBoxItem>
</ComboBox>
<Button x:Name="btnSearch" Width="30" Height="20"
Foreground="#FFFFFFFF" Template="{DynamicResource GlassButton}"
Margin="10,5,0,0" HorizontalAlignment="Left"
VerticalAlignment="Top" Content="Go"
ToolTip="Search Flickr using current Search Type"/>
</StackPanel>
-->
<StackPanel x:Name="pnlSearchWord" HorizontalAlignment="Left"
Height="Auto" Orientation="Horizontal" Margin="0,5,0,0">
<Label HorizontalAlignment="Left" Margin="20,0,0,0"
VerticalAlignment="Center" Content="Enter Search Word"
FontSize="11" FontWeight="Bold" Foreground="White"/>
<TextBox x:Name="txtSearchWord" Margin="0,5,0,0"
Style="{DynamicResource txtBoxStyle}" TextWrapping="Wrap"
HorizontalAlignment="Left" VerticalAlignment="Center"
Width="150" Height="20" Background="#FFFFFFFF"
Foreground="#FF000000" FontSize="11"/>
</StackPanel>
</StackPanel>
</StackPanel>
-->
<Grid x:Name="ItemsGrid" Grid.Row="1">
<Grid.Background>
<ImageBrush ImageSource="Resources/bg.png" Opacity="0.45" >
</ImageBrush >
</Grid.Background>
<Grid.RowDefinitions>
-->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
-->
-->
<StackPanel x:Name="pnlContents" HorizontalAlignment="Left"
Height="Auto" Width="Auto" Orientation="Vertical">
</StackPanel>
</Grid>
-->
<StackPanel Grid.Row="2" x:Name="pnlNextPrev" HorizontalAlignment="Left"
Height="Auto" Orientation="Horizontal" Background="Black">
<Image Source="Resources/Flickr.png" Margin="10,2,0,0" Width="300"
Height="30"/>
<Button x:Name="btnPrev" Width="80" Height="20" Foreground="#FFFFFFFF"
Template="{DynamicResource GlassButton}" Margin="10,10,0,10"
HorizontalAlignment="Left" VerticalAlignment="Top" Content="Prev">
</Button>
<Button x:Name="btnNext" Width="80" Height="20" Foreground="#FFFFFFFF"
Template="{DynamicResource GlassButton}" Margin="10,10,0,10"
HorizontalAlignment="Left" VerticalAlignment="Top" Content="Next">
</Button>
<Label x:Name="lblPageIngex" HorizontalAlignment="Left"
Margin="15,0,0,0" VerticalAlignment="Center" FontSize="16"
FontWeight="Bold" Foreground="DarkGray" FontFamily="Arial"/>
</StackPanel>
</Grid>
</Window>
This matches what I previously showed in the annotated diagram above.
Those new to WPF may be interested to know that anything that can be done in XAML can also be done in the separate code behind. This code behind file model is fairly similar to the ASP.NET idea, where the .xaml file holds the design as markup (XAML) and the .xaml.cs holds the code, such as events. Though you can do just as much in code as you can in XAML, typically I guess a normal application will be a combination of both.
As I stated above the .xaml file contains various controls, such as next and previous buttons. These controls are created in the .xaml file, but as long as they have an c:Name
they can be used within the code behind file. I am not going to go into every little detail about how the code behind file works, but I will be explaining some of the core WPF techniques that are used to make this application.
To be honest a lot of what I would probably have said here is going to have already been covered by Josh Smith in his excellent Guided Tour WPF series here at CodeProject. So I would strongly suggest that you also read that series, starting with this article. Why learn from the monkey (me), when you can learn from the organ grinder (Josh). Josh really is the WPF man, in my humble opinion. I mean he's been offered a job, and been given MVP status by Microsoft for his WPF efforts, um lets think, yep he knows his stuff.
But all that said, I am still going to tell you how I used these certainly WPF features, and why I did so.
WPF introduces even better binding than we previously had with Windows Forms applications. In this attached application, I wanted to get an RSS feed of images from Flickr, and then have the results shown in some sort of WPF control.
Having looked into databinding in some previous exercises, I knew that the ListBox would be able to bind to XMLDataProvider using the DataBinding class. So that's what lead me into looking at using XLINQ to firstly fetch an RSS feed, then use XLINQ projection to create a new structure that was then saved to a local XML file. The local XML file is then used to create a new DataBinding which can then be used as a binding source for a ListBox control.
The following code is used within the code behind file to create a DataBinding that is bound to a ListBox.
private void getFlickrData(string searchType,string searchWord)
{
if (RSSImages.LoadPictures(searchType, searchWord))
{
lblPageIngex.Visibility = Visibility.Visible;
int pIndex = RSSImages.PageIndex;
lblPageIngex.Content = "Page " + (++pIndex);
XmlDataProvider xmldata = new XmlDataProvider();
xmldata.Source = new Uri(@"c:\photos.xml", UriKind.Absolute);
Binding binding = new Binding();
binding.Source = xmldata;
binding.XPath = @"/photos/photo";
BindingOperations.SetBinding(lstXMLData, ListBox.ItemsSourceProperty,
binding);
lstXMLData.Visibility = Visibility.Visible;
setNextPrevStates(RSSImages.IsPrevAvail, RSSImages.IsNextAvail);
}
else
{
lblPageIngex.Visibility = Visibility.Hidden;
lstXMLData.Visibility = Visibility.Hidden;
setNextPrevStates(false, false);
}
}
It can be seen that this code actually makes use of a call to another method that actually fetches the RSS feed from Flickr and creates the local XML in the first place. That code is part of LINQ Preview Project included with the downloadable zip at the top of this article.
public bool LoadPictures(string searchType, string searchWord)
{
switch (searchType)
{
case "Most Recent":
this.url=MOST_RECENT;
break;
case "Interesting":
this.url=INTERESTING;
break;
case "By Search Word":
this.url = ENTER_TAG + searchWord;
break;
default :
this.url = MOST_RECENT;
break;
}
try
{
var xraw = XElement.Load(url);
var xroot = XElement.Parse(xraw.Xml);
var photos = (from photo in xroot.Element("photos").Elements("photo")
select new PhotoInfo
{
Id = (string)photo.Attribute("id"),
Owner = (string)photo.Attribute("owner"),
Title = (string)photo.Attribute("title"),
Secret = (string)photo.Attribute("secret"),
Server = (string)photo.Attribute("server"),
Farm = (string)photo.Attribute("Farm"),
}).Skip(pageIndex * columns * rows).Take(columns * rows);
int count = photos.Count();
if (pageIndex == 0)
{
this.prevAvail = false;
this.nextAvail = true;
}
else
{
this.prevAvail = true;
}
if (count < columns * rows)
{
this.nextAvail = false;
}
XDocument photosDoc = new XDocument();
XElement photosElement = new XElement("photos", from pi in photos
select new XElement("photo",
new XElement("url", pi.PhotoUrl(false)),
new XElement("title", pi.Title))
);
photosDoc.Add(photosElement);
photosDoc.Save(@"c:\photos.xml");
return true;
}
catch (Exception ex)
{
return false;
}
}
Well this is all pretty cool isn't it. We've used XLINQ to go and fetch an RSS feed, then used XLINQ to get the data into a format we want to use, then used XLINQ to save that new format to a local XML file. Then we've used WPF data binding to bind an XML file to a ListBox.
But just how does the ListBox know how to display the XML data. All we said originally about the XML data was the XPath that got us the correct section of the XML file.
....
binding.XPath = @"/photos/photo";
BindingOperations.SetBinding(lstXMLData, ListBox.ItemsSourceProperty, binding);
....
It turns out this is only half the story, we need to also look into another WPF feature called Style. So let's look at that.
If I told you this was simply a ListBox, would you be surprised?
The correct answer, just in case you were in any doubt should be "Hell Yeah, that ain't no freekin ListBox, man. Game over man, It can't be". Well actually yes it can. Recall that we set the ListBox to have a DataBinding. Well now we are going to see how that XML data is used to create this cool (Style-ish) ListBox . It's all about the Style. Here is the Style that creates this rather nice ListBox.
<Grid x:Name="MainGrid" Background="Black">
<Grid.Resources>
<ResourceDictionary>
<Style x:Key="WrapItemTemplate" TargetType="{x:Type ListBox}">
-->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid Width="120" Height="120" Grid.Row="3" Grid.Column="3">
<Button Width="100" Height="100" Foreground="#FFFFFFFF"
Template="{DynamicResource GlassButton}"
Margin="10,10,10,10" HorizontalAlignment="Left"
VerticalAlignment="Top" Click="ItemClicked">
<Image x:Name="AssociatedImage"
Source="{Binding Mode=OneWay, XPath=url}"
Width="70" Height="70" Stretch="Fill">
</Image>
<Button.ToolTip>
<ToolTip Content="{Binding Mode=OneWay, XPath=title}"/>
</Button.ToolTip>
</Button>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
-->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
-->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
</Style>
</ResourceDictionary>
</Grid.Resources>
I'm not going into it line by line, but will mention the key points.
- As this Style section of XAML is being defined within the
<Grid.Resources><ResourceDictionary>
section of the main Grid (MainGrid
) this Style is available as a resource to any UIElement within the main Grid (MainGrid
). I'll go into more about resources in a minute.
- The
<Setter Property="ItemTemplate">
is what is telling the ListBox how the individual items should be displayed. As can be seen above, each item consists of a Grid, within the Grid is a Button
(which has a template assigned to it, I'll get to that later), and a ToolTip
. The Button
contains an Image, the Image Source is bound to an XPath pointer "url
". Remember this ListBox is bond in code behind to the XML file that was created by requesting an RSS Feed from Flickr. The ToolTip
is also bound to the XPath pointer "title
".
- Next we tell the ListBox to swap out its original ItemsPanel which was probably a default StackPanel, to now use a WrapPanel. I did this as I wanted a flowing auto-adjusting layout, that could change when the Window hosting this ListBox gets resized, We would like the items to re-arrange themselves to take up all available space.
- The last thing done here is to hide the horizontal scroll bar of the ScrollViewer. If we don't do this, the WrapPanel, will never wrap properly.
Now that we have this Style in place in the XAML, we just need to apply it. I chose to do this in code behind, but it could be done directly in the XAML. The code behind to apply the Style is as follows:
Style itemsStyle = ItemsGrid.TryFindResource("WrapItemTemplate") as Style;
if (itemsStyle != null)
lstXMLData.Style = itemsStyle;
And that is enough to change a rather standard uninteresting looking ListBox into a nice handsome ListBox.
I just had to mention resources, before I had a chance to tell you what resources actually are, and how they are used. So please forgive me for that, I'll now tell you about them.
Resources really define reusable sections of XAML code, that can be put either within the current .xaml file, or can be placed within separate .xaml files, they can even be put into the <Application/>
tag.
In the case where the resource is being placed in a separate .xaml file, the opening declaration tag must always be <ResourceDictionary>
and end with </ResourceDictionary>
. This denotes that this section of XAML is available to be used as a resource by other files.
Typically a <ResourceDictionary>
would hold <Style>
or <ControlTemplate>
XAML markup. The attached application contains the following separate resource .xaml files:
- cmbStyle.xaml : Style for a combo box
- GlassButtonTemplate.xaml : Control template for a Button, which creates a nice Vista style glass button. This was obtained here
- RoundBorderPanelStyle.xaml : Style for a panel
- txtBoxStyle.xaml : Style for a text box
In order to use these separate Resource files within another .xaml file, the correct Build Action within Visual Studio has to be set. I decided to use "Page", which means the resource will get compiled into the page where it is used.
So how do we use of one of the resources in another .xaml file, say in our Window1
(frmLoader.xaml).
Well quite simply put, when we need to use an external resource file, we specify a <ResourceDictionary>
then a <ResourceDictionary.MergedDictionaries>
followed by the names of the external resource files within a separate <ResourceDictionary>
tag. Where the source will be the location of the .xaml file to be included.
NOTE : One thing to be wary of, is that every UIElement can declare its own resources, as such if we define a resource in one Grid, like I have here for MainGrid
, that doesn't mean this resource will be available globally to the whole Window. Quite the opposite in fact, it means this resource will only be available to any child UIElement that is within the MainGrid
. So if you want a resource to be available to the whole Window, you should make it a Window (<Window/>
) resource, or for the whole application, would be a Application (<Application/>
) resource.
<Grid x:Name="MainGrid" Background="Black">
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/GlassButtonTemplate.xaml" />
<ResourceDictionary Source="Resources/txtBoxStyle.xaml" />
<ResourceDictionary Source="Resources/cmbStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</Grid.Resources>
This application is really only using one ControlTemplate which changes a ordinary button into a nice Vista style glass button. This ControlTemplate was contained here. I'm not going to go into this ControlTemplate as it is not my own work. What you should know is that by using a ControlTemplate it is possible to totally change how a standard control acts and looks. It's like creating a Windows Forms control that inherits from another control, but has custom paints, overridden methods and new methods events and properties.
Microsoft states this about ControlTemplate, "Specifies the visual structure and behavioral aspects of a Control that can be shared across multiple instances of the control."
Phew, we made it through the first Window (frmLoader.xaml) but there is another Window (frmImage.xaml). I'll now go on to describe that one.
Window2 (frmImage.xaml)
Recall that the second Window (code file is frmImage.xaml / frmImage.xaml.cs) looks like the figure below:
The code behind file for this Window (frmImage.xaml) is pretty self explanatory, so I'll leave that to you to look at.
As well as the DataBinding to external data sources, previously mentioned, WPF can also bind to other controls properties (dependency properties actually). So it is entirely feasible to bind the value from a slider, to the opacity of another control. This is exactly what this Window (frmImage.xaml) does. It uses the value from the slider to bind to a Border control. The Border control, has an Image control as a child, so any opacity changes made to the Border control, also affect the Image control.
So let's see the XAML that does that:
<Border RenderTransformOrigin="0.5,0.5" x:Name="borderImage"
Style="{StaticResource RoundBorderPanelStyle}"
Background="LightSteelBlue" BorderBrush="White"
Opacity="{Binding ElementName=opSlider, Path=Value}">
This Window make use of a very simple Style that is used to give any Border element rounded corners. This is what is done for the Border control that contains the main Image control used in this Window.
Let's have a look at this Styling, its very simple.
<ResourceDictionary
xmlns:local="clr-namespace:ImageTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xml:lang="en-US"
xmlns:Microsoft_Windows_Themes=
"clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Classic"
>
<Style x:Key="RoundBorderPanelStyle" TargetType="Border">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="CornerRadius" Value="6" />
<Setter Property="Margin" Value="5,5,5,5" />
</Style>
</ResourceDictionary>
All it does is target Border controls, and applies round corners. Nice and easy.
Now that I've introduced resources, I'll just say that this Window uses an external resource file called "Animations.xaml", this time the resource is declared as being a Window resource. That means it is available to any UIElement within the Window.
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Animations.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
The Grid (MainGrid
) also uses a local (local to the Grid) resource called "RoundBorderPanelStyle.xaml" which is used to give panels rounded corners as we described above in the Styles section.
<Grid x:Name="mainGrid" Background="#FF363636">
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary
Source="Resources/RoundBorderPanelStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Grid.Resources>
OK last thing is animation which is a lot of fun to play with. I have included 3 animation triggers, doing 2 separate animations, in this Window (frmImage.xaml). Animations always work as follows:
- An animation Trigger is raised, usually based on some event, such as mouse over
- The trigger then calls some Animation
This is described quite nicely here.
I have three triggers defined within the Window (frmImage.xaml), let's see them.
<Window.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter" SourceName="borderImage">
<BeginStoryboard Storyboard="{StaticResource
borderImage_MouseEnter_SCALE}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseEnter" SourceName="imgScale">
<BeginStoryboard Storyboard="{StaticResource
borderImage_MouseEnter_SCALE}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseMove" SourceName="imgSkew">
<BeginStoryboard
Storyboard="{StaticResource imgSkew_OnMouseMove_SKEW}"/>
</EventTrigger>
</Window.Triggers>
It can be seen that the BeginStoryBoard is making use of a Window resource. Remember we defined that here.
So let's see what this animations resource looks like.
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ImageTest"
>
<Storyboard x:Key="borderImage_MouseEnter_SCALE">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="borderImage"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="0.5"/>
<SplineDoubleKeyFrame KeyTime="00:00:02" Value="1"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="borderImage"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="0.5"/>
<SplineDoubleKeyFrame KeyTime="00:00:02" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="imgSkew_OnMouseMove_SKEW">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="borderImage"
Storyboard.TargetProperty="(UIElement.RenderTransform).
(TransformGroup.Children)[1].(SkewTransform.AngleX)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="45"/>
<SplineDoubleKeyFrame KeyTime="00:00:01" Value="215"/>
<SplineDoubleKeyFrame KeyTime="00:00:02" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ResourceDictionary>
There is a bit of code here, but breaking it down, it becomes more easy to understand. Take the StoryBoard called "imgSkew_OnMouseMove_SKEW
", this is simply targeting a certain UIElement within the Window, namely "borderImage" which is the Border control that contains the main Image control.
It is then applying a SkewTransform to the Border control, such that the Border control and the contained Image control are skewed over some timeline.
If you understood that StoryBoard, the other one should be fairly easy to work out. I'll leave that to you.
I hope this article shows that all these new Microsoft technologies can actually work together quite well. I have to say I think this application looks awesome. When I did the 3rd part of my LINQ series I tried to make the Windows form app, look good, but XAML/WPF really does rock. I keep trying to look at other things, but come across little WPF things, that excite me, that I simply have to investigate further. To this end, I think I have decided to write more and more WPF articles, it's very addictive. Go on try do some, you'll see. It happened to Josh Smith and it's happening to me, I can feel it, I've started dreaming about StackPanels
. Mmmmm Sexy.
It's no bad thing.
I would just like to ask, if you liked the article please vote for it, and leave some comments, as it let's me know if the article was at the right level or not, and whether it contained what people need to know.
I have enjoyed writing this and the LINQ series actually, and I think I will soon become hopelessly addicted to WPF. So if you want to keep a work/life balance, I would suggest you stay as far away from WPF as possible, because once it's got you in its little tentacles there really is no escape. "Resistance Is Futile. You Will Be Assimilated".
- v1.0 12/05/07: Initial issue
I would personally like to thank Josh Smith for his help in getting data binding to work using code behind, and his unstoppable WPFing that seems to be popping up everywhere. He is a great source of knowledge for all things WPF, so spend some time visiting his Blog he has lots of good resources there.