生成WPF窗口的屏幕截图

时间:2011-02-26 03:21:31

标签: c# wpf

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

2 个答案:

答案 0 :(得分:11)

你试过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);
        }
    }

答案 1 :(得分:0)

已测试:

  • 在企业WPF应用程序中使用。
  • 在整个屏幕的一小部分进行了测试(可以截取屏幕上任何元素的屏幕截图)。
  • 经过多台显示器的测试。
  • 在WindowState = Normal和WindowState = Maximized的窗口上测试。
  • 经过96 DPI测试。
  • 使用120 DPI(在Windows 10中将字体大小设置为“ 125%”,然后注销并登录)进行测试。
  • 避免使用Windows-Form-Style像素计算,这些计算在不同的DPI设置中会出现缩放问题。
  • 即使部分窗口不在屏幕上也可以工作。
  • 即使另一个流氓窗口覆盖了当前窗口的一部分,该工程仍然有效。
  • 使用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页上其他位置的命名元素。如果要对整个窗口进行屏幕快照,则不能将窗口传递进来(因为我们无法在窗口上设置ClipToBounds),但是可以在窗口内部传递<Grid>

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