Solved

How to tell if a Windows Form has painted itself COMPLETELY

Posted on 2004-03-31
21
2,144 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
Comment Utility
What browser component are you using?
0
 

Author Comment

by:shifty_mc
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
Also, it looks like "Event 15" is significant, don't you think?
0
 
LVL 10

Expert Comment

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

Author Comment

by:shifty_mc
Comment Utility
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
Comment Utility
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
6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

 

Author Comment

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

Author Comment

by:shifty_mc
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

This article describes a simple method to resize a control at runtime.  It includes ready-to-use source code and a complete sample demonstration application.  We'll also talk about C# Extension Methods. Introduction In one of my applications…
Introduction Hi all and welcome to my first article on Experts Exchange. A while ago, someone asked me if i could do some tutorials on object oriented programming. I decided to do them on C#. Now you may ask me, why's that? Well, one of the re…
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…
This video explains how to create simple products associated to Magento configurable product and offers fast way of their generation with Store Manager for Magento tool.

744 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

19 Experts available now in Live!

Get 1:1 Help Now