1

我有一个问题,在我目前正在进行的项目中,我有一个 WPF 应用程序。在这个 WPF 应用程序中,我们有一个带有 WPF 网络浏览器的选项卡项...

这个 wpf webbrowser 托管一个 Silverlight 网站项目。

为了让事情更容易,假设我有一个这样的异步方法:

// Script to call in WPF to see if technical inspection has changed
[ScriptableMember]
public async Task<int> VerifyModifiedTechnicalInspection()
{
    var rvm = ((MainViewModel)DataContext).TechnicalInspectionViewModel;
    if (rvm == null)
        return -1;

    // Verify if we have some technical inspection to save. If yes, show a dialog.
    // Gets the dialog's result
    var result = await rvm.SaveTechnicalInspection();

    switch(result)
    {
        case DialogButton.Cancel:
            return 2;

        case DialogButton.No:
            return 1;

        case DialogButton.Yes:
            return 0;

        default:
            return -1;
     }
}

现在我有一个这样的javascript函数:

function VerifyModifiedTechnicalInspection() {
    if (slControl != null) {
       return slControl.Content.SLMainPage.VerifyModifiedTechnicalInspection();
    }

    return -1;
}

问题是 :

当我更改选项卡项时,我需要像这样运行脚本:

var result = (int)webBrowser.InvokeScript("VerifyModifiedRelances");

switch(result)
{
}

所以在这里,问题是脚本是异步调用的,所以结果返回时没有实际值,所以 tabitem 改变了,我不会……

如何同步调用我的脚本?

4

1 回答 1

2

您需要将回调委托从 C# 传递给 JavaScript。当 JavaScript 端的异步操作完成时,将回调此委托。什么信号完成自然是特定于您的脚本逻辑。它可以是 AJAX、DOM 或 Silverlight 事件。

正如@StephenCleary 在评论中指出的那样,您可以在脚本端进一步使用Deferred对象来方便地将完成事件包装在 JavaScript 中,就像您使用Task(或TaskCompletionSource) 在 C# 中包装事件一样。您仍然需要传递一个 C# 回调,当 deferred 得到解决时您将调用该回调。

在下面的代码中,我使用 JavaScript 计时器作为异步操作的示例。为简单起见,代码不使用延迟。

C#:

using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows;

namespace WpfWebBrowser
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            SetBrowserFeatureControl();
            InitializeComponent();

            this.Loaded += MainWindow_Loaded;
        }

        async void MainWindow_Loaded(object sender, RoutedEventArgs eventArg)
        {
            // navigate the browser
            System.Windows.Navigation.LoadCompletedEventHandler loadedHandler = null;
            var loadedTcs = new TaskCompletionSource<bool>();

            loadedHandler = (s, e) =>
            {
                this.webBrowser.LoadCompleted -= loadedHandler;
                loadedTcs.SetResult(true);
            };

            this.webBrowser.LoadCompleted += loadedHandler;
            this.webBrowser.Navigate("http://localhost:81/callback.html");
            await loadedTcs.Task;

            // call the script method "scriptFuncAsync"
            var asyncScriptTcs = new TaskCompletionSource<object>();
            var oncompleted = new ScriptEventHandler((ref object returnResult, object[] args) => 
            {
                // we are here when the script has called us back
                asyncScriptTcs.SetResult(args[0]);
            });

            this.webBrowser.InvokeScript("scriptFuncAsync", oncompleted);
            await asyncScriptTcs.Task;

            // show the result of the asyc call
            dynamic result = asyncScriptTcs.Task.Result;
            MessageBox.Show(result.outcome.ToString());
        }


        /// <summary>
        /// ScriptEventHandler - an adaptor to call C# back from JavaScript or DOM event handlers
        /// </summary>
        [ComVisible(true), ClassInterface(ClassInterfaceType.AutoDispatch)]
        public class ScriptEventHandler
        {
            [ComVisible(false)]
            public delegate void Callback(ref object returnResult, params object[] args);

            [ComVisible(false)]
            private Callback _callback;

            [DispId(0)]
            public object Method(params object[] args)
            {
                var returnResult = Type.Missing; // Type.Missing is "undefined" in JavaScript
                _callback(ref returnResult, args);
                return returnResult;
            }

            public ScriptEventHandler(Callback callback)
            {
                _callback = callback;
            }
        }

        /// <summary>
        /// WebBrowser version control to enable HTML5
        /// http://msdn.microsoft.com/en-us/library/ee330730(v=vs.85).aspx#browser_emulation
        /// </summary>
        void SetBrowserFeatureControl()
        {
            // http://msdn.microsoft.com/en-us/library/ee330720(v=vs.85).aspx

            // FeatureControl settings are per-process
            var fileName = System.IO.Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName);

            // make the control is not running inside Visual Studio Designer
            if (String.Compare(fileName, "devenv.exe", true) == 0 || String.Compare(fileName, "XDesProc.exe", true) == 0)
                return;

            // Webpages containing standards-based !DOCTYPE directives are displayed in IE9/IE10 Standards mode.
            SetBrowserFeatureControlKey("FEATURE_BROWSER_EMULATION", fileName, 9000);
        }

        void SetBrowserFeatureControlKey(string feature, string appName, uint value)
        {
            using (var key = Registry.CurrentUser.CreateSubKey(
                String.Concat(@"Software\Microsoft\Internet Explorer\Main\FeatureControl\", feature),
                RegistryKeyPermissionCheck.ReadWriteSubTree))
            {
                key.SetValue(appName, (UInt32)value, RegistryValueKind.DWord);
            }
        }

    }
}

XAML:

<Window x:Class="WpfWebBrowser.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <WebBrowser Name="webBrowser"/>
</Window>

http://localhost:81/callback.html

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <script>
        window.scriptFuncAsync = function (oncompleted) {
            // do something async, use timer
            setTimeout(function () {
                info.innerHTML = "Async operation completed.";
                oncompleted({ outcome: 42 });
            }, 2000);
        }
    </script>
</head>
<body>
    <span>Async Script Test Page</span><br>
    <span id="info"></span>
</body>
</html>

[更新]

下面是一个更复杂的脚本,说明了 jQuery 的Deferred/Promise 模式的使用。它执行两个异步操作(并行),然后在两个操作完成时回调 C#(因此它们的承诺已经解决)。

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>  
    <script>
        window.scriptFuncAsync = function (oncompleted) {
            // do something async, use timer
            var promise1 = (function () {
                var deferred = $.Deferred();
                setTimeout(function () {
                    deferred.resolve("Async timeout completed.");
                }, 3000);
                return deferred.promise();
            })();

            promise1.done(function () {
                output("promise1 resolved.");
            });

            var promise2 = $.ajax({ url: "http://www.html5rocks.com/en/resources" });

            promise2.done(function () {
                output("promise2 resolved.");
            });

            $.when(promise1, promise2).then(function(result1, result2) {
                output("result1: " + result1);
                output("result2: " + result2.toString().substr(0, 100) + "...");
                oncompleted({ outcome: 42 });
            });

            function output(str) {
                if (!info.firstChild)
                    info.appendChild(document.createTextNode(""));
                info.firstChild.data += str + "\n";
            }
        };
    </script>
</head>
<body>
    <span>Async Script Test Page</span><br>
    <pre id="info"></pre>
</body>
</html>
于 2013-10-18T01:30:58.617 回答