I'm going to assume you want actual separate windows that can be dragged and dropped independently around your screen among other applications' windows. (If this assumption is incorrect and a MDI-like interface is better for you, take a look at Rob's answer.)
I would implement an Expander subclass that accepts a Window and:
- When IsExpanded=false, it presents the window content itself using a ContentPresenter, but
- When IsExpanded=true, it lets the window present its own content but uses a VisualBrush with a Rectangle to display that content
It might be named "WindowExpander" and would have its Content property set to the actual Window object to be shown when the Expander is expanded. For example it could be used in one of these ways, depending on how your Windows are defined:
<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 implementation of WindowExpander would be a ToggleButton containing a ViewBox that displayed the the thumbnail, like this:
<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'm thinking you would 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; // Rect where the window will animate from
var toRect = IsExpanded ? winRect : myRect; // Rect where the window will animate to
_storyboard = new Storyboard { FillBehavior = FillBehavior.Stop };
// ... code to build storyboard here ...
// ... should animate "Top", "Left", "Width" and "Height" of window from 'fromRect' to 'toRect' using desired timeframe
// ... should also animate Visibility=Visibility.Visible at time=0
_storyboard.Begin(Window);
Window.Visibility = IsExpanded ? Visibility.Visible : Visibility.Hidden;
}
private void OnStateChanged(object sender, EventArgs e)
{
if(IsExpanded && Window.WindowState == WindowState.Minimized)
{
Window.WindowState = WindowState.Normal;
IsExpanded = false;
}
}
}
The above code omits the steps to construct the animation. It also has not been tested - it was just written quickly off the top of my head. I hope it works for you.
How it works: IsExpanded controls the Window's visibility, except that when IsExpanded changes the storyboard temporarily forces the window to stay visible long enough for the animation run. At any given moment either the Window or the ContentPresenter in the template contains the window's content. You might say that whenever the expander is not expanded (no window), the Content is "stolen" from the window for use within the WindowExpander. This is done by the SwapContent method. It puts the Rectangle painted with the VisualBrush into the Window and the Window's actual content into the Header, which is the thumbnail shown on the ToggleButton.
This technique works around the fact that VisualBrush doesn't work on an invisible Visual because the "Content" visual is actually always visible - it is always a child of either the Window or of the ContentPresenter inside the ViewBox.
Because a VisualBrush is used, the thumbnail always gives a live preview.
One caveat: Don't set a DataContext or create resources at the Window level. If you do, when your content is "stolen" it will not have the right DataContext or resources so it won't look right. My recommendation would be to use a UserControl instead of a Window for each form, and have your main form wrap it in a Window as illustrated 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>