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

17 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

  3. I am trying to modify your example (a big help, by the way) to work when the size of the propertiesPanel is not known until runtime (for example, to display the complete file name when it is too long to display in the fixed 250 pixel width. Any suggestions?

    • It’s been a while since I looked at this so I might not be much help! 🙂 If you give a name to the TextBlock that holds the image name, such as textBlockImageName, and then call UpdateLayout() after opening the image, the textBlockImageName should have its real width.
      You might be able to assign the propertiesPanel.Width property to the textBlockImageName.ActualWidth (plus an additional amount to accout for the margins etc.) This would then give the propertiesPanel an actual Width value that the Storyboard animation can use.

  4. Bob,

    I was able to get it to work by indirectly binding to the ActualWidth. Since ActualWidth is read-only, it is not bindable, but I used the solution presented here ==> http://stackoverflow.com/questions/1083224/pushing-read-only-gui-properties-back-into-viewmodel to create a change notification property in the code behind and bound the margin property and the storyboards to it.

    I also simplified the binding in the storyboards since the multi-binding didn’t really do anything. This is because the tabButton does not have a Width dependency property, and in any case the translation amount is only the width of the propertiesPanel. I replaced the converter and added a ConverterParameter to indicate to the converter which margin I am animating from. That way the converter can conditionally multiply by -1, if the animation is from the bottom or right.

    Here is what my modified animation element looks like:

    In any case, your initial solution got me started. Thanks for posting it.

  5. Well, the XML that I posted in my previous got scrubbed when it was posted. If you know how I can post the XML and you are interested, let me know.

    Also the time of the post is 17 hours in the future…Weird!

  6. Hey good job. This is really an interesting subject which I myself had to research and figure out several years back. I am laughing at the irony of one thing: You did the RenderTransform.(TranslateTransform.X) which moves the window/panel from the left to the right. Do you know how to do a top-down slide? I am going to attempt window height but I’d like the user to press on a tab from the top and have their info slide down…Any thoughts?

    • Its been a while since I looked at this but I think if you want to have a tab come down from above then you can use TranslateTransform.Y instead of X and have the sidePanel bind to the Height like you mentioned instead of the width (and maybe use VerticalAlignment instead of HorizontalAlignment)

  7. Hi,
    Mayday Mayday !
    I’m trying to do this but inside an UserControl (Resources and Design for the entire grid). I’m not able to bind with the storyboard’s DoubleAnimations.

    For information, I put the storyboard inside the UC as
    and the Window_Loaded Methode inside the UC ‘s Code Behind and in the UC’s Xaml Loaded attribut.
    Should be correct but not…binding issues for “propertiesPanel” and “tabButton” in doubleAnimations.
    What’s wrong with this? Any idea Why?
    Thanks.

    • Hi,

      Sorry to reply so late – when you put the Storyboards inside the UserControl I think WPF has trouble finding the elements. If you put the storyboards inside the resources of the main StackPanel in the UserControl, instead of the UserControl resources, I think it should work (Sorry I can’t seem to paste in the code to a comment). Here’s a question on MSDN about it/

      If you have just moved all the code directly into a UserControl you will also need to add a dependency property so you can bind the Window Height that is used by the propertiesPanel element

Leave a reply to Rick B. Cancel reply