Silverlight ChildWindow for WPF

Jeff picture Jeff · Feb 9, 2010 · Viewed 10k times · Source

is it possible to make a ChildWindow like ChildWindow in Silverlight, but for WPF? I tried to adapt the Silverlight ChildWindow to WPF but ran into issues with Transformations and not being able to set the Popup's Parent. I'm trying to make something that works simular so I do not have to add code to the XAML for popups. Any ideas?

Answer

Mike Pateras picture Mike Pateras · Feb 9, 2010

This class should do what you want to do:

public class SilverlightishPopup
{
    private Rectangle maskRectangle = new Rectangle { Fill = new SolidColorBrush(Colors.DarkGray), Opacity = 0.0 };

    public FrameworkElement Parent
    {
        get;
        set;
    }

    public FrameworkElement Content
    {
        get;
        set;
    }

    public SilverlightishPopup()
    {
        Button button = new Button();
        button.Width = 100;
        button.Height = 200;
        button.Content = "I am the popup!";

        button.Click += delegate { Close(); };

        Content = button;
    }

    public void Show()
    {
        Grid grid = GetRootGrid();

        if (grid != null)
        {
            DoubleAnimation opacityAnimation = new DoubleAnimation(0.5, new Duration(TimeSpan.FromSeconds(0.5)));

            Storyboard opacityBoard = new Storyboard();
            opacityBoard.Children.Add(opacityAnimation);

            Storyboard.SetTarget(opacityAnimation, maskRectangle);
            Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("(Opacity)"));

            opacityBoard.Completed += delegate
            {
                ScaleTransform scaleTransform = new ScaleTransform(0.0, 0.0, Content.Width / 2.0, Content.Height / 2.0);
                Content.RenderTransform = scaleTransform;

                grid.Children.Add(Content);

                Storyboard scaleBoard = new Storyboard();

                DoubleAnimation scaleXAnimation = new DoubleAnimation(1.0, TimeSpan.FromSeconds(0.5));

                scaleBoard.Children.Add(scaleXAnimation);

                Storyboard.SetTarget(scaleXAnimation, Content);
                Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)"));

                DoubleAnimation scaleYAnimation = new DoubleAnimation(1.0, TimeSpan.FromSeconds(0.5));

                scaleBoard.Children.Add(scaleYAnimation);

                Storyboard.SetTarget(scaleYAnimation, Content);
                Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)"));

                scaleBoard.Begin();
            };

            opacityBoard.Begin();

            grid.Children.Add(maskRectangle);
        }
    }

    public void Close()
    {
        Grid grid = GetRootGrid();

        if (grid != null)
        {
            ScaleTransform scaleTransform = new ScaleTransform(1.0, 1.0, Content.Width / 2.0, Content.Height / 2.0);
            Content.RenderTransform = scaleTransform;

            Storyboard scaleBoard = new Storyboard();

            DoubleAnimation scaleXAnimation = new DoubleAnimation(0.0, TimeSpan.FromSeconds(0.5));

            scaleBoard.Children.Add(scaleXAnimation);

            Storyboard.SetTarget(scaleXAnimation, Content);
            Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)"));

            DoubleAnimation scaleYAnimation = new DoubleAnimation(0.0, TimeSpan.FromSeconds(0.5));

            scaleBoard.Children.Add(scaleYAnimation);

            Storyboard.SetTarget(scaleYAnimation, Content);
            Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)"));

            scaleBoard.Completed += delegate
            {
                DoubleAnimation opacityAnimation = new DoubleAnimation(0.5, 0.0, new Duration(TimeSpan.FromSeconds(0.5)));

                Storyboard opacityBoard = new Storyboard();
                opacityBoard.Children.Add(opacityAnimation);

                Storyboard.SetTarget(opacityAnimation, maskRectangle);
                Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath("(Opacity)"));

                opacityBoard.Completed += delegate
                {
                    grid.Children.Remove(maskRectangle);
                    grid.Children.Remove(Content);
                };

                opacityBoard.Begin();
            };

            scaleBoard.Begin();
        }
    }

    private Grid GetRootGrid()
    {
        FrameworkElement root = Parent;

        while (root is FrameworkElement && root.Parent != null)
        {
            FrameworkElement rootElement = root as FrameworkElement;

            if (rootElement.Parent is FrameworkElement)
            {
                root = rootElement.Parent as FrameworkElement;
            }
        }

        ContentControl contentControl = root as ContentControl;

        return contentControl.Content as Grid;
    }
}

Just set the Parent property to any Framework element in the parent window (it'll find the Window to block it with the mask), and set the content to whatever you want to be popped up (and call the Show method when you want it to be shown, of course). You'll have to come up with the pop-up wrapper (i.e. something with a border and a close button that calls the close method) on your own, but it shouldn't be difficult, and obviously remove the placeholder button in the constructor (it's just there to show you how it it will look).

The only problem with this is it will only work on windows that have their content (i.e. the thing that is named "LayoutRoot" in Silverlight) is a grid (the default when you create a new WPF/Silverlight Window/Page). I had it set to work for all panels, but it looks weird when used with a StackPanel or a DockPanel (as expected). If that doesn't work for you, let me know and we'll figure something out.

If you play with it, you can probably get the animation to look closer to the original pop-up (perhaps using some easing). There might also be a better way to find the root, I just came up with that method on the fly, but I think it'll work (though again, only with a Contentcontrol with its content set to a grid).

Let me know if you have any questions/problems, and I hope this solves your problem.