RSS 2.0 Feed
RSS 2.0


Atom 1.0 Feed
Atom 1.0

  Interacting with the Web Browser Control 


In my last post, I outlined some ways to make the Web Browser control more useful in your C# applications, to include things such as printing and setting the text or html of the browser dynamically. That is all good, but in a typical application it does little for you if you can't interactively respond to events that occur in the document loaded in the browser control.

Last time, we looked at getting a reference to the browser's document object via the IHTMLDocument2 interface. It all starts there. Once we have a reference to the document, we can start wiring up events. Essentially what we are going to do here is setup our own application to be the receiver of the COM dispatch interop events raised by the browser's document. When we get the reference to the document via the IHTMLDocument2 interface we can wire up events, such as the document's onclick event. To do so we use the following code:

private object _empty = System.Reflection.Missing.Value;
private IHTMLDocument2 _doc;

public MainForm()
{
    //navigate to about:blank to create the document
    axWebBrowser1.Navigate("about:blank", ref _empty, ref _empty, ref _empty, ref _empty);
    _doc = axWebBrowser1.Document as IHTMLDocument2;

    //wait for document to load
    while (_doc.body == null) Application.DoEvents();

    //write the HTML to the document's body
    _doc.body.innerHTML = "<form id=\"MainForm\">This is some text.<br><br>" +
                          "<input type=button id=\"btnTest1\" value=\"Button #1\">&nbsp;" + 
                          "<input type=button id=\"btnTest2\" value=\"Button #2\"></form>";

    //wire up our form as the IDispatch object to receive click events
    _doc.onclick = this;
}

So what have we done here? It all starts out with the things we looked at last time. We navigate to about:blank to create a document in the browser, then we write some HTML to it which includes a couple of input (type=button) tags for some buttons. Then we set 'this', or our form instance, as the IDispatch interface object to be invoked when an 'onclick' event occurs. We still need to define the default IDispatch method that will be invoked. Let's move on to that now.

When the document's event is raised it will attempt to invoke the default IDispatch method of the event receiver. We can specify which event in our code is the default method with the DispId attribute using an index of zero. Like this:

using System.Runtime.InteropServices;
//...

[DispId(0)]
public void DispatchDefault()
{
    //...
}

Now our DispatchDefault method will be invoked whenever the onclick event is raised by the document. It doesn't matter what we call it, as long as it has the correct attribute and takes no arguments. But, we've wired the document's onclick event to our form. How will we know which button was clicked and raised the event? We can use the document's parent window, which is raising our events to find out the source of the event as well as which event has occurred. Take a look at the following to complete the picture:

[DispId(0)]
public void DispatchDefault()
{
    HTMLWindow2 win = (HTMLWindow2)_doc.parentWindow;

    string eventtype = win.@event.type;
    string elementid = win.@event.srcElement.id;

    switch (elementid)
    {
        case "btnTest1":
            if (eventtype.Equals("click")) MessageBox.Show("Button #1 was clicked.");
            break;
        case "btnTest2":
            if (eventtype.Equals("click")) MessageBox.Show("Button #2 was clicked.");
            break;
    }
}

If we wanted to wire up other events, such as the mouseover event, we would just wire it up and look for that event in the DispatchDefault method. You can take a look at the code above in a sample project I've included below.




                   



Leave a comment below.

Comments

  1. CBW 2/15/2005 12:38 PM
    Gravatar
    Hi Ryan,

    I noticed that in your example you are waiting for the document body to no longer be null to determine that the page is loaded. Why is this used instead of waiting for documentcomplete from the web browser control?

    Thanks.
  2. Nik 6/16/2005 7:15 AM
    Gravatar
    Hi Ryan,

    Is it possible to 'modify' the web browser control in order to use it in a web form instead of windows form?

    Nik
  3. mrunali 7/1/2005 4:22 AM
    Gravatar
    its good!!!
  4. azmat 9/10/2005 11:13 PM
    Gravatar
    amazing code i was looking for

    thanx
  5. Zabardasth 9/22/2005 11:53 AM
    Gravatar
    Its amazing code, i was surching for such a nice and short way of catching events. But isnt there any way to directly call a c# function from html script code. Its possible in .net 2 There you write:
    <button onclick="window.external.CShFunctiont('hello')>

    Ryan thanks any way
  6. Jayant Patil 11/29/2005 9:44 AM
    Gravatar
    Its really nice.
    But IF I have one variable (var1) defined in main form ...
    and I want to add some validations like
    say, when I click on "btntest1", i want to check value of the variable var1
    and want to make decisions (in web page) according to value of var1., whether to submit the page or not???

    How can I do that?


    Thanks.
  7. Steve Allen 12/21/2005 5:15 AM
    Gravatar
    Thank you, thank you, thank you!!! Just spent the last 3 days trying to figure out how to do this and here it is. Awesome. Again, thanks!
  8. Michel Docter 12/30/2005 3:03 PM
    Gravatar
    In .Net 2.0 I have troubles navigating to a page, when a script on the pages runs. How do i know when it finishes, cause the BrowserReadyState = complete, the body is also available.
  9. Matt Wilcox 1/24/2006 11:42 PM
    Gravatar
    Hi Ryan,

    Thanks for that it works great. But I just have the one question, you've said before that you'd tell us how to disable the right click button? I can't find this information anywhere...

    I assume its easy oncce you get the click event, but I can't work out what to do next.

    Can you help

    Thanks?
  10. savy 11/14/2006 9:01 PM
    Gravatar
    Hi Ryan,

    I have a code in my start.html file something like this.

    <HTML>
    <BODY>
    <form name="uploadform" ID="Form1" action="commit.htm">
    <input type="button" value="SubmitMe" onclick="SubmitMe();" id=button1 name=button1>
    <SCRIPT language="javascript">
    function SubmitMe()
    {
    ....
    alert('hi');
    }
    </SCRIPT>
    </form>
    </BODY>
    </HTML>

    I have to click the button and submit the form within a c# code using mshtml.

    I used following code to click the button.

    mshtml.HTMLInputButtonElement he = (mshtml.HTMLInputButtonElement)wd.getElementsByName("button1").item("button1", 0);

    he contains all the details about the button, but when i use
    he.click(); nothing is happening.

    What i am missing here. Please help me.
    Thank you,
    Savy
  11. savy 11/14/2006 9:02 PM
    Gravatar
    Hi Ryan,

    I have a code in my start.html file something like this.

    <HTML>
    <BODY>
    <form name="uploadform" ID="Form1" action="commit.htm">
    <input type="button" value="SubmitMe" onclick="SubmitMe();" id=button1 name=button1>
    <SCRIPT language="javascript">
    function SubmitMe()
    {
    ....
    alert('hi');
    }
    </SCRIPT>
    </form>
    </BODY>
    </HTML>

    I have to click the button and submit the form within a c# code using mshtml.

    I used following code to click the button.

    mshtml.HTMLInputButtonElement he = (mshtml.HTMLInputButtonElement)wd.getElementsByName("button1").item("button1", 0);

    he contains all the details about the button, but when i use
    he.click(); nothing is happening.

    What i am missing here. Please help me.
    Thank you,
    Savy
  12. Mark Johnson 12/27/2006 7:43 AM
    Gravatar
    I tried rebuilding this project by hand and I kept getting an exception on the _doc.onclick = this. After much gnashing of teeth I discovered that I had to remove the following line from the AssemblyInfo.cs to get rid of the exception.

    // Setting ComVisible to false makes the types in this assembly not visible
    // to COM components. If you need to access a type in this assembly from
    // COM, set the ComVisible attribute to true on that type.
    //[assembly: ComVisible(false)]


  13. Galib Anwar 6/29/2007 6:18 AM
    Gravatar
    Oh Ryan,

    Cant thank you enough for the sample.
    It was GREAT HELP.

    The little I had to do to run it was,

    - remove the reference to the <b>MSHTML</b> with a yellow exclamation

    - add reference to the <b>COM</b> library
    <b>Microsoft HTML Object Library</b>

    - replace <b>mshtml</b> with <b>MSHTML</b>

    - change a message text to make myself believe that the html document is relly interfacing with the C# portion of the app.
  14. Roman 10/19/2007 6:14 AM
    Gravatar
    Under Visual Studio 2005 remove

    [assembly: ComVisible(false)]

    otherwise you will get NotImplementedException()!

  15. Libby 6/4/2008 7:00 PM
    Gravatar
    How do you disable the right click button when in your C# applications, it is hard to find information about this. Thanks in advance for your help. http://www.seegeorgiarealestate.com/Bridgemill_In_Canton_Georgia.htm

  16. John 11/28/2008 12:03 AM
    Gravatar
    Thanks for this.

    On Visual Studio 2008, with WPF webbrowser control.

    public partial class BsnGUI : UserControl
    {
    public IHTMLDocument2 doc;
    public IHTMLDocument3 doc2;

    public BsnGUI()
    {
    InitializeComponent();
    Uri myUri = new Uri("about:blank");
    webBrowser1.Navigate(myUri);
    doc = (IHTMLDocument2)webBrowser1.Document;
    doc2 = (IHTMLDocument3)webBrowser1.Document;
    setEvents();
    }

    private void setEvents()
    {
    DHTMLEventHandler docEvents = new DHTMLEventHandler(doc);
    docEvents.Handler += new DHTMLEvent(this.BsnEvents);
    doc.onclick = docEvents;
    doc.onkeydown = docEvents;
    }

    public void BsnEvents(IHTMLEventObj sender)
    {
    EventArgs e = new EventArgs();
    switch (sender.type)
    {
    case "click":
    MessageBox.Show("Click")
    break;
    case "keydown":
    MessageBox.Show("Click")
    break;
    }
    sender.returnValue = true;
    }

    public void write(string html)
    {
    doc.write(html);
    setEvents();
    }

    public void html(string id, string html)
    {
    doc2.getElementById(id).innerHTML = html;
    setEvents();
    }
    }

    public delegate void DHTMLEvent(IHTMLEventObj e);

    [ComVisible(true)]
    public class DHTMLEventHandler
    {
    public DHTMLEvent Handler;
    private IHTMLDocument2 Document;

    public DHTMLEventHandler(IHTMLDocument2 doc)
    {
    this.Document = doc;
    }

    [DispId(0)]
    public void Call()
    {
    Handler(Document.parentWindow.@event);
    }
    }
  17. Frank Drebbin 12/17/2008 4:40 PM
    Gravatar
    I was googlin' desperate and finally a light...
    Clear,Simple exhaustive...
    Great job!
    I'm working on a DLL Library based on abstract classes:
    I have only a question:

    given this code:
    //Method To Find All IE Instances

    ShellWindows Sw = new ShellWindows();
    private static List<HTMLDocument> X(ShellWindows Windows)
    {
    List<HTMLDocument> Documents = new List<HTMLDocument>();
    String IEName = "Windows Internet Explorer";
    foreach (InternetExplorer Browser in Windows)
    {
    if (Browser.Name == IEName )
    {
    Documents.Add((HTMLDocument)Browser.Document);
    }
    }
    return Documents ;
    }

    public static List<String> Names = new List<string>();

    public static string GetActiveWebPage()
    {
    foreach (HTMLDocument WebPage in X(Sw))
    {
    Please Tell me what I need to catch
    the "WebPage" Events..
    (WebPage.parentWindow.@event is always null!)
    I need to catch parentWindow Events! not
    Document Events!)
    Name.Add(\\Here I don't Know);
    }
    return String.Join("\n",Names.ToArray());
    }
    What can I write to obtain current events?I need only
    to know the HTMLDocument.parentWindow.@event.type...
    sent to my tabs on IE7,8 etc... or from the HWND of other instances...
    Please Help me,I'm feeling a dummy after 10 years...
    Tell me I'm a stupid and that was a simple solution...
    Thanks so much!



  18. 1/18/2009 3:41 AM
    Gravatar
    Webbrowser | hilpers
  19. Atlanta houses 3/16/2009 3:22 AM
    Gravatar
    I have a code in my start.html file something like this.

    <HTML>
    <BODY>
    <form name="uploadform" ID="Form1" action="commit.htm">
    <input type="button" value="SubmitMe" onclick="SubmitMe();" id=button1 name=button1>
    <SCRIPT language="javascript">
    function SubmitMe()
    {
    ....
    alert('hi');
    }
    </SCRIPT>
    </form>
    </BODY>
    </HTML>

    I have to click the button and submit the form within a c# code using mshtml.

    I used following code to click the button.

    mshtml.HTMLInputButtonElement he = (mshtml.HTMLInputButtonElement)wd.getElementsByName("button1").item("button1", 0);

    he contains all the details about the button, but when i use
    he.click(); nothing is happening.
  20. Evan 7/8/2009 8:27 AM
    Gravatar
    Thank you for this post; the following code saved me some headache:

    //wait for document to load
    while (_doc.body == null) Application.DoEvents();

    Thanks for doing my thinking for me.
  21. Naty 10/26/2009 7:20 AM
    Gravatar
    Hi ,

    I have the same problem of : Frank Drebbin



    I was googlin' desperate and finally a light...
    Clear,Simple exhaustive...
    Great job!
    I'm working on a DLL Library based on abstract classes:
    I have only a question:

    given this code:
    //Method To Find All IE Instances

    ShellWindows Sw = new ShellWindows();
    private static List<HTMLDocument> X(ShellWindows Windows)
    {
    List<HTMLDocument> Documents = new List<HTMLDocument>();
    String IEName = "Windows Internet Explorer";
    foreach (InternetExplorer Browser in Windows)
    {
    if (Browser.Name == IEName )
    {
    Documents.Add((HTMLDocument)Browser.Document);
    }
    }
    return Documents ;
    }

    public static List<String> Names = new List<string>();

    public static string GetActiveWebPage()
    {
    foreach (HTMLDocument WebPage in X(Sw))
    {
    Please Tell me what I need to catch
    the "WebPage" Events..
    (WebPage.parentWindow.@event is always null!)
    I need to catch parentWindow Events! not
    Document Events!)
    Name.Add(\\Here I don't Know);
    }
    return String.Join("\n",Names.ToArray());
    }
    What can I write to obtain current events?I need only
    to know the HTMLDocument.parentWindow.@event.type...
    sent to my tabs on IE7,8 etc... or from the HWND of other instances...
    Please Help me,I'm feeling a dummy after 10 years...
    Tell me I'm a stupid and that was a simple solution...
    Thanks so much!


    Any solution?

    I look all over the web, i just found some information about problem with multithreding & COM , ManuelResetEvent & AutoResetEvent.

    Thanks
Comments have been closed on this topic.



 

News


Also see my CRM Developer blog

Connect:              


Sponsor

Sections