2013-10-17 99 views
1

我有一個問題,在我目前正在處理的項目中,我有一個WPF應用程序。 在這個WPF應用程序中,我們有一個標籤項目,其中有一個WPF瀏覽器...Wpf:webbrowser在javascript中同步調用異步Silverlight方法

此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的改變,我也不會說......

我怎樣才能把我的腳本同步?

+0

JavaScript沒有像'async'或'Task'這樣的內置任何東西。我建議嘗試使用Promise或Deferred(例如Q),並從'ScriptableMember'返回。 –

+0

這對我來說更新,我該如何做到這一點? –

+1

@JérémyJaniszewski,如果我的回答沒有幫助,請考慮留言。 – Noseratio

回答

2

您需要將C#的回調委託傳遞給JavaScript。這個委託將在JavaScript端的異步操作完成時被調用。完成的信號自然是特定於腳本邏輯的。它可能是一個AJAX,DOM或Silverlight事件。

由於@StephenCleary在評論中指出的那樣,你可以進一步使用的腳本側Deferred對象方便地包裹在JavaScript中完成事件,以類似的方式,你使用一個Task(或TaskCompletionSource)包裹在事件C#。您仍然需要傳遞一個C#回調函數,您可以在延遲解析時調用它。

在下面的代碼中,我使用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 pattern。它執行兩個異步操作(並行),然後在兩個操作完成時調用C#(所以它們的promise已經解決)。

<!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>