我假设您想要实际的独立窗口,可以在屏幕上与其他应用程序的窗口相互拖放。如果这种假设是不正确的,MDI式界面更适合您,请参考Rob的答案。
我会实现一个Expander子类,接受一个Window,并且:
1. 当IsExpanded=false时,它使用ContentPresenter呈现窗口内容本身,但是
2. 当IsExpanded=true时,它允许窗口呈现自己的内容,但使用VisualBrush和Rectangle显示该内容
它可能被命名为"WindowExpander",并且将其Content属性设置为要在展开Expander时显示的实际Window对象。例如,根据您的Windows如何定义,可以使用以下其中一种方式之一:
<UniformGrid Rows="2" Columns="2">
<local:WindowExpander Window="" />
<local:WindowExpander Window="" />
<local:WindowExpander Window="" />
<local:WindowExpander Window="" />
</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="">
<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="">
<Viewbox IsHitTestVisible="False">
<ContentPresenter Content=" />
</Viewbox>
</ToggleButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
我认为你可能想要像这样实现WindowExpander:
我认为您可能希望实现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;
var toRect = IsExpanded ? winRect : myRect;
_storyboard = new Storyboard { FillBehavior = FillBehavior.Stop };
_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改变时,Storyboard暂时强制窗口保持可见,足够长的时间使动画运行。在任何给定的时刻,无论是Window还是模板中的ContentPresenter都包含窗口的内容。可以说,每当展开器未展开(没有窗口)时,就会从窗口中“窃取”Content以在WindowExpander中使用。这是通过SwapContent方法完成的。它将用VisualBrush绘制的矩形放入窗口中,并将窗口的实际内容放入Header中,Header是ToggleButton上显示的缩略图。
这种技术解决了VisualBrush不能在不可见的Visual上工作的问题,因为“Content”视觉实际上总是可见的 - 它始终是Window或ViewBox内部的ContentPresenter的子项。
由于使用了VisualBrush,所以缩略图始终提供实时预览。
一个注意点:不要在Window级别设置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>