简体   繁体   English

创建非活动C#WPF窗口的缩略图

[英]Create a thumbnail of an inactive C# WPF Window

I've looked through many topics here, and googled for the information, but I haven't found anything relating to my question. 我在这里查看了很多主题,并搜索了相关信息,但我没有找到与我的问题有关的任何内容。

What I want to do is have it so when a user starts the application, the main window (not an MDI) opens with four imageboxes, each showing an image of the form that would open when they click on it. 我想要做的就是让用户启动应用程序时,主窗口(不是MDI)打开时有四个图像框,每个图像框显示一个窗体的图像,当它们点击它时会打开。 Once the selected form is open, and changes are made, if they click to minimize/close the form, it will (seemingly) minimize into the imagebox showing a real-time image of what the form looks like in a thumbnail view. 一旦选定的表单打开并进行了更改,如果他们单击以最小化/关闭表单,它将(看似)最小化到图像框中,显示表单在缩略图视图中的外观的实时图像。

My question is, how do I make a form into an image so I can use the image as a thumbnail in an imagebox? 我的问题是,如何将图像制作成图像,以便将图像用作图像框中的缩略图?

Also... Can someone point me in the direction of some resources that will help me figure out how to animate the "minimizing" into the imagebox? 此外......有人能指出我的某些资源,这将有助于我弄清楚如何动画“最小化”到图像框?

I'm not asking anyone to do my work for me, because I'd like to learn it myself, but I'm kinda stuck. 我不是要求任何人为我做我的工作,因为我想自己学习,但我有点卡住了。

Lastly, I'm not sure what's involved in this, so I don't know what tags to put for this post. 最后,我不确定这涉及到什么,所以我不知道为这篇文章添加什么标签。 I'll add tags as I figure it out so others can find this information. 当我弄清楚时,我会添加标签,以便其他人可以找到这些信息。

EDIT: Sorry, it is in WPF. 编辑:对不起,它在WPF中。 Wasn't sure it would be any different. 不确定它会有什么不同。 I'm still not particularly experienced in WPF. 我在WPF方面仍然没有特别的经验。

You can use the VisualBrush, here is a quick example of a button with a background set to a downscaled version of a stackpanel. 您可以使用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>

Edit: though this solution works to copy stuff that is on the screen, when the stuff on screen is hidden or removed, so will the VisualBrush. 编辑:虽然这个解决方案可以复制屏幕上的内容,当隐藏或删除屏幕上的内容时,VisualBrush也是如此。 In order to persist the image, it is necessary to render the control to a bitmap. 为了保持图像,有必要将控件呈现给位图。 This can be done with the RenderTargetBitMap 这可以使用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;

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.) (如果这个假设不正确并且类似MDI的界面对你更好,请看看Rob的回答。)

I would implement an Expander subclass that accepts a Window and: 我将实现一个接受Window的Expander子类,并且:

  1. When IsExpanded=false, it presents the window content itself using a ContentPresenter, but 当IsExpanded = false时,它使用ContentPresenter呈现窗口内容,但是
  2. When IsExpanded=true, it lets the window present its own content but uses a VisualBrush with a Rectangle to display that content 当IsExpanded = true时,它会让窗口显示自己的内容,但使用带有Rectangle的VisualBrush来显示该内容

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. 它可能被命名为“WindowExpander”,并且将其Content属性设置为扩展Expander时要显示的实际Window对象。 For example it could be used in one of these ways, depending on how your Windows are defined: 例如,它可以以下列方式之一使用,具体取决于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>

The implementation of WindowExpander would be a ToggleButton containing a ViewBox that displayed the the thumbnail, like this: 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>

I'm thinking you would probably want to implement WindowExpander something like this: 我想你可能想要实现像这样的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;
    }
  }
}

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. 工作原理:IsExpanded控制Window的可见性,但IsExpanded更改故事板时会暂时强制窗口保持足够长的时间以便动画运行。 At any given moment either the Window or the ContentPresenter in the template contains the window's content. 在任何给定时刻,模板中的Window或ContentPresenter都包含窗口的内容。 You might say that whenever the expander is not expanded (no window), the Content is "stolen" from the window for use within the WindowExpander. 您可能会说,无论何时扩展器未展开(无窗口),内容都会从窗口“被盗”,以便在WindowExpander中使用。 This is done by the SwapContent method. 这是通过SwapContent方法完成的。 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. 它将使用VisualBrush绘制的Rectangle放入Window,将Window的实际内容放入Header,这是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. 这种技术解决了VisualBrush无法在不可见的Visual上工作的事实,因为“Content”视觉实际上始终是可见的 - 它始终是ViewBox中的Window或ContentPresenter的子节点。

Because a VisualBrush is used, the thumbnail always gives a live preview. 由于使用了VisualBrush,因此缩略图始终提供实时预览。

One caveat: Don't set a DataContext or create resources at the Window level. 一个警告:不要在Window级别设置DataContext或创建资源。 If you do, when your content is "stolen" it will not have the right DataContext or resources so it won't look right. 如果您这样做,当您的内容被“窃取”时,它将没有正确的DataContext或资源,因此它看起来不正确。 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: 我的建议是为每个表单使用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>

If you're beginning with WPF, then what you're planning to do will likely require that you either learn Blend in order to define the conditions and animations, or dive deep into the animation system in order to understand it and hand-code the XAML. 如果您是从WPF开始,那么您计划要做的事情可能要求您学习混合以定义条件和动画,或者深入了解动画系统以便理解它并手动编码XAML。

At a high level, I imagine you could approach this by defining each of your four "forms" as UserControls or ContentPresenters, perhaps with a Border around them. 在高层次上,我想你可以通过将你的四个“形式”中的每一个定义为UserControls或ContentPresenters来实现这一点,也许是围绕它们的边框。

Then, when the "form" is in an inactive state, use the LayoutTransform or RenderTransform property along with other positioning properties to position and shrink it. 然后,当“表单”处于非活动状态时,使用LayoutTransformRenderTransform属性以及其他定位属性来定位和缩小它。 Once your brain is accustomed to Blend it's actually pretty easy to define this using the "States" and "Triggers". 一旦你的大脑习惯于混合,实际上很容易使用“状态”和“触发器”来定义它。

To add a behavior to grow the minimized form, handle the "PreviewMouseDown" event and in the handler, test for the state of the form. 要添加行为以增长最小化的表单,请处理“PreviewMouseDown”事件,并在处理程序中测试表单的状态。

I found the "Learn Blend in 5 Days" videos useful for this, but I'll confess to sharing your confusion; 我发现“5天学习混合”视频对此有用,但我承认分享你的困惑; there is no unified place that I've found which teaches XAML and WPF in a systematic way, without simply enrolling in a third-party training class or calling in a mentor-consultant. 我发现没有统一的地方以系统的方式教授XAML和WPF,而不是简单地参加第三方培训课程或打电话给导师顾问。 It doesn't help that at this time, the fifth day of the training is "Coming Soon", or that the entire thing is keyed to Silverlight rather than WPF. 在这个时候,培训的第五天是“即将推出”,或者整个事情都是关键的Silverlight而不是WPF,这没有任何帮助。

But, it's a start; 但是,这是一个开始; The "Learn Blend" videos are found here: “学习混合”视频可在此处找到:

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

You'll also see a link to something called ".toolbox", which I haven't yet tried. 你还会看到一个名为“.toolbox”的链接,我还没有尝试过。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM