Link to home
Start Free TrialLog in
Avatar of raheelasadkhan
raheelasadkhanFlag for Pakistan

asked on

HTMLDocument events in C#

Hello,

I am using the Web Browser ActiveX control in C# (VS2003 Framework 1.1). The following code traps click events various elements (mostly <SPAN> elements. It works very nicely except that sometimes, the events simply stop working. When this happens, I have to call LoadHtmlDocument() to reload the document and reassociate events.

As you can imagine, the need to reload the document every time this happens breaks the flow of the application. Please advise.

Thanks,

Khan

private void LoadHtmlDocument ()
{
   NavigateHome();
   // wbAdministrator is the Web Browser ActiveX control
   while (this.wbAdministrator.Busy)
   {
      System.Windows.Forms.Application.DoEvents();
   }
   AssociateHtmlDocumentEvents();
}

private void NavigateHome ()
{
   object   obj   = null;
   string   url   = "C:\\Test.htm";

   // wbAdministrator is the Web Browser ActiveX control
   this.wbAdministrator.Navigate(url, ref obj, ref obj, ref obj, ref obj);
}

private void AssociateHtmlDocumentEvents ()
{
   mshtml.IHTMLElement                  htmlElement                  = null;
   mshtml.HTMLDocument                  htmlDocument               = null;
   mshtml.HTMLElementEvents2_Event         htmlElementEvents2_Event      = null;

   // wbAdministrator is the Web Browser ActiveX control
   if (!this.wbAdministrator.Busy)
   {
      if (this.wbAdministrator.Document != null)
      {
         htmlDocument   = (mshtml.HTMLDocument) this.wbAdministrator.Document;

         // A self designed HTML page is loaded in the web browser containing the "spanQuoteSubmit" <SPAN> element
         htmlElement      = htmlDocument.getElementById("spanQuoteSubmit");
         if (htmlElement != null)
         {
            if (htmlElement is mshtml.HTMLSpanElement)
            {
               htmlElementEvents2_Event            =   (mshtml.HTMLElementEvents2_Event) htmlElement;
               htmlElementEvents2_Event.onclick      +=   new mshtml.HTMLElementEvents2_onclickEventHandler(htmlElementEvents2_Event_onclick);
            }
         }
      }
   }
}

private bool htmlElementEvents2_Event_onclick(mshtml.IHTMLEventObj pEvtObj)
{
   // Process pEvtObj based on pEvtObj.srcElement.id
   if (pEvtObj.srcElement.id != null)
   {
      switch (pEvtObj.srcElement.id)
      {
         case "spanQuoteSubmit":
         {
            // Process
            break;
         }
      }
   }
}
Avatar of raheelasadkhan
raheelasadkhan
Flag of Pakistan image

ASKER

I wrote the above code by hand and missed out the last two lines of the htmlElementEvents2_Event_onclick function:

private bool htmlElementEvents2_Event_onclick(mshtml.IHTMLEventObj pEvtObj)
{
   // Process pEvtObj based on pEvtObj.srcElement.id
   if (pEvtObj.srcElement.id != null)
   {
      switch (pEvtObj.srcElement.id)
      {
         case "spanQuoteSubmit":
         {
            // Process
            break;
         }
      }
   }

   // The following function call should not be here but is called so that everytime the event is fired,
   // the entire document should be reloaded to make sure events stay in tact. Even this crude approach
   // does not offer a permanent solution and events are sometimes still lost.
   LoadHtmlDocument();

   return (false);
}
Avatar of vo1d
try assigning your events on the navigate2completed event.
sorry, i ment the DocumentComplete event of the webbrowser control.
I wrote the above code to make it easier for readers to understand. I have already done what you suggested:

After calling NavigateHome(), the NavigateComplete2 events gets called. Associating events within that event is not possible since the document is not yet ready for use so I simply start a timer which waits for the browser to reach its ready state and then associates events. I have not tried the DocumentComplete event as yet and will try that right now.

private void LoadHtmlDocument ()
{
   NavigateHome();
}

private void wbAdministrator_NavigateComplete2(object sender, AxSHDocVw.DWebBrowserEvents2_NavigateComplete2Event e)
{
   // The tmrBrowserAdministrator is a simple timer. Associating events here is not possible since the document object is not yet ready.
   // Even the {while (this.wbAdministrator.Busy)} loop does not work because the document will not be ready until this event returns.
   // So an external timer is used.
   this.tmrBrowserAdministrator.Start();
}

private void tmrBrowserAdministrator_Tick(object sender, System.EventArgs e)
{
   // wbAdministrator is the Web Browser ActiveX control
   while (this.wbAdministrator.Busy)
   {
      System.Windows.Forms.Application.DoEvents();
   }
   AssociateHtmlDocumentEvents();
}
Just tried the DocumentComplete event and as I suspected, the problem persists :(. Since I was already waiting for the browser to reach it's ready state, switching between NavigateComplete2 and DocumentComplete did not make a difference.

This has become a very urgent matter for me. Any help would be appreciated.

Thanks,

Khan
I found another solution:

Instead of associating events for each element individually, I now associate the click event for the HtmlDocument object. This responds when the mouse is clicked anywhere in the web browser control. I then use the HtmlDocument.elementFromPoint(x, y) function to determine which element the mouse was over when it was clicked.

This approach takes care of the previous problem and the associated OnClick event remains associated.

private bool htmlDocumentEvents2_Event_onclick(mshtml.IHTMLEventObj pEvtObj)
{
   mshtml.IHTMLElement                  htmlElement                  = null;
   mshtml.HTMLDocument                  htmlDocument               = null;

   if (!this.wbAdministrator.Busy)
   {
      if (this.wbAdministrator.Document != null)
      {
         htmlDocument   = (mshtml.HTMLDocument) this.wbAdministrator.Document;
         htmlElement      = htmlDocument.elementFromPoint(pEvtObj.clientX, pEvtObj.clientY);
         if (htmlElement != null)
         {
            // Process element click
         }
      }
   }

   return (false);
}
The above solution works nicely but a new issue has come up. Not critical but a little annoying.

The last statement in the event above is return(false). This ensures that the click events bubbles/cascades to any other handlers. Even then, the right click events does not show the usual IE context menu. Also, no text can be selected with the mouse.

Any ideas on how to make sure the onclick event does not stop the browser control from processing it's usual processing?
what happens, if you return true at the end and only false if your element is the wanted?
Actually returning false is what allows it to cascade. True would stop it. Since I am returning false in every case, it still doesn't help.
Bad news. The Document_onclick solution works perfectly from within the development environment. If the app is launched from the windows shell, the same damn problem comes up again !@#!@#!@#!!!

One thing I noticed is that if any kind exception is thrown from within the event handlers of elements, the event associations are lost and even a try catch block does not help. So consider the following:

// Any document or element level event
private void Document_onclick (/* event dependent parameters */)
{
   try
   {
      // This statement will get called
      MessageBox.Show (e.Message);
      throw (new System.Exception("xyz"));
   }
   catch (Exception e)
   {
      // This statement will never get called!!! What the !@#!@!#!@!!!
      MessageBox.Show (e.Message);
   }

   return (false); // If needed. Not all events support this
}

And what more, the document and it's elements refuse to fire events after such a case. What was MS thinking? I'm sure there is a way around this but with the documentation MSDN and PSDK provide on this stuff, I don't expect finding one anytime soon.

Is there some kind of a MS support platform where I can pay and have this answered?
if you are in the parnterprogram of microsoft, then you could get support.
what about posting this issue in one of their newsgroups?
have you tried using net2? there is a manager wrapper for the webcontrol implemented.
Thanks for all your help vo1d.

Yes I know about version 2 and a completely managed wrapper of the Wbe Browser control but unfortunately, my current project is written in 1.1 and I don't have a choice about it.

I've never tried the MS newsgroups for the simple reason that I post problems on EE when it is mission critical. So far the success rate from EE has been 100%! until this problem. Although it will probably be too late before I find a solution but I'd still definately like to find one. Could you guide me to the newsgroups?
ASKER CERTIFIED SOLUTION
Avatar of vo1d
vo1d
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
vo1d: The second link you mentioned took me to an MS discussion forum but I did not find a relevant topic there.

Anyways, I found a rather silly solution and have been using it. Works rather nicely. The WM_PARENTNOTIFY is sent to a window when WM_LBUTTONDOWN occurs on child controls. So overriding the WndProc function and traping that message was all it took!. Then it's simply a matter of checking if the bounds fall within the browser and if so, querying the HtmlDocument's elementFromPoint(x, y) method.

Here is the code:

protected override void WndProc (ref System.Windows.Forms.Message m)
{
   // The WndProc function has been overriden to overcome the following problem:
   //   The Web Browser doesn't seem to want to react to HtmlElement events consistently.
   //   The following approach simply detects LBUTTONDOWN on child controls and checks to
   //   see if the click was targeted at one of the buttons
   switch (m.Msg)
   {
      case WM_PARENTNOTIFY:
      {
         if (m.WParam.ToInt32() == WM_LBUTTONDOWN)
         {
            int                     x;
            int                     y;
            System.Drawing.Rectangle   rectangle;

            x         = (((m.LParam.ToInt32() << 16) >> 16));
            y         = ((m.LParam.ToInt32() >> 16));
            rectangle   = new System.Drawing.Rectangle
               (
               this.pnlAdministrator.Left,
               this.pnlAdministrator.Top,
               this.pnlAdministrator.Width,
               this.pnlAdministrator.Height
               );

            if (rectangle.Contains(x, y))
            {
               mshtml.IHTMLElement                  htmlElement                  = null;
               mshtml.HTMLDocument                  htmlDocument               = null;

               if (!this.wbAdministrator.Busy)
               {
                  if (this.wbAdministrator.Document != null)
                  {
                     htmlDocument   = (mshtml.HTMLDocument) this.wbAdministrator.Document;
                     htmlElement      = htmlDocument.elementFromPoint(x - this.pnlAdministrator.Left, y - this.pnlAdministrator.Top);
                     if (htmlElement != null)
                     {
                        this._AdministratorEventSource   = htmlElement.id;
                        this.tmrProcessElementEvents.Start();
                     }
                  }
               }
            }
         }

         break;
      }
   }

   base.WndProc (ref m);
}


But I'm still mad about the HtmlDocument_Events!!! :)