生成WPF窗口的屏幕快照

gv8xihay  于 2023-01-10  发布在  其他
关注(0)|答案(3)|浏览(296)

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

cbjzeqam

cbjzeqam1#

你试过RenderTargetBitmap吗?https://msdn.microsoft.com/en-us/library/system.windows.media.imaging.rendertargetbitmap.aspx
有几个“screenshot”方法会用到它,比如这个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);
        }
    }
fhg3lkii

fhg3lkii2#

受试:

  • 正在企业WPF应用程序中使用。
  • 在整个屏幕的一小部分上进行测试(可以对屏幕上的任何元素进行截图)。
  • 使用多台显示器进行测试。
  • 在WindowState =正常且WindowState =最大化的窗口上测试。
  • 采用96 DPI进行检测。
  • 使用120 DPI进行测试(在Windows 10中将字体大小设置为"125%",然后注销并登录)。
  • 避免使用任何Windows窗体样式像素计算,这种计算在不同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;
}

在视图模型中:

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>
niwlg2el

niwlg2el3#

我在WPF上遇到了同样的问题,经过大量的尝试和错误,我得出了这个解决方案:
首先,您需要以下名称空间:

using System.Windows;    
using System.Windows.Media.Imaging;
using System.IO.Stream;

然后创建如下函数:

public bool CapturaPantalla(string saveAs)
{
    try
    {
        //Get the Current instance of the window
        Window window = Application.Current.Windows.OfType<Window>().Single(x => x.IsActive);
        
        //Render the current control (window) with specified parameters of: Widht, Height, horizontal DPI of the bitmap, vertical DPI of the bitmap, The format of the bitmap
        RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap((int)window.ActualWidth, (int)window.ActualHeight, 96, 96, PixelFormats.Pbgra32);
        renderTargetBitmap.Render(window);

        //Encoding the rendered bitmap as desired (PNG,on my case because I wanted losless compression)
        PngBitmapEncoder png = new PngBitmapEncoder();
        png.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
        
        //Save the image on the desired location, on my case saveAs was C:\test.png
        using (Stream stm = File.Create(saveAs))
        {
            png.Save(stm);
        }

        return true;
    }
    catch (Exception ex)
    {
        return false;
    }

我希望它能帮助更多的人。

相关问题