4

I have a set of controls inside a WindowsFormsHost and I would like to capture the current view and just save it as an image, I however only get some Panel visible in the Image.

Is it possible to use the WindowsFormsHost as a "Visual" and capture the wrapped controls?

See my example:

<WindowsFormsHost x:Name="windowHost">
    <wf:Panel Dock="Fill" x:Name="basePanel"/>
</WindowsFormsHost>

If i were to add a Button or whatever to the basePanel this wouldn't be visible when exporting to a PNG with the following code:

 RenderTargetBitmap rtb = new RenderTargetBitmap(basePanel.Width, 
                                 basePanel.Height, 96, 96, PixelFormats.Pbgra32);
 rtb.Render(windowHost);

 PngBitmapEncoder pnge = new PngBitmapEncoder();
 pnge.Frames.Add(BitmapFrame.Create(rtb));
 Stream stream = File.Create("test.jpg");

 pnge.Save(stream);

 stream.Close();

Suggestions on why this might not work and maybe a possible work-around? I guess it's not really suppose to work this way, but one could really hope!

Filip Ekberg
  • 36,033
  • 20
  • 126
  • 183

2 Answers2

6

The content of the WindowsFormsHost is rendered by GDI+, like in a Windows Forms application, so you can't use RenderTargetBitmap for that since it is not rendered by WPF. Instead, you should use the GDI+ BitBlt function, which allows you to make a capture of an area on the screen.

See this post for an example


UPDATE: here's another version of the code, updated for use with WPF :

using System.Drawing;
...

public static ImageSource Capture(IWin32Window w)
{
    IntPtr hwnd = new WindowInteropHelper(w).Handle;
    IntPtr hDC = GetDC(hwnd);
    if (hDC != IntPtr.Zero)
    {
        Rectangle rect = GetWindowRectangle(hwnd);
        Bitmap bmp = new Bitmap(rect.Width, rect.Height);
        using (Graphics destGraphics = Graphics.FromImage(bmp))
        {
            BitBlt(
                destGraphics.GetHdc(),
                0,
                0,
                rect.Width,
                rect.Height,
                hDC,
                0,
                0,
                TernaryRasterOperations.SRCCOPY);
        }
        ImageSource img = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
            bmp.GetHBitmap(),
            IntPtr.Zero,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());
        return img;
    }
    return null;
}

Just call pass your WindowsFormsHost control as a parameter to the Capture method, and do whatever you like with the resulting ImageSource. For the definitions of BitBlt and GetDC, have a look at this website (I wrote that on my home computer, which I can't access from where I am now)

Community
  • 1
  • 1
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
4

A Windows Forms control also knows how to render itself, you don't have to jump through the screen capture hoop. Make it look like this:

    using (var bmp = new System.Drawing.Bitmap(basePanel.Width, basePanel.Height)) {
        basePanel.DrawToBitmap(bmp, new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height));
        bmp.Save(@"c:\temp\test.png");
    }
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Good point, I totally forgot about that method ! That's indeed much easier than using BitBlt, and it doesn't require any P/Invoke calls... – Thomas Levesque Nov 18 '09 at 17:03
  • Awesome.. However! This "only" caputes what you see, it doesn't capture all the content ( if you have a scrollbars ). Is it possible to say something like "Render the whole control and skip the scrollies" ? – Filip Ekberg Nov 19 '09 at 08:00
  • No. You could resize the control first but that rapidly runs out of gas. – Hans Passant Nov 19 '09 at 09:23