25

I would like to "copy to clipboard" what a Control of my WPF app draws on the screen. Therefore, I need to build a bitmap image from my Control current display.

Is there an easy way to do that ?

Thanks in advance.

Aurelien Ribon
  • 7,548
  • 3
  • 43
  • 54
  • This question/answer helped me as well: http://stackoverflow.com/questions/2557183/drawing-a-wpf-usercontrol-with-databinding-to-an-image – Aaron Hoffman Aug 26 '12 at 21:04

3 Answers3

46

I wouldn't call it easy...but the key component is the RenderTargetBitmap, which you can use as follows:

RenderTargetBitmap rtb = new RenderTargetBitmap((int)control.ActualWidth, (int)control.ActualHeight, 96, 96, PixelFormats.Pbgra32);
rtb.Render(control);

Well, that part is easy, now the RTB has the pixels stored internally...but your next step would be putting that in a useful format to place it on the clipboard, and figuring that out can be messy...there are a lot of image related classes that all interact one or another.

Here's what we use to create a System.Drawing.Image, which i think you should be able to put on the clipboard.

PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream stream = new MemoryStream();
png.Save(stream);
Image image = Image.FromStream(stream);

System.Drawing.Image (a forms image) cannot interact directly with the RenderTargetBitmap (a WPF class), so we use a MemoryStream to convert it.

Bubblewrap
  • 7,266
  • 1
  • 35
  • 33
  • Thank you, this was my answer. The nice thing is that Clipboard.SetImage() requires a BitmapSource, so I can directly give it the RenderTargetBitmap. Works like a charm. You can call it easy. Thanks again ! Now I need to figure out how to allow the user to manipulate the control visual (zooming it essentially) before exporting it to a fixed-resolution bitmap. i think I will copy the current display as a VisualBrush in a Viewbox, and let the user plays with it before exporting. – Aurelien Ribon Mar 26 '10 at 14:31
  • 5
    It's worth noting that, while this works for normal WPF components, any special controls that are just wrapping a component from a different airspace (like the Webbrowser control) are not compatible with RenderTargetBitmap. – nextgentech Feb 08 '13 at 06:25
  • 7
    Thank you past self! Over 8 years later I am in a situation where again I want to save a WPF control to a stream. But I just can't remember how it worked exactly. So I google it and to my surprise, I end up reading my own answer. :D – Bubblewrap Oct 23 '18 at 11:53
  • 1
    How do you capture the entire control that is also off the screen? – JeremyK Jan 22 '19 at 20:42
17

If the control you are trying to create a bitmap from is inside a StackPanel it won't work, you will just get an empty image.

Jaime Rodriguez has a good piece of code to get around this on his blog:

private static BitmapSource CaptureScreen(Visual target, double dpiX, double dpiY)
{
    if (target == null)
    {
        return null;
    }
    Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
    RenderTargetBitmap rtb = new RenderTargetBitmap((int)(bounds.Width * dpiX / 96.0),
                                                    (int)(bounds.Height * dpiY / 96.0),
                                                    dpiX,
                                                    dpiY,
                                                    PixelFormats.Pbgra32);
    DrawingVisual dv = new DrawingVisual();
    using (DrawingContext ctx = dv.RenderOpen())
    {
        VisualBrush vb = new VisualBrush(target);
        ctx.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
    }
    rtb.Render(dv);
    return rtb;
}
Björn
  • 3,098
  • 2
  • 26
  • 40
  • Damn I've been busting my brain with this for few hours now. One canvas that was inside a grid worked an other one did not. This works, even after years WPF can still surprise me. – CiucaS Jun 01 '23 at 08:20
2

This goes in your codebehind: (Or pass in the window.)

Not mine, but a mishmash of mine, and googling. Works in .Net 5 and WPF

    public void RenderThisWindow(string fullPngPath)
    {
        Image myImage = new Image();
        
        DrawingVisual drawingVisual = new DrawingVisual();

        RenderTargetBitmap bmp = new RenderTargetBitmap((int)Width, (int)Height, 120, 96, PixelFormats.Pbgra32);
        bmp.Render(this);
        myImage.Source = bmp;

        SaveToPng(this, fullPngPath);
    }
    
    private void SaveToPng(FrameworkElement visual, string fileName)
    {
        var encoder = new PngBitmapEncoder();
        SaveUsingEncoder(visual, fileName, encoder);
    }
    
    private void SaveUsingEncoder(FrameworkElement visual, string fileName, BitmapEncoder encoder)
    {
        RenderTargetBitmap bitmap = new RenderTargetBitmap((int)visual.Width, (int)visual.Width, 96, 96, PixelFormats.Pbgra32);
        bitmap.Render(visual);
        BitmapFrame frame = BitmapFrame.Create(bitmap);
        encoder.Frames.Add(frame);

        using (var stream = File.Create(fileName))
        {
            encoder.Save(stream);
        }
    }