I'm going to assume that you need actual individual windows that you can drag independently around the screen among other application windows. (If this assumption is incorrect and an MDI-like interface is better for you, take a look at Rob's answer.)
I would subclass Expander, which takes a window and:
- When IsExpanded = false, it itself represents the contents of the window using ContentPresenter, but
- When IsExpanded = true, it allows the window to present its own content, but uses a VisualBrush with a rectangle to display that content
It can be called "WindowExpander" and will have the Content property set for the actual Window object that will be displayed when the Expander extension is used. For example, it can be used in one of these ways, depending on how your Windows is detected:
<UniformGrid Rows="2" Columns="2"> <local:WindowExpander Window="{StaticResource Form1Window}" /> <local:WindowExpander Window="{StaticResource Form2Window}" /> <local:WindowExpander Window="{StaticResource Form3Window}" /> <local:WindowExpander Window="{StaticResource Form4Window}" /> </UniformGrid> <UniformGrid Rows="2" Columns="2"> <local:WindowExpander><Window Width="800" Height="600"><local:Form1 /></Window></local:WindowExpander> <local:WindowExpander><Window Width="800" Height="600"><local:Form2 /></Window></local:WindowExpander> <local:WindowExpander><Window Width="800" Height="600"><local:Form3 /></Window></local:WindowExpander> <local:WindowExpander><Window Width="800" Height="600"><local:Form4 /></Window></local:WindowExpander> </UniformGrid> <ItemsControl ItemsSource="{Binding Forms}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate><UniformGrid Rows="2" Columns="2"/></ItemsPanelTemplate> </ItemsControl.ItemsPanel> </ItemsControl>
The WindowExpander implementation will be a ToggleButton containing a ViewBox that displays a thumbnail, for example:
<Style TargetType="local:WindowExpander"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:WindowExpander"> <ToggleButton IsChecked="{TemplateBinding IsExpanded}"> <Viewbox IsHitTestVisible="False"> <ContentPresenter Content="{Binding Header} /> </Viewbox> </ToggleButton> </ControlTemplate> </Setter.Value> </Setter> </Style>
I think you probably want to implement WindowExpander something like this:
[ContentProperty("Window")] public class WindowExpander : Expander { Storyboard _storyboard; public static WindowExpander() { DefaultStyleKeyProperty.OverrideMetadata(typeof(WindowExpander), new FrameworkPropertyMetadata(typeof(WindowExpander))); IsExpandedProperty.OverrideMetadata(typeof(WindowExpander), new FrameworkPropertyMetadata { PropertyChangedCallback = (obj, e) => { var expander = (WindowExpander)obj; if(expander.Window!=null) { expander.SwapContent(expander.Window); expander.AnimateWindow(); } } }); } public Window Window { get { return (Window)GetValue(WindowProperty); } set { SetValue(WindowProperty, value); } } public static readonly DependencyProperty WindowProperty = DependencyProperty.Register("Window", typeof(Window), typeof(WindowExpander), new UIPropertyMetadata { PropertyChangedCallback = (obj, e) => { var expander = (WindowExpander)obj; var oldWindow = (Window)e.OldValue; var newWindow = (Window)e.NewValue; if(oldWindow!=null) { if(!expander.IsExpanded) expander.SwapContent(oldWindow); oldWindow.StateChanged -= expander.OnStateChanged; } expander.ConstructLiveThumbnail(); if(newWindow!=null) { if(!expander.IsExpanded) expander.SwapContent(newWindow); newWindow.StateChanged -= expander.OnStateChanged; } } }); private void ConstructLiveThumbnail() { if(Window==null) Header = null; else { var rectangle = new Rectangle { Fill = new VisualBrush { Visual = (Visual)Window.Content } }; rectangle.SetBinding(Rectangle.WidthProperty, new Binding("Width") { Source = Window }); rectangle.SetBinding(Rectangle.HeightProperty, new Binding("Height") { Source = Window }); Header = rectangle; } } private void SwapContent(Window window) { var a = Header; var b = window.Content; Header = null; window.Content = null; Header = b; window.Content = a; } private void AnimateWindow() { if(_storyboard!=null) _storyboard.Stop(Window); var myUpperLeft = PointToScreen(new Point(0, 0)); var myLowerRight = PointToScreen(new Point(ActualWidth, ActualHeight)); var myRect = new Rect(myUpperLeft, myLowerRight); var winRect = new Rect(Window.Left, Window.Top, Window.Width, Window.Height); var fromRect = IsExpanded ? myRect : winRect;
There are no steps in the above code to create the animation. It was also not tested - it was simply written quickly from the head. Hope this works for you.
How it works: IsExpanded controls the visibility of the Window, except that when you change IsExpanded, the storyboard temporarily makes the window remain visible long enough to start the animation. At any given time, a Window or ContentPresenter in a template contains the contents of a window. You can say that whenever the expander does not expand (without a window), the Content is “stolen” from the window for use in WindowExpander. This is done by the SwapContent method. It puts the rectangle drawn with VisualBrush in the window and the actual contents of the window in the title bar, this is the thumbnail displayed in ToggleButton.
This method works around the fact that VisualBrush does not work with invisible Visuals because the Content visual is actually always visible - it is always a child of a Window or ContentPresenter inside the ViewBox.
Since VisualBrush is used, the thumbnail always gives a live preview.
One warning: do not set the DataContext or create resources at the window level. If you do this when your content is “stolen”, it will not have the required DataContext or resources, so it will not look right. My recommendation would be to use UserControl instead of Window for each form, and your main form will transfer it to Window, as shown here:
<UniformGrid Rows="2" Columns="2"> <local:WindowExpander><Window Width="800" Height="600"><local:Form1 /></Window></local:WindowExpander> <local:WindowExpander><Window Width="800" Height="600"><local:Form2 /></Window></local:WindowExpander> <local:WindowExpander><Window Width="800" Height="600"><local:Form3 /></Window></local:WindowExpander> <local:WindowExpander><Window Width="800" Height="600"><local:Form4 /></Window></local:WindowExpander> </UniformGrid>