Я собираюсь предположить, что вам нужны отдельные отдельные окна, которые можно перетаскивать независимо друг от друга по экрану среди окон других приложений. (Если это предположение неверно и вам больше подходит интерфейс, подобный MDI, взгляните на ответ Роба.)
Я бы реализовал подкласс Expander, который принимает окно и:
- Когда IsExpanded = false, он представляет сам контент окна, используя ContentPresenter, но
- Когда IsExpanded = true, оно позволяет окну представлять свой собственный контент, но использует VisualBrush с прямоугольником для отображения этого контента
Он может иметь имя «WindowExpander», и его свойство Content будет установлено на фактический объект Window, который будет отображаться при развертывании Expander. Например, его можно использовать одним из следующих способов, в зависимости от того, как определяется ваша Windows:
<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>
Реализация WindowExpander была бы ToggleButton, содержащей ViewBox, который отображал миниатюру, например так:
<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>
Я думаю, вы, вероятно, захотите реализовать WindowExpander примерно так:
[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;
}
}
}
Приведенный выше код пропускает шаги для создания анимации. Это также не было проверено - это было просто написано быстро от макушки моей головы. Я надеюсь, что это работает для вас.
Как это работает: IsExpanded контролирует видимость окна, за исключением того, что при изменении IsExpanded раскадровка временно заставляет окно оставаться видимым достаточно долго для запуска анимации. В любой момент либо Window, либо ContentPresenter в шаблоне содержат содержимое окна. Вы можете сказать, что всякий раз, когда расширитель не раскрывается (нет окна), Контент «украден» из окна для использования в WindowExpander. Это делается методом SwapContent. Он помещает прямоугольник, нарисованный с помощью VisualBrush, в окно, а фактическое содержимое окна - в заголовок, который является миниатюрой, отображаемой на кнопке ToggleButton.
Этот метод основан на том факте, что VisualBrush не работает с невидимым Visual, потому что визуал «Content» фактически всегда виден - он всегда является потомком Window или ContentPresenter внутри ViewBox.
Поскольку используется VisualBrush, миниатюра всегда дает предварительный просмотр в реальном времени.
Одно предупреждение: не устанавливайте DataContext и не создавайте ресурсы на уровне окна. Если вы это сделаете, когда ваш контент «украден», у него не будет нужного DataContext или ресурсов, поэтому он не будет выглядеть правильно. Я бы порекомендовал использовать UserControl вместо Window для каждой формы, а вашей основной формой обернуть его в Window, как показано здесь:
<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>