【问题标题】:Generating a screenshot of a WPF window生成 WPF 窗口的屏幕截图
【发布时间】:2011-02-26 03:21:31
【问题描述】:

在winforms中,我们可以使用DrawToBitmap。 WPF中有类似的方法吗?

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

    你试过RenderTargetBitmap吗? https://msdn.microsoft.com/en-us/library/system.windows.media.imaging.rendertargetbitmap.aspx

    有一些“屏幕截图”方法可以使用它,例如from here

        public static void CreateBitmapFromVisual(Visual target, string fileName)
        {
            if (target == null || string.IsNullOrEmpty(fileName))
            {
                return;
            }
    
            Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
    
            RenderTargetBitmap renderTarget = new RenderTargetBitmap((Int32)bounds.Width, (Int32)bounds.Height, 96, 96, PixelFormats.Pbgra32);
    
            DrawingVisual visual = new DrawingVisual();
    
            using (DrawingContext context = visual.RenderOpen())
            {
                VisualBrush visualBrush = new VisualBrush(target);
                context.DrawRectangle(visualBrush, null, new Rect(new Point(), bounds.Size));
            }
    
            renderTarget.Render(visual);
            PngBitmapEncoder bitmapEncoder = new PngBitmapEncoder();
            bitmapEncoder.Frames.Add(BitmapFrame.Create(renderTarget));
            using (Stream stm = File.Create(fileName))
            {
                bitmapEncoder.Save(stm);
            }
        }
    

    【讨论】:

    • 因为它也没有在您引用示例的网站上提到:什么是“视觉”对象以及如何使用它?它来自哪里?
    • @Lorgarn 所有控件和视图元素(TextBlocks、Grids 等)都继承自 Visual。所以你可以将它们中的任何一个传递给这个方法来创建它的图像文件。但想法是传递一个Window 对象来获取整个窗口的屏幕截图。
    • 谢谢。我厌倦了它,到目前为止它还在工作,除了所有的 Windowdecorations 都被省略了......
    • 我知道这是一篇旧帖子,但是如何将带有装饰器的选定元素保存为图像?
    • 我正在传递当前窗口,但 GetDescendantBounds 没有返回边界。
    【解决方案2】:

    测试:

    • 在企业 WPF 应用程序中使用。
    • 在整个屏幕的一小部分测试(可以截取屏幕上任何元素的屏幕截图)。
    • 用多台显示器测试。
    • 在 WindowState=Normal 和 WindowState=Maximized 的窗口上测试。
    • 经过 96 DPI 测试。
    • 使用 120 DPI 进行测试(在 Windows 10 中将字体大小设置为“125%”,然后注销再登录)。
    • 避免使用在不同 DPI 设置中存在缩放问题的 Windows 窗体样式像素计算。
    • 即使窗口的一部分不在屏幕上也能正常工作。
    • 即使另一个恶意窗口覆盖了当前窗口的一部分,也能正常工作。
    • 使用 ClipToBounds,因此它与 Infragistics 中的多个停靠窗口兼容。

    功能:

    /// <summary>
    /// Take screenshot of a Window.
    /// </summary>
    /// <remarks>
    /// - Usage example: screenshot icon in every window header.                
    /// - Keep well away from any Windows Forms based methods that involve screen pixels. You will run into scaling issues at different
    ///   monitor DPI values. Quote: "Keep in mind though that WPF units aren't pixels, they're device-independent @ 96DPI
    ///   "pixelish-units"; so really what you want, is the scale factor between 96DPI and the current screen DPI (so like 1.5 for
    ///   144DPI) - Paul Betts."
    /// </remarks>
    public async Task<bool> TryScreenshotToClipboardAsync(FrameworkElement frameworkElement)
    {
        frameworkElement.ClipToBounds = true; // Can remove if everything still works when the screen is maximised.
    
        Rect relativeBounds = VisualTreeHelper.GetDescendantBounds(frameworkElement);
        double areaWidth = frameworkElement.RenderSize.Width; // Cannot use relativeBounds.Width as this may be incorrect if a window is maximised.
        double areaHeight = frameworkElement.RenderSize.Height; // Cannot use relativeBounds.Height for same reason.
        double XLeft = relativeBounds.X;
        double XRight = XLeft + areaWidth;
        double YTop = relativeBounds.Y;
        double YBottom = YTop + areaHeight;
        var bitmap = new RenderTargetBitmap((int)Math.Round(XRight, MidpointRounding.AwayFromZero),
                                            (int)Math.Round(YBottom, MidpointRounding.AwayFromZero),
                                            96, 96, PixelFormats.Default);
    
        // Render framework element to a bitmap. This works better than any screen-pixel-scraping methods which will pick up unwanted
        // artifacts such as the taskbar or another window covering the current window.
        var dv = new DrawingVisual();
        using (DrawingContext ctx = dv.RenderOpen())
        {
            var vb = new VisualBrush(frameworkElement);
            ctx.DrawRectangle(vb, null, new Rect(new Point(XLeft, YTop), new Point(XRight, YBottom)));
        }
        bitmap.Render(dv);
        return await TryCopyBitmapToClipboard(bitmap);         
    }        
    
    private static async Task<bool> TryCopyBitmapToClipboard(BitmapSource bmpCopied)
    {
        var tries = 3;
        while (tries-- > 0)
        {
            try
            {
                // This must be executed on the calling dispatcher.
                Clipboard.SetImage(bmpCopied);
                return true;
            }
            catch (COMException)
            {
                // Windows clipboard is optimistic concurrency. On fail (as in use by another process), retry.
                await Task.Delay(TimeSpan.FromMilliseconds(100));
            }
        }
        return false;
    }   
    

    在 ViewModel 中:

     public ICommand ScreenShotCommand { get; set; }
    

    命令:

     private async void OnScreenShotCommandAsync(FrameworkElement frameworkElement)
     {
         var result = await this.TryScreenshotToClipboardAsync(frameworkElement); 
         if (result == true)
         {
            // Success.
         }
     }
    

    在构造函数中:

    // See: https://stackoverflow.com/questions/22285866/why-relaycommand
    // Or use MVVM Light to obtain RelayCommand.
    this.ScreenShotCommand = new RelayCommand<FrameworkElement>(this.OnScreenShotCommandAsync);
    

    在 XAML 中:

    <Button Command="{Binding ScreenShotCommand, Mode=OneWay}"
            CommandParameter="{Binding ElementName=Content}"                                                        
            ToolTip="Save screenshot to clipboard">    
    </Button>
    

    ElementName=Content 指向同一 XAML 页面上其他位置的命名元素。如果要截屏整个窗口,则不能传入 Window(因为我们无法在 Window 上设置 ClipToBounds),但我们可以在 Window 内传入 &lt;Grid&gt;

    <Grid x:Name="Content">
        <!-- Content to take a screenshot of. -->
    </Grid>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-02
      • 2021-04-16
      • 1970-01-01
      • 1970-01-01
      • 2013-08-22
      • 2010-12-23
      相关资源
      最近更新 更多