4

I am using webBrowser.Document.InvokeScript("Function") to run a javascript, located in a local file opened with the Winforms WebBrowser.

The problem is, I need the javascript to finish executing before continuing. How do I wait/listen for that?

This is my C# code:

    private void Button1_ItemClick_1(object sender, DevExpress.XtraBars.ItemClickEventArgs e)
    {
        webBrowser.Document.InvokeScript("Script_A");
        Method_A();
        DialogResult = System.Windows.Forms.DialogResult.OK;
    }

Javascript code:

<script>function Script_A() { Script_B(); }</script>

How do I make sure that Method_A is not executed before Script_B has finished?

  • 1
    You could make the JavaScript call back to the WinForms application notifying it that the script has finished and that sets a flag that is either checked by your ButtonClick code or finishes off the button code. – Belogix May 22 '13 at 12:11

2 Answers2

8

using async/await you can wait until the script is executed without blocking the UI.

public async void AMethod()
{
    string script =
     @"<script>
        function Script_A() { 
            Script_B(); 
            window.external.Completed(); //call C#: CallbackObject's Completed method
        }
        function Script_B(){
            alert('in script');
        }
    </script>";

    TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

    webBrowser1.ObjectForScripting = new CallbackObject(tcs);
    //Ensure DocumentText is loaded before invoking "InvokeScript",
    //by extension method "SetDocumentTextAsync" (below)
    await webBrowser1.SetDocumentTextAsync(script);
    webBrowser1.Document.InvokeScript("Script_A");

    await tcs.Task;

    MessageBox.Show("Script executed");
}


[ComVisible(true)]
public class CallbackObject
{
    TaskCompletionSource<bool> _tcs = null;

    public CallbackObject(TaskCompletionSource<bool> tcs)
    {
        _tcs = tcs;
    }
    public void Completed()
    {
        _tcs.TrySetResult(true);
    }
}

public static class BrowserExtensions
{
    public static Task SetDocumentTextAsync(this WebBrowser wb, string html)
    {
        TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
        WebBrowserDocumentCompletedEventHandler completedEvent = null;
        completedEvent = (sender, e) =>
        {
            wb.DocumentCompleted -= completedEvent;
            tcs.SetResult(null);
        };
        wb.DocumentCompleted += completedEvent;

        wb.ScriptErrorsSuppressed = true;
        wb.DocumentText = html;

        return tcs.Task;
    }
}
I4V
  • 34,891
  • 6
  • 67
  • 79
  • Okay, I tried, and I got stucked with the await webBrowser.SetDocumentTextAsync(Script); What should be there instead of "script"? – Valentin Kold Gundersen May 23 '13 at 09:16
  • @ValentinKoldGundersen I don't get you. it is the script you wrote/will write. Just append `window.external.Completed();` to it to inform c# code it has finished/ – I4V May 23 '13 at 09:38
  • @I4V for years I've been fighting trying to effectively find a solution to this and your's has been the most effective and the only one that finally gives me closure on this issue! (I didn't use your exact method but your implementation of `WebBrowserDocumentCompletedEventHandler` set me on the right direction) – fictus Oct 02 '15 at 12:43
0

You would Need to implement a callback-method (don't Forget ComVisible-Attribute) which you would be calling from your script using

window.mymethod();

This way, you would actually need to 'split' your method.

Here is a nice post from SO.


And here is a tutorial:

Call a C# Method From JavaScript Hosted in a WebBrowser

By AspDotNetDev, 6 May 2011

This sample demonstrates how to call C# from JavaScript. It also shows that parameters can be passed to C# methods.

First, create a Windows Forms application. Then, add a WebBrowser control to your form. Then modify the code for the form so it looks like this:

 namespace WindowsFormsApplication6
{
    // This first namespace is required for the ComVisible attribute used on the ScriptManager class.
    using System.Runtime.InteropServices;
    using System.Windows.Forms;

    // This is your form.
    public partial class Form1 : Form
    {
        // This nested class must be ComVisible for the JavaScript to be able to call it.
        [ComVisible(true)]
        public class ScriptManager
        {
            // Variable to store the form of type Form1.
            private Form1 mForm;

            // Constructor.
            public ScriptManager(Form1 form)
            {
                // Save the form so it can be referenced later.
                mForm = form;
            }

            // This method can be called from JavaScript.
            public void MethodToCallFromScript()
            {
                // Call a method on the form.
                mForm.DoSomething();
            }

            // This method can also be called from JavaScript.
            public void AnotherMethod(string message)
            {
                MessageBox.Show(message);
            }
        }

        // This method will be called by the other method (MethodToCallFromScript) that gets called by JavaScript.
        public void DoSomething()
        {
            // Indicate success.
            MessageBox.Show("It worked!");
        }

        // Constructor.
        public Form1()
        {
            // Boilerplate code.
            InitializeComponent();

            // Set the WebBrowser to use an instance of the ScriptManager to handle method calls to C#.
            webBrowser1.ObjectForScripting = new ScriptManager(this);

            // Create the webpage.
            webBrowser1.DocumentText = @"<html>
                <head>
                    <title>Test</title>
                </head>
                <body>
                <input type=""button"" value=""Go!"" onclick=""window.external.MethodToCallFromScript();"" />
                    <br />
                    <input type=""button"" value=""Go Again!"" onclick=""window.external.AnotherMethod('Hello');"" />
                </body>
                </html>";
        }
    }
}

Note that your application may be part of a namespace other than WindowsFormsApplication6, but the rest of the code should work if you follow the above instructions explicitly. I created this tip/trick because somebody asked me a question and they didn't understand this sample that I sent them to. This tip/trick makes the sample more understandable by fixing the two bugs I spotted, adding the using statements that weren't mentioned, and by heavily commenting the code. Hopefully the rest of you will find this of use as well. License

Community
  • 1
  • 1
bash.d
  • 13,029
  • 3
  • 29
  • 42