WPF’s animation capabilities let you provide some nice styling to what would otherwise just be a static panel. As part of a project that displayed images I wanted to create a side-panel that would display the properties of an opened image, but rather than just have it sit there taking up space it would be nice to have a tab you could click that would make it slide in and out. This can be done by just creating an animation that changes the margin properties of the panel.

In the Windows.Resources tag for the main window I’ve added two animation Storyboards which are responsible for moving the side panel in and out. Named expandStoryboard and collapseStoryboard, these are bound to the propertiesPanel element (which is a grid and stackpanel containing all the image properties to display) and a tabButton user control which is the actual tab next to the properties panel and which should be the only part visible when the properties pane is hidden/collapsed.
<Window.Resources>
<Storyboard x:Key="expandStoryBoard"
TargetProperty="RenderTransform.(TranslateTransform.X)"
AccelerationRatio=".4"
DecelerationRatio=".4">
<DoubleAnimation Storyboard.TargetName="sidePanel" Duration="0:0:0.6"
From="0">
<DoubleAnimation.To>
<MultiBinding Converter="{StaticResource PanelConverter}">
<Binding Mode="OneWay" ElementName="propertiesPanel"
Path="Width" />
<Binding Mode="OneWay" ElementName="tabButton" Path="Width" />
</MultiBinding>
</DoubleAnimation.To>
</DoubleAnimation>
</Storyboard>
<Storyboard x:Key="collapseStoryBoard"
TargetProperty="RenderTransform.(TranslateTransform.X)"
AccelerationRatio=".4"
DecelerationRatio=".4">
<DoubleAnimation Storyboard.TargetName="sidePanel" Duration="0:0:0.6"
To="0">
<DoubleAnimation.From>
<MultiBinding Converter="{StaticResource PanelConverter}">
<Binding Mode="OneWay" ElementName="propertiesPanel"
Path="Width" />
<Binding Mode="OneWay" ElementName="tabButton" Path="Width" />
</MultiBinding>
</DoubleAnimation.From>
</DoubleAnimation>
</Storyboard>
</Window.Resources>
In order for the panel to be hidden to begin with, its margin property is offset to the right. Instead of just entering a value for the offset I’ve bound the sidePanel margin to the width of the entire panel (including the tab) so we don’t have to worry about specific widths. A ValueConverter is used to convert the width of the panel to a Thickness value required by the Margin property.
<Grid Grid.Row="1" x:Name="mainGrid">
<Image x:Name="mainImage" Stretch="Fill" />
<StackPanel x:Name="sidePanel" HorizontalAlignment="Right"
Margin="{Binding ElementName=propertiesPanel, Path=Width,
Converter={StaticResource MarginConverter}}">
<StackPanel.RenderTransform>
<TranslateTransform />
</StackPanel.RenderTransform>
<StackPanel Orientation="Horizontal">
<local:SidePanelTab x:Name="tabButton" VerticalAlignment="Top"
Margin="0,20,0,0"
MouseDown="StackPanel_MouseDown" />
<StackPanel Orientation="Vertical" Background="LightGray">
<!-- Image properties displayed here -->
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
So when the mouse is clicked on the tab section of the side panel (which is just a small user control containing the tab portion itself and ideally could be styled much nicer than this!), the resulting event handler in the main window triggers the animation, calling the expanding or collapsing animation depending on the current state of the panel
private void StackPanel_MouseDown(object sender, MouseButtonEventArgs e)
{
//Handle single leftbutton mouse clicks
if (e.ClickCount < 2 && e.LeftButton == MouseButtonState.Pressed)
{
//Ensure an image has been loaded
if (imageLoaded == true)
{
if (expanded == false)
sidePanel.BeginStoryboard((Storyboard)this.Resources["expandStoryBoard"]);
else
sidePanel.BeginStoryboard((Storyboard)this.Resources["collapseStoryBoard"]);
expanded = !expanded;
}
}
}
The last component is the actual loading of an image and binding of image properties to those of the side panel. This is done using an a very simple ImageViewModel, created usingĀ MVVM Light which provides a ViewModelBase class. This ensures all the property changed events will update the side panel properties when another image is loaded.
private void CommandBinding_Open_Executed(object sender, ExecutedRoutedEventArgs e)
{
//Launch the file open dialog
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "Images (*.jpg, *.png)|*.jpg*;*.png";
if (dlg.ShowDialog() == true)
{
//Display the image -BitmapImage.UriSource must be in a BeginInit/EndInit block
myBitmapImage = new BitmapImage();
myBitmapImage.BeginInit();
myBitmapImage.UriSource = new Uri(dlg.FileName);
myBitmapImage.EndInit();
FileInfo fInfo = new FileInfo(dlg.FileName);
mViewModel.ImageName =
System.IO.Path.GetFileName(myBitmapImage.UriSource.LocalPath);
mViewModel.ImageWidth = myBitmapImage.PixelWidth;
mViewModel.ImageHeight = myBitmapImage.PixelHeight;
mViewModel.ImageDepth = myBitmapImage.Format.BitsPerPixel;
mViewModel.DateCreated = fInfo.CreationTime;
mViewModel.FileType = fInfo.Extension;
//set image source
mainImage.Source = myBitmapImage;
imageLoaded = true;
}
}
The source for this project can be downloaded here
Pingback: Force redraw after StoryBoard with WindowsFormsHost WPF
Pingback: Force redraw after StoryBoard with WindowsFormsHost WPF | Jisku.com - Developers Network