Take a screenshot of a WPF window

In winforms we can use DrawToBitmap. Is there a similar method for doing this in WPF?

+9
c # wpf
source share
2 answers

Have you tried RenderTargetBitmap ? https://msdn.microsoft.com/en-us/library/system.windows.media.imaging.rendertargetbitmap.aspx

There are several โ€œscreenshotโ€ methods that use this, like this one 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); } } 
+11
source share

Checked by:

  • Used in a corporate WPF application.
  • Tested on a small part of the entire screen (you can take a screenshot of any element on the screen)
  • Tested with multiple monitors.
  • Tested in a window with WindowState = Normal and WindowState = Maximized.
  • Tested with 96 DPI.
  • Tested at 120 dpi (set the font size to "125%" in Windows 10, then log out and log in).
  • Prevents any use of pixel calculations in Windows-Form-Style, which have problems with scaling in various DPI settings.
  • It works even if part of the window is off-screen.
  • It works even if another fraudulent window covers part of the current window.
  • Uses ClipToBounds, so it is compatible with multiple docked windows in Infragistics.

Function:

 /// <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; } 

In ViewModel:

  public ICommand ScreenShotCommand { get; set; } 

Team:

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

In the constructor:

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

And in XAML:

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

ElementName=Content points to a named element somewhere else on the same XAML page. If you want to take a snapshot of the entire window, you cannot transfer the window (since we cannot set ClipToBounds for the window), but we can pass <Grid> inside the window.

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

All Articles