20

I am using an MVVM pattern on WPF4, though I am new to both. I am looking for a good solution to using a WebBrowser control that can receive Javascript commands and communicate with the ViewModel. It needs the following:

  1. Ability to collect values from Javascript forms, and return them to the ViewModel
  2. Use Javascript to determine ReadyState before
  3. Running Javascript commands (setting form values, use form values for logical steps, submit form) some which happen across multiple page loads

The site being worked on is not under my control to edit or update. It makes heavy use of ActiveX and will not accept non-IE browsers (Awesomium will not work), so the standard WPF WebBrowser control is likely the only option.

This question provides a solution to binding the source of a browser control with an attached property. I think this could be adapted to use the navigate method to send javascript, though I am not sure how values could be returned to the Viewmodel. This is the major hurdle I need to get over.

Heavy Edit - Question receiving very low views and no answers, completely reworded

Community
  • 1
  • 1
Kyeotic
  • 19,697
  • 10
  • 71
  • 128
  • re your flag: that's beyond mod abilities. You can inquire on [meta]. –  Sep 02 '11 at 15:27
  • The way the bounty was awarded does seem a bit strange, you might want to raise this as a possible bug on MSO. I'm not sure why it was awarded after just six days. – Tim Post Sep 04 '11 at 08:09
  • [Known (and fixed) bug](http://meta.stackexchange.com/questions/104919/bounty-auto-awarded-after-6-days). A dev should be able to undo the bounty or somesuch. – Matthew Read Sep 18 '11 at 19:18

2 Answers2

11

Well if you were working with the site developers to create a solution for your application, then you would use ObjectForScripting to communicate between JavaScript and the app. There a good article here, and another question which might be helpful here.

However as I understand your question, the site is an arbitrary third party site with no affiliation to your application, and you want to auto-fill some form values and submit the form within your code.

To do this you can handle the LoadCompleted event of the WebBrowser. This is called when the loaded document readyState is changed to completed. You can therefore use this event as a hook to then set/read the document form values. Note you will need to add a reference to Microsoft mshtml in the project.

The following is an MVVM style (PRISM) command, which allows an event to bind directly to the ViewModel using behaviors. This is equivalent to registering an event handler in code-behind.

public ICommand LoadCompleted
{
    get
    {
        return new EventToCommandWithSender<NavigationEventArgs>(
            (s,e) => { 

               WebBrowser browser = (WebBrowser) sender;
               // false if nested frame
               if (e.IsNavigationInitiator)
               {
                   mshtml.IHTMLDocument2 doc = (mshtml.IHTMLDocument2)browser.Document;
                   // always completed
                   var readyState = doc.readyState;
                   // populate form
                   var name = doc.body.document.getElementById("username");
                   name.value = "@TheCodeKing";
                   // submit form
                   var submit = doc.body.document.getElementById("submit");
                   submit.Click();
                }
        });
    }
}

Unfortunately the NavigationEventArgs don't provide a way to access the HTML document or request data. It does contain a WebRequest property but this has not been implemented, and will always be null. In my example I've assumed a custom EventToCommandWithSender class which providers the sender as well as the event ARGs when the event fires, but it's down to your own implementation to get access to the sender.

Community
  • 1
  • 1
TheCodeKing
  • 19,064
  • 3
  • 47
  • 70
  • I can see how this can be used to get values, but it is also possible to use this method to set values, and submit the form via javascript? – Kyeotic Aug 29 '11 at 16:00
  • Actually I do have a question about this, would I have to use attached behaviors to bind a command to the navigating event, or is there a direct way to do that? – Kyeotic Aug 29 '11 at 16:16
  • Yes I think you can use the document loaded event to then call into JavaScript and post the form. I haven't tried it through. You would use attached behaviours for events. I think Prism offers an alternative, but it's been a while since I've used it. – TheCodeKing Sep 01 '11 at 06:06
  • Doesn't the document loaded event occur when html rendering it complete, before javascript has finished executing? I will research prism to see if it can do this. Your answer is helpful, but I don't think it is complete. – Kyeotic Sep 01 '11 at 16:39
  • The WebBrowser control DocumentLoaded event occurs when the control has finished downloading content and rendered the page. It gives you a hook for parts 2 & 3 of your question. I'll clarify when back at a computer (when you say ReadyState what do you mean?). Prism is a framework for building WPF applications to separate concerns (nothing to do with client-side code). I wasn't sure if you were using it. – TheCodeKing Sep 01 '11 at 17:28
  • I was confused. I thought DocumentLoaded occured before Readystate.completed, but according to this article that is not the case.http://support.microsoft.com/kb/180366. I was referring to the Doucment.Readystate property, which in javascript you use to determine when the page has finished loading. – Kyeotic Sep 01 '11 at 17:43
  • Um... I don't know what just happened. The bounty shouldn't have expired until tomorrow, but its gone now and it says you got awarded a bounty of 25 by "Community". You should have received the full 50 rep. – Kyeotic Sep 01 '11 at 18:08
  • No probs, maybe you could start a bounty and give me the other 25 points :) – TheCodeKing Sep 01 '11 at 21:45
  • 50 is the minimum. I really don't know why it closed like that, you deserve the other 25. I would vote it up more if I could. I will flag it to see if I can get a mod to fix it. – Kyeotic Sep 01 '11 at 21:55
  • Weird. According to the timestamps, the bounty was auto-awarded after 6 days (minus about 15 minutes). – Michael Myers Sep 04 '11 at 04:57
  • Just for help: For this to work add the Microsoft.mshtml .NET reference – Tony Apr 15 '13 at 17:12
7

I don't know why this never occurred to me before, but the solution seems so simple.

Instead of having a <WebBrowser> control on the view, use a <ContentControl> and bind its content to a WebBrowser property in your ViewModel. Create the WebBrowser in your ViewModel's constructor, and then you can register the browser's navigation event (or documentloaded) to an event in your ViewModel.

Full browser control from the ViewModel! You can even capture user events, since anything they do to navigate the page will be captured in your ViewModel's navigating event.

Kyeotic
  • 19,697
  • 10
  • 71
  • 128
  • Incredible solution! – AsValeO Feb 07 '17 at 12:50
  • Nice, this helped me with a somewhat related problem of having to write DependencyProperties for bindable Source and ObjectForScripting properties, where the scripting object seemed to get bound too late anyway. Now I just have a simple WebBrowser constructor instead, and can dump that whole helper class. – Jonas Jan 31 '18 at 13:57