22

I am trying to capture the content of the WebBrowser control. DrawToBitmap() would work perfectly, but it is not supported in documentation for the WebBrowser control. I have been trying to find another way to capture the contents of the WebBrowser control and save them to a local image file.

Does anyone have any workarounds or other methods to save the contents of the WebBrowser control to a local image file?

Matt
  • 2,078
  • 2
  • 27
  • 40
  • A related q/a: [How to fix a opacity bug with DrawToBitmap on WebBrowser Control?](http://stackoverflow.com/q/21697048/1768303) – noseratio Feb 18 '14 at 08:11
  • [Belated but maybe of interest](http://stackoverflow.com/questions/24343393/webbrowser-madness?noredirect=1#comment37647172_24343393) - The solution at the end is simple and seems to be reliable. – TaW Jun 22 '14 at 09:35

4 Answers4

38

The Control.DrawToBitmap doesn't always work so I resorted to the following native API calls that provide more consistent results:

The Utilities class. Call Utilities.CaptureWindow(Control.Handle) to capture a specific control:

public static class Utilities
{
    public static Image CaptureScreen()
    {
        return CaptureWindow(User32.GetDesktopWindow());
    }

    public static Image CaptureWindow(IntPtr handle)
    {

        IntPtr hdcSrc = User32.GetWindowDC(handle);

        RECT windowRect = new RECT();
        User32.GetWindowRect(handle, ref windowRect);

        int width = windowRect.right - windowRect.left;
        int height = windowRect.bottom - windowRect.top;

        IntPtr hdcDest = Gdi32.CreateCompatibleDC(hdcSrc);
        IntPtr hBitmap = Gdi32.CreateCompatibleBitmap(hdcSrc, width, height);

        IntPtr hOld = Gdi32.SelectObject(hdcDest, hBitmap);
        Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, ApiConstants.SRCCOPY);
        Gdi32.SelectObject(hdcDest, hOld);
        Gdi32.DeleteDC(hdcDest);
        User32.ReleaseDC(handle, hdcSrc);

        Image image = Image.FromHbitmap(hBitmap);
        Gdi32.DeleteObject(hBitmap);

        return image;
    }
}

The Gdi32 class:

public class Gdi32
{
    [DllImport("gdi32.dll")]
    public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop);
    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, int nHeight);
    [DllImport("gdi32.dll")]
    public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
    [DllImport("gdi32.dll")]
    public static extern bool DeleteDC(IntPtr hDC);
    [DllImport("gdi32.dll")]
    public static extern bool DeleteObject(IntPtr hObject);
    [DllImport("gdi32.dll")]
    public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
}

The User32 class:

public static class User32
{
    [DllImport("user32.dll")]
    public static extern IntPtr GetDesktopWindow();
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowDC(IntPtr hWnd);
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
    [DllImport("user32.dll")]
    public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
}

The constants used:

    public const int SRCCOPY = 13369376;

The structs used:

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int left;
    public int top;
    public int right;
    public int bottom;
}

A friendly Control extension method:

public static class ControlExtensions
{
    public static Image DrawToImage(this Control control)
    {
        return Utilities.CaptureWindow(control.Handle);
    }
}

This is a code snippet from my CC.Utilities project and I specifically wrote it to take screenshots from the WebBrowser control.

Cory Charlton
  • 8,868
  • 4
  • 48
  • 68
  • 1
    @Matt: Hope it helps. I had left out the Gdi32 class from my original post but fixed that now. – Cory Charlton Mar 12 '10 at 17:18
  • 1
    @Cory Charlton: It works great! My only question is say I have a page in the WebBrowser control that extends onto more than one page. There is no way using this to grab that? – Matt Mar 12 '10 at 17:25
  • 1
    @Matt: I believe this only gets the visible part of the control. I haven't tried it but you could try making the control large enough for the entire content but not necessarily visible on a form. I expect you'll get an empty image in this case but it's worth a try. – Cory Charlton Mar 12 '10 at 18:23
  • Yeah it seems to be capturing the visible portion of the control only. I am looking for a solution to contain the entire contents of the WebBrowser control. I will have to keep messing around. If you have any other thoughts for that please let me know. Thanks – Matt Mar 12 '10 at 20:04
  • Hello, I just tried in my back-end application. It does NOT work!!! Because it is a back-end process, the WebBrowser is not shown. So invalid image is captured. – Alex Yeung Dec 30 '10 at 02:56
  • @Alex Yeung: I stated in my comment it only works for the visible portion of the control... – Matt Jan 24 '11 at 13:10
  • 5
    @Cory I tried this approach, unfortunately it doesn't seem to work. All of the screenshots that it generates are just black boxes, including for URLs that work with the standard DrawToBitmap call. – Justin May 24 '11 at 23:22
  • Will this work for individual HTML Elements, rather than an entire web page? – Will Jan 31 '13 at 03:27
  • Does anyone know a method that will also grab the nonvisible components of the control? – Robert Christ Apr 15 '14 at 21:29
  • How can I take the complete scroll window screen shot. It does work but only visible area on the webbrowser control however its a scroll window with more than 2 3 pages – Sandeep May 25 '16 at 21:52
3

The following method can capture the entire window image even if the window is larger than the size of screen. Then it can capture the image of the contents of the page if the window be resized to the webBrowser.Document.OffsetRectangle.Size

class NativeMethods
{
    [ComImport]
    [Guid("0000010D-0000-0000-C000-000000000046")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IViewObject
    {
        void Draw([MarshalAs(UnmanagedType.U4)] uint dwAspect, int lindex, IntPtr pvAspect, [In] IntPtr ptd, IntPtr hdcTargetDev, IntPtr hdcDraw, [MarshalAs(UnmanagedType.Struct)] ref RECT lprcBounds, [In] IntPtr lprcWBounds, IntPtr pfnContinue, [MarshalAs(UnmanagedType.U4)] uint dwContinue);
    }

    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    public static void GetImage(object obj, Image destination, Color backgroundColor)
    {
        using(Graphics graphics = Graphics.FromImage(destination))
        {
            IntPtr deviceContextHandle = IntPtr.Zero;
            RECT rectangle = new RECT();

            rectangle.Right = destination.Width;
            rectangle.Bottom = destination.Height;

            graphics.Clear(backgroundColor);

            try
            {
                deviceContextHandle = graphics.GetHdc();

                IViewObject viewObject = obj as IViewObject;
                viewObject.Draw(1, -1, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, deviceContextHandle, ref rectangle, IntPtr.Zero, IntPtr.Zero, 0);
            }
            finally
            {
                if(deviceContextHandle != IntPtr.Zero)
                {
                    graphics.ReleaseHdc(deviceContextHandle);
                }
            }
        }
    }
}

Usage :

Bitmap screenshot = new Bitmap(1024, 768);
NativeMethods.GetImage(webBrowser.ActiveXInstance, screenshot, Color.White);
Ahmad
  • 8,811
  • 11
  • 76
  • 141
  • Is above able to take screenshot if the html page has scrolls. I tried using @Cory Charlton it does work but only the visible screen not the entire page – Sandeep May 26 '16 at 15:02
0

I am using DrawToBitmap (Visual Studio 2008 C#) to capture big images (user signed invoices,content out of the screen). Basically it is working well but I am getting blank images. About 100 employees are using this software, about every 2 weeks I can see one blank image.

I have done a lot of testing and one funny thing I found is: I created a button to generate the image from the webbrowser. Usually is OK but if I click the webbrowser first, then click the create-button, the blank image will appear.

John Jin
  • 1
  • 1
  • 4
    The blank image is a complaint seen a lot. Microsoft's own documentation on the method tell people not to use it but everyone still does because this is a much needed method. I've been looking for a solution on this for about a week. I have to rely on IE doing the rendering so cannot use other solutions. It'd be interesting to see if there is a better method to get consistent results from the DrawToBitmap method. – Frank Hale Feb 24 '12 at 14:49
  • Just a suggestion, something I may have to do myself if I cant resolve it otherwise, you should be able to determine that the image is blank, not only will every pixel be white, but if they are all white then a bit of math should show that there is only one color pixel in the image. – John Faulkner Apr 06 '14 at 03:07
0

I used OleDraw method as in this topic on SO, but integrated it in a class derived from WebBrowser. This allows to do normal Control.DrawToBitmap not only for the WebBrowser, but for a form with WebBrowser in it as well. This also works if the form is hidden (covered by another form, including MDI parent form) and should work when user has locked session with Win+L (I haven't tested it).

public class WebBrowserEx : WebBrowser
{
    private const uint DVASPECT_CONTENT = 1;

    [DllImport("ole32.dll", PreserveSig = false)]
    private static extern void OleDraw([MarshalAs(UnmanagedType.IUnknown)] object pUnk,
        uint dwAspect,
        IntPtr hdcDraw,
        [In] ref System.Drawing.Rectangle lprcBounds
    );

    protected override void WndProc(ref Message m)
    {
        const int WM_PRINT = 0x0317;

        switch (m.Msg)
        {
            case WM_PRINT:
                Rectangle browserRect = new Rectangle(0, 0, this.Width, this.Height);

                // Don't know why, but drawing with OleDraw directly on HDC from m.WParam.
                // results in badly scaled (stretched) image of the browser.
                // So, drawing to an intermediate bitmap first.
                using (Bitmap browserBitmap = new Bitmap(browserRect.Width, browserRect.Height))
                {
                    using (var graphics = Graphics.FromImage(browserBitmap))
                    {
                        var hdc = graphics.GetHdc();
                        OleDraw(this.ActiveXInstance, DVASPECT_CONTENT, hdc, ref browserRect);
                        graphics.ReleaseHdc(hdc);
                    }

                    using (var graphics = Graphics.FromHdc(m.WParam))
                    {
                        graphics.DrawImage(browserBitmap, Point.Empty);
                    }
                }
                // ignore default WndProc
                return;

        }

        base.WndProc(ref m);
    }
}
Roman
  • 515
  • 5
  • 16