我假设您需要实际的独立窗口,可以在屏幕周围和其他应用程序的窗口之间独立地拖动和放置。(如果这个假设是错误的,并且类似MDI的接口更适合您,请看Rob的答案。)
我将实现一个接受窗口的Expander子类,并且:
-
当isexpanded=false时,它使用ContentPresenter显示窗口内容本身,但是
-
当isexpanded=true时,它允许窗口显示自己的内容,但使用带有矩形的VisualBrush来显示该内容。
它可能被命名为“WindowExpander”,并将其内容属性设置为展开展开时要显示的实际窗口对象。例如,它可以通过以下方式之一使用,具体取决于如何定义窗口:
<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,其中包含一个显示缩略图的视图框,如下所示:
<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更改时,故事板会临时强制窗口在动画运行期间保持足够长的可见时间。在任何给定的时刻,模板中的窗口或ContentPresenter都包含窗口的内容。您可能会说,每当扩展器没有展开(没有窗口)时,内容都会从窗口“被盗”,以便在窗口扩展器中使用。这是通过swapContent方法完成的。它将用VisualBrush绘制的矩形放入窗口,并将窗口的实际内容放入头中,这是ToggleButton上显示的缩略图。
这项技术解决了这样一个事实:VisualBrush不适用于不可见的可视对象,因为“内容”可视对象实际上总是可见的——它始终是窗口的子对象,或是视盒内ContentPresenter的子对象。
因为使用了VisualBrush,缩略图总是提供实时预览。
警告:不要在窗口级别设置DataContext或创建资源。如果你这样做了,当你的内容被“窃取”时,它将没有正确的数据上下文或资源,因此看起来不正确。我的建议是对每个表单使用一个用户控件而不是一个窗口,并让主表单将其包装在一个窗口中,如下图所示:
<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>