Creating a sliding panel in WPF

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.

The expaned side 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

The source for this project is now available here: https://bitbucket.org/bitsbobsetc/slidingsidepanel/src

About these ads

7 thoughts on “Creating a sliding panel in WPF

  1. Pingback: Force redraw after StoryBoard with WindowsFormsHost WPF

  2. Pingback: Force redraw after StoryBoard with WindowsFormsHost WPF | Jisku.com - Developers Network

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s