In winforms, we can use DrawToBitmap. Is there a similar method to this in WPF?
Asked
Active
Viewed 1.7k times
3 Answers
12
Have you tried RenderTargetBitmap
? https://msdn.microsoft.com/en-us/library/system.windows.media.imaging.rendertargetbitmap.aspx
There are a few "screenshot" methods around that use that, like this one taken 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);
}
}

almulo
- 4,918
- 1
- 20
- 29
-
As it is also not mentioned on the website you quoted the example from: What is the 'visual' object and how do I use it? Where does it come from? – Lorgarn Oct 07 '15 at 12:12
-
1@Lorgarn All controls and view elements (TextBlocks, Grids, etc.) inherit from `Visual`. So you can pass any of them to this method to create an image file of it. But the idea is to pass a `Window` object to get a screenshot of the whole window. – almulo Oct 08 '15 at 08:34
-
Thanks. I tired that and it is working so far, except that all Windowdecorations are omitted... – Lorgarn Oct 08 '15 at 09:50
-
i know it is a old post, but how can i save a selected element with adorners as an image? – gmetax Jan 05 '16 at 20:03
-
I'm passing the current window, but GetDescendantBounds returns no bounds. – Mark13426 Aug 13 '16 at 23:09
5
Tested:
- In use in an enterprise WPF app.
- Tested on small part of an entire screen (can take a screenshot of any element on the screen).
- Tested with multiple monitors.
- Tested on a Window with WindowState=Normal and WindowState=Maximized.
- Tested with 96 DPI.
- Tested with 120 DPI (set font size to "125%" in Windows 10, then log out and log in).
- Avoids any use of Windows-Form-Style pixel calculations which have scaling issues in different DPI settings.
- Works even if part of the window is off the screen.
- Works even if another rogue window is covering 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; }
Command:
private async void OnScreenShotCommandAsync(FrameworkElement frameworkElement)
{
var result = await this.TryScreenshotToClipboardAsync(frameworkElement);
if (result == true)
{
// Success.
}
}
In constructor:
// See: https://stackoverflow.com/questions/22285866/why-relaycommand
// Or use MVVM Light to obtain 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>
The ElementName=Content
points to a named element somewhere else on the same XAML page. If you want to screenshot an entire window, you cannot pass the Window in (as we cannot set ClipToBounds
on a Window) but we can pass in a <Grid>
inside the Window.
<Grid x:Name="Content">
<!-- Content to take a screenshot of. -->
</Grid>

Contango
- 76,540
- 58
- 260
- 305
2
I faced the same problem on WPF and after a lot of trial and error I came with this solution:
First you need the following namespaces:
using System.Windows;
using System.Windows.Media.Imaging;
using System.IO.Stream;
Then create a function like this:
public bool ScreenShot(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;
}
}
I hope it can help someone else.