Solved

How to tell if a Windows Form has painted itself COMPLETELY

Posted on 2004-03-31
21
2,149 Views
Last Modified: 2011-10-03
Hi,

I'm trying to capture the screen of a Form which contains a browser, which works fine if I do it manually (say once you press a button or something).

The problem is how to do this programatically as the browser is opening a web page which could take a little while, and the DownloadComplete event seems to fire too early (ie it just captures a blank screen - presumably a millisecond or so before the screen properly paints.

I've tried other events, both in the form and the contained browser - none of which seem to be fired at just the right moment when the screen displays the information. Thread.Sleep() doesn't work either as it delays everything.

Is there an event I've missed which will go at the right time? Or a workaround using threads?

I've also looked at intercepting WM_PAINT messages, but couldn't get it working.

Any ideas? Cheers.
0
Comment
Question by:shifty_mc
  • 9
  • 8
  • 2
  • +2
21 Comments
 
LVL 10

Expert Comment

by:eternal_21
ID: 10729295
What browser component are you using?
0
 

Author Comment

by:shifty_mc
ID: 10729348
Ummm...

private AxSHDocVw.AxWebBrowser axWebBrowser1;

that answer your question? It's just the 'Microsoft Web Browser' that you can add to the toolbox in Visual Studio.
0
 
LVL 10

Accepted Solution

by:
eternal_21 earned 200 total points
ID: 10729931
Instead of using the Navigate method, and the DownloadComplete event, try using the Navigate2 method and the NavigateComplete2 event:

// Code to opent the website:

  object o = new object();
  object url = "http://www.yahoo.com/";
  this.axWebBrowser1.NavigateComplete2 += new AxSHDocVw.DWebBrowserEvents2_NavigateComplete2EventHandler(this.axWebBrowser1_NavigateComplete2);
  this.axWebBrowser1.Navigate2(ref url, ref o, ref o, ref o, ref o);

// NavigateComplete2 Event Handler:

  private void axWebBrowser1_NavigateComplete2(object sender, AxSHDocVw.DWebBrowserEvents2_NavigateComplete2Event e)
  {
    System.Threading.Thread.Sleep(250);
    Application.DoEvents();
    // Get your screen shot here
  }
0
 

Author Comment

by:shifty_mc
ID: 10731661
Ok, I changed from Navigate to Navigate2 and DownloadComplete to NavigateComplete2 but still no joy :-(

If it helps, I've overriden wnproc in order to log the events, and this is the result (the messages are ints - I've just spent a little time looking to find out which ones are which with no luck - I'll post this and carry on - don't know if I'm looking in all the wrong places- they must be out there somewhere!)

Loading Browser                                       (in Form_Load)
Navigating to file://C:\webpagetest.html    (still in Form_Load and calling Navigate2() method)
Download complete event firing!
Event firing: 24
Event firing: 70
Event firing: 70
Event firing: 28
Event firing: 134
Event firing: 6
Event firing: 133
Event firing: 20
Event firing: 71
Event firing: 5
Event firing: 3
Event firing: 127
Event firing: 127
Event firing: 127
NavigateComplete2 Event Firing!!
Sleeping Thread
Back from sleeping thread
Calling Application.DoEvents()
Event firing: 15
Back from Application.DoEvents()
Calling capturescreen method
Calling SendMessage
Event firing: 791
Event firing: 175
Event firing: 20
Event firing: 792
Back from SendMessage
Saving... test.bmp
Back from capturescreen method
Event firing: 132
Event firing: 132
Event firing: 32
Event firing: 49721
Event firing: 512
Event firing: 675
Event firing: 134
Event firing: 6
Event firing: 28
Event firing: 70
Event firing: 28
Event firing: 134
Event firing: 6
Event firing: 134
Event firing: 6
Event firing: 28

The Thread.Sleep() bit you mentioned still holds up everything as it did with DownloadComplete. The NavigateComplete2 is fired later than DownloadComplete as you can see, which is good.  Also, the capture screen method works by using the line
SendMessage(hWnd, Win32.WM_PRINT, hDCMem, new IntPtr(Win32.PRF_CHILDREN | Win32.PRF_CLIENT | Win32.PRF_ERASEBKGND | Win32.PRF_NONCLIENT | Win32.PRF_OWNED));
which is obviously creating a PRINT event - so don't know if this is messing things up.

Thanks
0
 
LVL 10

Expert Comment

by:eternal_21
ID: 10732612
I *did* test this code, at first it didn't work.  Then I added the Application.DoEvents() after the call to Thread.Sleep.  It worked fine for me.

Try this for your form capture?

    [System.Runtime.InteropServices.DllImport("gdi32.dll")] private static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, System.Int32 dwRop);

    static Bitmap CaptureForm(System.Windows.Forms.Form form)
    {
      Graphics g1 = form.CreateGraphics();
      Bitmap bitmap = new Bitmap(form.ClientRectangle.Width, form.ClientRectangle.Height, g1);
      Graphics g2 = Graphics.FromImage(bitmap);
      IntPtr dc1 = g1.GetHdc();
      IntPtr dc2 = g2.GetHdc();
      BitBlt(dc2, 0, 0, form.ClientRectangle.Width, form.ClientRectangle.Height, dc1, 0, 0, 13369376);
      g1.ReleaseHdc(dc1);
      g2.ReleaseHdc(dc2);
      return bitmap;
    }

And here was my code:

  private void Form1_Load(object sender, System.EventArgs e)
  {
    object o = new object();
    object url = "http://www.yahoo.com/";
    this.axWebBrowser1.NavigateComplete2 += new AxSHDocVw.DWebBrowserEvents2_NavigateComplete2EventHandler(this.axWebBrowser1_NavigateComplete2);
    this.axWebBrowser1.Navigate2(ref url, ref o, ref o, ref o, ref o);
  }

  void axWebBrowser1_NavigateComplete2(object sender, AxSHDocVw.DWebBrowserEvents2_NavigateComplete2Event e)
  {
    System.Threading.Thread.Sleep(250);
    Application.DoEvents();
    System.Drawing.Bitmap bitmap;
    bitmap = CaptureForm(this);
    bitmap.Save("c:\\test.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
  }
0
 
LVL 10

Expert Comment

by:eternal_21
ID: 10732620
By the way, how did you log the form events using WndProc?  (Just curious, I have been trying to do that for a while).
0
 
LVL 10

Expert Comment

by:eternal_21
ID: 10732763
Also, it looks like "Event 15" is significant, don't you think?
0
 
LVL 10

Expert Comment

by:eternal_21
ID: 10732768
Can you post that trace again WITHOUT the DoEvents, and see where that Event 15 is sitting?
0
 

Author Comment

by:shifty_mc
ID: 10732870
yeah, I saw that but couldn't work out what it was, hold on a minute - I've just tried what you said and am organising in order to post back....
0
 

Author Comment

by:shifty_mc
ID: 10732966
Right,

Unfortunately I can't use BitBlt because I'm capturing a screen that might not be visible, so I have to use SendMessage (as I said before I know it works because I've tried implementing it by clicking a button when the form is half off the screen and it's fine).
The whole code for the method I use is...

public Bitmap GrabWindow(IntPtr hWnd)
{
      Class1.Log("Capturing Browser Screen...");
      IntPtr hBmp;
      Bitmap MyBitmap=null;

      IntPtr hDCMem = Win32.CreateCompatibleDC((IntPtr)null);
      Rectangle rect = new Rectangle(this.Left,this.Top, this.Width,this.Height);
      IntPtr hDC = Win32.GetWindowDC(hWnd);
      hBmp = Win32.CreateCompatibleBitmap(hDC, rect.Width, rect.Height);
      Win32.ReleaseDC(hWnd, hDC);
      Graphics grfxScreen = Graphics.FromHwnd(hWnd);
      MyBitmap = new Bitmap((int) this.Width, (int) this.Height, grfxScreen);
      Graphics grfxBitmap = Graphics.FromImage(MyBitmap);
      IntPtr hdcScreen = grfxScreen.GetHdc();
      IntPtr hdcBitmap = grfxBitmap.GetHdc();
      IntPtr hOld = Win32.SelectObject(hDCMem, hBmp);
      Class1.Log("Calling SendMessage");
      Win32.SendMessage(hWnd, Win32.WM_PRINT, hDCMem, new IntPtr(Win32.PRF_CHILDREN | Win32.PRF_CLIENT | Win32.PRF_ERASEBKGND | Win32.PRF_NONCLIENT | Win32.PRF_OWNED));
      Class1.Log("Back from SendMessage");
      Win32.SelectObject(hDCMem, hOld);
      Win32.DeleteObject(hDCMem);
      MyBitmap = Bitmap.FromHbitmap(hBmp);

      Class1.Log("Saving... test.bmp");                  
      MyBitmap.Save("C:\\test.bmp");
      return MyBitmap;                  
}

(The Win32 class is just where I stored all the dllimport stuff)
I realise there's a lot of calls using these dllimports, but I've tried it with c# graphics and just couldn't get it to work - no idea why - it just returned a solid black bitmap.

Other than that the code's pretty identical. The Class1.Log stuff is just a quick method I created to write anything I want to my log.txt file, and to log the events I just used...

protected override void WndProc(ref Message m)
{
      Class1.Log("Event firing: " + m.Msg);
      base.WndProc(ref m);
}

And finally, I just ran it without the DoEvents and got the log...

Loading Browser
Navigating to file://C:\webpagetest.html
Event firing: 24
Event firing: 70
Event firing: 70
Event firing: 28
Event firing: 134
Event firing: 6
Event firing: 133
Event firing: 20
Event firing: 71
Event firing: 5
Event firing: 3
Event firing: 127
Event firing: 127
Event firing: 127
NavigateComplete2 Event Firing!!
Sleeping Thread
Back from sleeping thread
Calling capturescreen method
Capturing Browser Screen...
Calling SendMessage
Event firing: 791
Event firing: 175
Event firing: 20
Event firing: 792
Back from SendMessage
Saving... test.bmp
Back from capturescreen method
Event firing: 15
Event firing: 134
Event firing: 6
Event firing: 28
Event firing: 70
Event firing: 28
Event firing: 134
Event firing: 6
Event firing: 134
Event firing: 6
Event firing: 28
Event firing: 70
Event firing: 133
Event firing: 20
Event firing: 71
Event firing: 28
Event firing: 134
Event firing: 6
Event firing: 15
Event firing: 132
Event firing: 132
Event firing: 32
Event firing: 49721
Event firing: 512
Event firing: 675
Event firing: 132
Event firing: 132
Event firing: 32
Event firing: 49721
Event firing: 512
Event firing: 675
Event firing: 134
Event firing: 6
Event firing: 28
Event firing: 70
Event firing: 28
Event firing: 134
Event firing: 6
Event firing: 134
Event firing: 6
Event firing: 28
0
Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

 

Author Comment

by:shifty_mc
ID: 10732981
Oh, and I call it from NavigateComplete2 using
image = GrabWindow(this.Handle);
0
 

Author Comment

by:shifty_mc
ID: 10733105
I believe the 15 event is WM_PAINT, but I'm not exactly sure what that means - surely it should work as in the original log post which includes the DoEvents, because it paints before it captures the screen?

Phew, I don't know. This is a long mission!
0
 

Author Comment

by:shifty_mc
ID: 10733141
I found a webpage that says there are some problems with NavigateComplete2 firing too early, and explains how to fix it in C++ using BEGIN_DISPATCH_MAP, but I have no idea how this would work in C#, or indeed if this is the actual problem, but here it is anyway...
http://www.codeguru.com/Cpp/I-N/ieprogram/comments.php/c4403/?thread=52026
0
 
LVL 10

Expert Comment

by:eternal_21
ID: 10735408
I think there are other "events" going on when we call Application.DoEvents that are not generating Windows messages. I tried creating a WebBrowser control (inheriting from AxSHDocVw.AxWebBrowser), and monitoring WndProc calls, but was unable to come up with anything consistent.  Any messages related to paint are sent multiple time for a given refresh of a web page - there is no "Totally Painted" message.
0
 
LVL 10

Expert Comment

by:eternal_21
ID: 10735494
I have also found that this method does not work very well (at least on my system) with the AxWebBrowser control.

What is it exactly you are trying to do, generate images of web pages?
0
 

Author Comment

by:shifty_mc
ID: 10736292
Yeah, that's exactly it - generate an image of a webpage.

Ok, I've finally got (after 3 solid days out of my life) what I think is a feasible workaround. It's actually extremely simple - I swear I've gone back more times than I've gone forwards, but this is all a learning process for me so I guess it's good in the long run.

The answer is a combination of the events DocumentComplete and Application.Idle.

When DocumentComplete fires and returns the url that you initially specified (it might fire a few times) you can capture the screen. The advantage of this over NavigateComplete2 is that the final DocumentComplete returns the originally specified url, so you know what to check for, whereas this url is returned in the first NavigateComplete2 (assuming these events are fired a few times - which happened quite often to me with more complicated sites).

And then to combat the delay problem - ie if you tried to capture straightaway, there is a lag between the event firing and the screen being suitable for capture - the Application.Idle event can be used. I only create this when the final DocumentComplete is received, and then I can use...

private void OnIdle( object sender, System.EventArgs e)
{
      CountIdles++;
      if (CountIdles==10)
            image = GrabWindow();
}

This basically just waits a little bit, which I can do because the event is called regularly, not just once.
And finally I got the result I wanted. It's still not perfect, and as I said - the code is horrible, but I don't care any more!

If anyone comes up with a better solution, feel free to point me in the right direction.

eternal_21 - thanks for your help, I'll award the points to you for your time,

Cheers.
0
 
LVL 5

Expert Comment

by:JMoon5FTM
ID: 10737738
Glad you got everything to work out!

For future reference, this site defines the values of many window messages:

http://cvs.sourceforge.net/viewcvs.py/nsis/NSIS/Include/WinMessages.nsh?rev=1.2

So, 15 (0xF - the table is in hex) is indeed WM_PAINT.

:)
0
 

Expert Comment

by:carlblanchard
ID: 11038860
shifty_mc, Hi it looks like youve done just what im trying to do,

Save a webpage as a single image on the harddrive, being in mind webpages have different heights, therefore being able to capture the entire page and not just visable window...

I was wondering if you could post the code for your WIN32 class, as im having trouble in working certain elemements of your code out.

Look forward to hearing from you shortly


Carl
0
 

Expert Comment

by:derKrome
ID: 11121302
shifty, if you could post tips on the Win32 class you're using, it would greatly help me out as well.  

you can email me too: derkrome AT yahoo DOT com

cheers!
0
 

Expert Comment

by:derKrome
ID: 11123289
After some searching, I found a good Win32 .net library that exposes all of the needed defines & functions (thecodeproject.com/csharp/Win32.asp).

My only difficulty now is that some elements inside the web-control are not being grabbed.  For example, an html file is fine, but if you navigate to an Office file, the capture is all white.

ideas?
0
 

Author Comment

by:shifty_mc
ID: 11127047
Hi,

Sorry I didn't reply before - just wasn't checking my emails properly.

I think the class you found was the exact one I was using - I'm not that experienced a programmer and a lot of my code was amalgamations of other people's.

I'm afraid I only got to where you are now and stopped - I didn't need it for any other purpose other than capturing html.  I believe the problem is because this method isn't the greatest in terms of functionality - it just 'happens to work', if you see what I mean, and doesn't actually deal with the ins and outs. In other words I don't think there's an easy answer, but then I don't know for sure,and people were telling me for ages you couldn't do it with html, so who knows.

In my travels I found a very thorough article on this exact issue on codeproject.com (in fact the only article I found in about a month of doing this and it was published about 2 days after I'd got mine working - typicle).  It's in C++ and details how to do this 'properly' (which appears to be a very big issue indeed - I certainly couldn't understand it (but admittedly didn't look too much into it). However, I don't know if it would be of any use to the specific issue of opening office documents.

http://www.codeproject.com/internet/htmlimagecapture.asp

Hope that helps, good luck
0

Featured Post

Is Your Active Directory as Secure as You Think?

More than 75% of all records are compromised because of the loss or theft of a privileged credential. Experts have been exploring Active Directory infrastructure to identify key threats and establish best practices for keeping data safe. Attend this month’s webinar to learn more.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
C# TextBox 11 30
Problem of RegEx to match the first occurence of 10 36
Connection String 16 43
Eagerly loading related objects in entity framework 5 26
This article introduced a TextBox that supports transparent background.   Introduction TextBox is the most widely used control component in GUI design. Most GUI controls do not support transparent background and more or less do not have the…
This article aims to explain the working of CircularLogArchiver. This tool was designed to solve the buildup of log file in cases where systems do not support circular logging or where circular logging is not enabled
In this video I am going to show you how to back up and restore Office 365 mailboxes using CodeTwo Backup for Office 365. Learn more about the tool used in this video here: http://www.codetwo.com/backup-for-office-365/ (http://www.codetwo.com/ba…
Learn how to create flexible layouts using relative units in CSS.  New relative units added in CSS3 include vw(viewports width), vh(viewports height), vmin(minimum of viewports height and width), and vmax (maximum of viewports height and width).

867 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

21 Experts available now in Live!

Get 1:1 Help Now