创建一个C# WPF窗口的缩略图

7
我查阅了很多主题并在Google上搜索了相关信息,但都没有找到与我的问题相关的内容。
我想要做的是,当用户启动应用程序时,主窗口(不是MDI)将打开四个图像框,每个图像框都显示单击后将打开的表单的图像。一旦选定的表单打开并进行更改,如果他们单击最小化/关闭表单,则它将(似乎)最小化为显示表单缩略图的图像框中的实时图像。
我的问题是,如何将表单制作成图像,以便我可以在图像框中使用该图像作为缩略图?
另外...有人能指点我一些资源,帮助我弄清楚如何将“最小化”动画化到图像框中吗?
我不是让任何人替我完成工作,因为我想自己学习,但我有点卡住了。
最后,我不确定这需要什么,所以我不知道要为此帖子添加哪些标签。我会随着了解而添加标签,以便其他人可以找到这些信息。
编辑:抱歉,这是在WPF中。不确定是否有任何不同。我对WPF还不是很熟练。

这在WPF中非常可行;但在WinForms中就另当别论了。请更加具体地标记您的问题。 - Jay
抱歉,这是WPF。我已经将其添加到帖子中了。 - Sivvy
3个回答

6
您可以使用VisualBrush,这里是一个快速示例,将按钮的背景设置为缩小版本的stackpanel。
 <DockPanel>
        <StackPanel x:Name="myRect" >
            <TextBox Text="MyTexasdfasdfasdfasdfasdft" Height="50" />
            <CheckBox IsChecked="True"  />
            <Rectangle Fill="Red" Width="100" Height="100" />
        </StackPanel>


        <Button>
            <Button.Background>
                <VisualBrush TileMode="None"  Viewport="0,0,1,1" Visual="{Binding ElementName=myRect}" >
                    <VisualBrush.Transform>
                        <ScaleTransform ScaleX="0.3" ScaleY="0.3" />
                    </VisualBrush.Transform>
                </VisualBrush>
            </Button.Background>
        </Button>
    </DockPanel>

编辑:虽然这个解决方案可以复制屏幕上的内容,但是当屏幕上的内容被隐藏或移除时,VisualBrush也会被隐藏或移除。 为了保留图像,需要将控件渲染为位图。这可以使用RenderTargetBitMap完成。

// CenterControl is the target to render, ShowControl is the control to render the CenterControl onto.
var rtb = new RenderTargetBitmap((int)CenterControl.ActualWidth, (int)CenterControl.ActualHeight, 96, 96,
                                             PixelFormats.Pbgra32);
            rtb.Render(CenterControl);
            var bgBrush = new ImageBrush(rtb) {Transform = new ScaleTransform(0.1, 0.1)};
            ShowControl.Background = bgBrush;

谢谢hkon。我试过了,现在正在调整以适应我的需求。是否可能将一个窗口作为另一个窗口的控件? - Sivvy
我已经添加了一个答案,允许缩略图保持活动状态,通过以不同的方式解决“不可见视觉”问题。 - Ray Burns
由于我无法让其他解决方案正常工作,所以我找到了一个使用这个示例的解决方案。感谢hkon。 - Sivvy
很高兴,我喜欢创建这个示例 =) - hkon

3
我假设您想要实际的独立窗口,可以在屏幕上与其他应用程序的窗口相互拖放。如果这种假设是不正确的,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="{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:

我认为您可能希望实现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改变时,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>

干得好,Ray Burns。我其实一直在思考如何以一种通用的方式解决这个问题,但被迫去处理一些不那么有趣的问题。我喜欢使用扩展器的解决方案。希望它符合 OP 的要求 =) - hkon
看起来是这样的...我要试一下,看看它是否适合我所需的一切。从外表看,这可能就是答案。 - Sivvy
我差不多搞定了,但是出现了这个错误信息: "元数据覆盖和基础元数据必须是相同类型或派生类型."你有什么想法可以解决这个问题吗? - Sivvy
抱歉,我在 OverrideMetadata 调用中错误地输入了“PropertyMetadata”,而实际上我想要的是“FrameworkPropertyMetadata”。覆盖元数据的规则之一是覆盖类必须是(或继承自)用于原始元数据的类。每当您遇到此错误时,只需更改传递给 OverrideMetadata 的对象的类型即可。我已经修复了代码。 - Ray Burns
我很不想再问了,但是现在我遇到了错误:“PropertyMetadata已经为类型'WindowExpander'注册”。堆栈跟踪显示“at System.Windows.DependencyProperty.ProcessOverrideMetadata(Type forType, PropertyMetadata typeMetadata, DependencyObjectType dType, PropertyMetadata baseMetadata)”。此外,我一直在从构造函数中删除“static”属性以解决另一个错误。很抱歉一直打扰你,一旦它正常工作,我就能弄清楚所有的东西。感谢迄今为止提供的所有帮助。我喜欢你的答案,但目前对我来说太复杂了。 - Sivvy

1
如果你刚开始学习WPF,那么你计划做的事情很可能需要你学习Blend来定义条件和动画,或者深入了解动画系统并手动编写XAML代码。
从高层次上看,我想你可以通过将每个“表单”定义为UserControls或ContentPresenters来处理这个问题,也许还可以在它们周围加上一个边框。
然后,当“表单”处于非活动状态时,使用LayoutTransform或RenderTransform属性以及其他定位属性来定位和缩小它。一旦你熟悉了Blend,使用“States”和“Triggers”来定义这个其实相当容易。
要添加一个使最小化的表单变大的行为,处理“PreviewMouseDown”事件,并在处理程序中测试表单的状态。
我发现“5天学会Blend”视频对此很有用,但我必须承认与您一样困惑;我没有找到任何统一教授XAML和WPF的地方,而不是简单地参加第三方培训班或咨询导师。目前这个培训的第五天尚未推出,整个课程还是以Silverlight为主,并不针对WPF。
但是,这是一个开始;“Learn Blend”视频可以在这里找到:

http://www.microsoft.com/expression/resources/blendtraining/

您还会看到一个名为“.toolbox”的链接,我还没有尝试过。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接