2

After failing to use the control.drawtobitmap in c#, my second option was to take screenshots of the desktop and crop out the desired sections. My hiccup shows up once i switch user accounts, although the program does not crash, once the user is switched the program generates pure black images only.

I used this code as a reference: WebBrowser.DrawToBitmap() or other methods?

I guess logically this makes sense as this would help windows save resources.

What options/ solutions do i have in my situation?

Edit 1 made a modification to the code for testing:

        int c = 0;
        while (true)
        {
            try
            {
                c++;
                Rectangle formBounds = this.Bounds;
                Bitmap bmp = new Bitmap(formBounds.Width, formBounds.Height);
                using (Graphics g = Graphics.FromImage(bmp))
                    g.CopyFromScreen(formBounds.Location, Point.Empty, formBounds.Size);
                bmp.Save("picture" + c.ToString() + ".jpg");
                Thread.Sleep(5000);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
            }
        }

this works perfectly while on the user account, but as soon as i switch users, it returns the exception: The handle is invalid.

Any ideas?

Edit 2:

The bug in DrawToBitmap is not exactly random... if i used the code you supplied:

Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
bmp.Save(".\\picture.jpg");

it works perfectly, example: http://oi61.tinypic.com/1z23ynp.jpg

However, the moment i right-click on the web-browser control, DrawToBitmap will return a blank image.

example: http://oi60.tinypic.com/9ay0yc.jpg

So i can easily overcome this bug by adding

((Control)webbrowser1).Enabled = false;

this makes any clicking impossible on the web-browser, but unfortunately to deactivate it would render my project useless as its main function is to emulate mouse clicks on a web-browser control. although this might also be a problem if the window is hidden.

currently im looking at this post, where code is supplied to give you a window handle.

Simulate click into a hidden window it seems it might be of some value... do have a look.

Community
  • 1
  • 1
Msegling
  • 365
  • 3
  • 12

4 Answers4

5

What were the problems you had with DrawToBitmap? It works fine here, (W8.1, VS2013) even with a Webcontrol and also after switching users. (But see the edit at the end for the conditions!)

Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
this.DrawToBitmap(bmp, this.ClientRectangle);
// Clipboard.SetImage(bmp); for testing only
bmp.Dispose();

Here is code to take a screenshot of your window:

Rectangle formBounds = this.Bounds;
Bitmap bmp = new Bitmap(formBounds.Width, formBounds.Height );
using (Graphics g = Graphics.FromImage(bmp))
       g.CopyFromScreen(formBounds.Location, Point.Empty, formBounds.Size);
//Clipboard.SetImage(bmp); for testing only
bmp.Dispose();

I can switch users like I want, the program keeps working.

BTW, the link you posted is really old, many things may have improved.

Edit:

With the updated question things are a lot clearer.

So you want to continuously get a screenshot of your program even when the user has changed, right? and you want to display a WebControl, right?

A user can have three types of desktop: the logon/logoff screen, the screensaver screen and one or more normal desktop screen(s). But while the user is logged off he has no desktop screen at all.

Therefore the screenshot method will not work if the user has no active desktop, neither as g.CopyFromScreen, which will cause a GDI-error nor using a window handle like in the various solutions on the web, including the ones your link leads to. All these will, at best, show a blank or black screen.

So the DrawToBitmap method is the only one that works.

You wrote that it has random errors. That's not what I see.

The problems come in predictably when the user interacts with the WebBrowser in any way. This includes scrolling or clicking with or without navigation. After these interactions the WebBrowser will draw itself as an empty box until its URL is reloaded - not only refreshed - but really reloaded by webBrowser1.Uri = new Uri(uriPath). This can be done, see my other post

The WebBrowser also has another issue when doing a DrawToBitmap: It will fail (with the said empty box) for any pages that include an <input type="text" element. I'm not sure what's the best way to workaround this, let alone why it happends in the first place.. A screenshot method doesn't have that specific problem.

Edit 2:

The code the OP has dug up code which, using a call to PrintWindow, seems to solve all problems we had: It works while being logged off, works with Refeshing even after clicking in the WebBrowser and scrapes all pages, including those with textual input fields. Hoorah!

After cutting down the slack here is a version that can create a copy of the Form or just the WebBroser (or any other Control) with or without borders:

[DllImport("user32.dll")]
public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);

public Bitmap CaptureWindow(Control ctl)
{
    //Bitmap bmp = new Bitmap(ctl.Width, ctl.Height);  // includes borders
    Bitmap bmp = new Bitmap(ctl.ClientRectangle.Width, ctl.ClientRectangle.Height);  // content only
    using (Graphics graphics = Graphics.FromImage(bmp))
    {
        IntPtr hDC = graphics.GetHdc();
        try      { PrintWindow(ctl.Handle, hDC, (uint)0);   }
        finally  { graphics.ReleaseHdc(hDC);                }
    }
    return bmp;
}
Community
  • 1
  • 1
TaW
  • 53,122
  • 8
  • 69
  • 111
  • Thanks for your reply, however DrawToBitmap has a bug that returns a blank images at random times. If you could post a solution to doing a screenshot that functions after switching users, i would be extremely grateful, although all my attempts have returned solid black images. – Msegling Jun 20 '14 at 17:35
  • See my edited post! Does switching users also involve changes in the screen resolution/color depth etc..? – TaW Jun 20 '14 at 18:29
  • Saw your edit. Now we're talking. But I am __puzzled__ by my current results. Can you please __describe the random errors?__ – TaW Jun 21 '14 at 12:53
  • Please have a look at my own question, regarding the problem [here](http://stackoverflow.com/questions/24343393/webbrowser-madness). Everthing works, unless the page has a textual input field.. – TaW Jun 21 '14 at 21:17
  • it seems i found some code that has solved the problem... for me at least, please have a look when u have some time. – Msegling Jun 21 '14 at 22:11
  • yes, this seems to solve __all__ problems. Great!! I have made it simpler and a bit more flexible, see my last edit! You may want to include System.Drawing.Imaging.ImageFormat.Jpg in your save command to make things waterproof. – TaW Jun 22 '14 at 08:56
  • that code is a lot more simplified, ill definitely make use of it. i have posted a new question [here](http://stackoverflow.com/questions/24347832/invoking-webbrowser-contextmenu), have a look at it when you have some free time. – Msegling Jun 22 '14 at 15:09
  • Good. If you use it you please consider _accepting_ my answer. I would like to close my own question as a duplicate of yours, but can't until yours has an accepted or upvoted answer.. – TaW Jun 22 '14 at 15:16
2

Finally this code seems to work even when i have switched users.

Code to take screen shot of any unsaved Notepad process ("Untitled - Notepad")

    private void Form1_Load(object sender, EventArgs e)
    {
        //loop for debugging
        int c = 0;
        while(true)
        {
            c++;
            System.Drawing.Bitmap image = CaptureWindow(FindWindow(null, "Untitled - Notepad"));
            image.Save(".\\picture"+c.ToString()+".jpg");
            Thread.Sleep(5000);
        }

    }

    [DllImport("user32.dll")]
    public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
    [DllImport("user32.dll")]
    public static extern IntPtr GetWindowDC(IntPtr hWnd);
    [DllImport("user32.dll", SetLastError = true)]
    static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    public System.Drawing.Bitmap CaptureWindow(IntPtr hWnd)
    {
        System.Drawing.Rectangle rctForm = System.Drawing.Rectangle.Empty;
        using (System.Drawing.Graphics grfx = System.Drawing.Graphics.FromHdc(GetWindowDC(hWnd)))
        {
            rctForm = System.Drawing.Rectangle.Round(grfx.VisibleClipBounds);
        }
        System.Drawing.Bitmap pImage = new System.Drawing.Bitmap(rctForm.Width, rctForm.Height);
        System.Drawing.Graphics graphics = System.Drawing.Graphics.FromImage(pImage);
        IntPtr hDC = graphics.GetHdc();

        try
        {
            PrintWindow(hWnd, hDC, (uint)0);
        }
        finally
        {
            graphics.ReleaseHdc(hDC);
        }
        return pImage;
    }

Note that the window may be hidden but it must still be maximized on the user account to get a complete screen shot.

Msegling
  • 365
  • 3
  • 12
  • Although this works great for most windows I have found that in Windows 10, Microsoft Edge still produces a black capture. Maybe it is because MS Edge uses DirectX for drawing? – GeoffCoope Nov 19 '15 at 19:05
  • @GeoffCoope I have unfortunately not tested this method on windows 10, however i don't think that's the problem. This method will take a snipping of the entire handles window, including the windows frame, so if the method returns a black image, the issue is with c#. If the image renders correctly but only the browser control appears black i would guess that it is a issue with MS Edge. Definitely do some more troubleshoot and also check out TAWs solution, marked as the solution, its a more condensed version of mine. Let me know if you make any progress. – Msegling Nov 20 '15 at 23:03
  • 1
    @Msegling I think that WPF windows cause the black screen capture. Microsoft Edge is a WPF application. I can capture Chrome and IE 11 fine passing in the HWND and capturing the web page area of the window (behind other windows) but not with Edge. The only solution I have found so far is to force the Edge app forward and do a standard copyfromscreen capture using the rect from the class "Windows.UI.Core.CoreWindow". This is after extensive tinkering with Spy++ and Inspect. If anybody manages to get a capture while it is a background window then please let me know. – GeoffCoope Nov 26 '15 at 12:04
1

Digital Rights Management may be stopping this, because Windows adds protection for digital media.

If, for example, you are attempting to create a screen capture of something in Media Player or Media Center using Microsoft's rendering of Graphics - yes, Microsoft is going to "protect you from any potential lawsuit."

Try this: Click "Print Screen" on your keyboard and then go into Microsoft Paint and try pasting your screen capture into it. Is anything there?

  • In fact some video overlay cards like TV cards will also do that even without DRM. But the problem seems to come and go either randomly or when switching users. So both reason should not apply. – TaW Jun 20 '14 at 18:50
0

I faced with the same problem and I couldn't use CopyFromScreen method because my form with WebBrowser could be hidded. PrintWindow method also didn't work well generating images with black areas, especially when my MDI form partially covered by MDI parent form.

Finally I used OleDraw method as in this topic on SO, but integrated it in a class derived from WebBrowser. Basically, it just overrides standard control reaction to WM_PRINT message. 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