NanUI文檔目錄 "NanUI簡介" "開始使用NanUI" "打包並使用內嵌式的HTML/CSS/JS資源" "使用網頁來設計整個視窗" "如何實現C 與Javascript的相互通信" 如何處理NanUI中的下載過程 DonwloadHandler的使用 (待更新。。。) 如何處理NanUI中 ...
NanUI文檔目錄
- NanUI簡介
- 開始使用NanUI
- 打包並使用內嵌式的HTML/CSS/JS資源
- 使用網頁來設計整個視窗
- 如何實現C#與Javascript的相互通信
- 如何處理NanUI中的下載過程 - DonwloadHandler的使用(待更新。。。)
- 如何處理NanUI中的彈窗過程 - LifeSpanHandler的使用(待更新。。。)
- 如何控制Javascript對話框 - JsDialogHandler的使用(待更新。。。)
- 自定義資源處理程式 (待更新。。。)
如何實現C#與Javascript的相互通信
通過之前的文章,相信您已經對NanUI有了初步的瞭解。但到目前為止,我們使用NanUI僅僅只是作為呈現HTML界面的容器,並未涉及CEF與C#間數據的交互。那麼本文將簡單介紹如何在NanUI中使用C#調用Javascript的函數以及如何在Javascript註入C#的對象、屬性和方法。
C#調用Javascript函數
不需要獲取返回值的情況
假設頁面中有如下Javascript的函數sayHello,它的作用是在DOM中創建一個包含有“Hello NanUI!”字樣的p元素。
function sayHello() {
var p = document.createElement("p");
p.innerText = "Hello NanUI!";
var container = document.getElementById("hello-container");
container.appendChild(p);
}
示例中,該函數並沒有在Javascript環境里調用,而是在頁面載入完成後使用NanUI的ExecuteJavascript方法來調用它。ExecuteJavascript方法執行的返回結果為一個bool類型,它指示了這次有沒有成功執行。
在窗體的構造函數中,通過註冊Formium的LoadHandler中的OnLoadEnd事件來監測頁面載入完成的情況,併在頁面載入成功後調用JS環境中的函數sayHello。
namespace CommunicateBetweenJsAndCSharp
{
using NetDimension.NanUI;
public partial class Form1 : Formium
{
public Form1()
: base("http://res.app.local/www/index.html",false)
{
InitializeComponent();
LoadHandler.OnLoadEnd += LoadHandler_OnLoadEnd;
}
private void LoadHandler_OnLoadEnd(object sender, Chromium.Event.CfxOnLoadEndEventArgs e)
{
// Check if it is the main frame when page has loaded.
if(e.Frame.IsMain)
{
ExecuteJavascript("sayHello()");
}
}
}
}
運行後,可以看到界面中顯示了“Hello NanUI!”字樣,說明使用ExecuteJavascript能夠調用JS函數。
需要獲取返回值的情況
上面的例子中通過ExecuteJavascript方法來成功調用了JS環境中的函數。但不難發現,這種調用方式C#是沒有接收到任何返回值的。但實際的項目里,我們是需要從JS環境獲取到返回值的,這時候使用ExecuteJavascript將不能滿足需求,使用另外一個方法EvaluateJavascript可以幫助我們從JS環境中獲得JS函數的返回值。
假如有另外一個Javascript函數sayHelloToSomeone,它能接收一個字元傳參數,在函數體中拼接並返回拼接後的字元串。
function sayHelloToSomeone(who) {
return "Hello " + who + "!";
}
同樣的,在上面例子LoadHandler的OnLoadEnd事件中我們來執行sayHelloToSomeone,並通過C#傳遞參數並獲取拼接後的返回值。EvaluateJavascript方法通過一個回調Action來獲取JS環境中的返回值。這個Action有兩個參數,第一個是返回值的集合,第二個是JS環境的異常對象,如果函數正確執行,那麼第二個參數為null。
namespace CommunicateBetweenJsAndCSharp
{
using NetDimension.NanUI;
public partial class Form1 : Formium
{
public Form1()
: base("http://res.app.local/www/index.html",false)
{
InitializeComponent();
LoadHandler.OnLoadEnd += LoadHandler_OnLoadEnd;
}
private void LoadHandler_OnLoadEnd(object sender, Chromium.Event.CfxOnLoadEndEventArgs e)
{
// Check if it is the main frame when page has loaded.
if(e.Frame.IsMain)
{
EvaluateJavascript("sayHelloToSomeone('C#')", (value, exception) =>
{
if(value.IsString)
{
// Get value from Javascript.
var jsValue = value.StringValue;
MessageBox.Show(jsValue);
}
});
}
}
}
}
在上面的示例中,通過我們可以明確知道JS函數sayHelloToSomeone的返回值一定為String類型,因此在C#的回調中直接使用Value的StringValue屬性來獲取JS函數的字元傳返回值。但在實際的應用中,有可能並不完全知道返回值的類型,因此需要使用Value中內置的各個判斷屬性來逐一篩選返回值。
需要註意的是,Value的類是是ChromiumFX中的CfrV8Value類型,它是一個非常重要的類型,基本上所有在C#與CEF間的通信都是由這個類型來完成的。
Javascript調用C#對象及方法
簡單的應用示例
上面的文章中演示瞭如何用C#來調用Javascript中的函數,那麼下麵的內容將介紹如何使用Javascript來調用C#中的對象、屬性和各種方法。
在此之前,需要介紹NanUI窗體基類Formium中的重要屬性GlobalObject,您可以把他理解成Javascript環境中的window對象。如果您需要在Javascript環境下使用C#中的各種對象、屬性和方法,都需要將這些對象、屬性、方法註冊到GlobalObject里。
下麵的例子,通過在Form1的構造函數中註冊一個名為my的JS對象,併在my內置一個只讀屬性name,以及showCSharpMessageBox、getArrayFromCSharp、getObjectFromCSharp三個函數。
//register the "my" object
var myObject = GlobalObject.AddObject("my");
//add property "name" to my, you should implemnt the getter/setter of name property by using PropertyGet/PropertySet events.
var nameProp = myObject.AddDynamicProperty("name");
nameProp.PropertyGet += (prop, args) =>
{
// getter - if js code "my.name" executes, it'll get the string "NanUI".
args.Retval = CfrV8Value.CreateString("NanUI");
args.SetReturnValue(true);
};
nameProp.PropertySet += (prop, args) =>
{
// setter's value from js context, here we do nothing, so it will store or igrone by your mind.
var value = args.Value;
args.SetReturnValue(true);
};
//add a function showCSharpMessageBox
var showMessageBoxFunc = myObject.AddFunction("showCSharpMessageBox");
showMessageBoxFunc.Execute += (func, args) =>
{
//it will be raised by js code "my.showCSharpMessageBox(`some text`)" executed.
//get the first string argument in Arguments, it pass by js function.
var stringArgument = args.Arguments.FirstOrDefault(p => p.IsString);
if (stringArgument != null)
{
MessageBox.Show(this, stringArgument.StringValue, "C# Messagebox", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
};
//add a function getArrayFromCSharp, this function has an argument, it will combind C# string array with js array and return to js context.
var friends = new string[] { "Mr.JSON", "Mr.Lee", "Mr.BONG" };
var getArrayFromCSFunc = myObject.AddFunction("getArrayFromCSharp");
getArrayFromCSFunc.Execute += (func, args) =>
{
var jsArray = args.Arguments.FirstOrDefault(p => p.IsArray);
if (jsArray == null)
{
jsArray = CfrV8Value.CreateArray(friends.Length);
for (int i = 0; i < friends.Length; i++)
{
jsArray.SetValue(i, CfrV8Value.CreateString(friends[i]));
}
}
else
{
var newArray = CfrV8Value.CreateArray(jsArray.ArrayLength + friends.Length);
for (int i = 0; i < jsArray.ArrayLength; i++)
{
newArray.SetValue(i, jsArray.GetValue(i));
}
var jsArrayLength = jsArray.ArrayLength;
for (int i = 0; i < friends.Length; i++)
{
newArray.SetValue(i + jsArrayLength, CfrV8Value.CreateString(friends[i]));
}
jsArray = newArray;
}
//return the array to js context
args.SetReturnValue(jsArray);
//in js context, use code "my.getArrayFromCSharp()" will get an array like ["Mr.JSON", "Mr.Lee", "Mr.BONG"]
};
//add a function getObjectFromCSharp, this function has no arguments, but it will return a Object to js context.
var getObjectFormCSFunc = myObject.AddFunction("getObjectFromCSharp");
getObjectFormCSFunc.Execute += (func, args) =>
{
//create the CfrV8Value object and the accssor of this Object.
var jsObjectAccessor = new CfrV8Accessor();
var jsObject = CfrV8Value.CreateObject(jsObjectAccessor);
//create a CfrV8Value array
var jsArray = CfrV8Value.CreateArray(friends.Length);
for (int i = 0; i < friends.Length; i++)
{
jsArray.SetValue(i, CfrV8Value.CreateString(friends[i]));
}
jsObject.SetValue("libName", CfrV8Value.CreateString("NanUI"), CfxV8PropertyAttribute.ReadOnly);
jsObject.SetValue("friends", jsArray, CfxV8PropertyAttribute.DontDelete);
args.SetReturnValue(jsObject);
//in js context, use code "my.getObjectFromCSharp()" will get an object like { friends:["Mr.JSON", "Mr.Lee", "Mr.BONG"], libName:"NanUI" }
};
運行項目打開CEF的DevTools視窗,在Console中輸入my,就能看到my對象的詳細信息。
執行my.showCSharpMessageBox("SOME TEXT FROM JS")命令,將調用C#的MessageBox來現實JS函數中提供的“SOME TEXT FROM JS”字樣。
執行my.getArrayFromCSharp()能夠從C#中取到我們內置的字元串數組中的三個字元串。如果在函數中指定了一個數組作為參數,那麼指定的這個數組將和C#的字元串數組合併。
> my.getArrayFromCSharp()
["Mr.JSON", "Mr.Lee", "Mr.BONG"]
> my.getArrayFromCSharp(["Js_Bison", "Js_Dick"])
["Js_Bison", "Js_Dick", "Mr.JSON", "Mr.Lee", "Mr.BONG"]
執行my.getObjectFromCSharp()能夠從C#返回我們拼裝的對象,該對象有一個字元型的libName屬性,以及一個字元串數組friends。
> my.getObjectFromCSharp()
Object {libName: "NanUI", friends: Array(3)}
回調函數
回調函數是Javascript裡面重要和常用的功能,如果您在JS環境中註冊的方法具有函數型的參數(即回調函數),通過Execute事件的Arguments可以獲得回調的function,並使用CfrV8Value的ExecuteFunction來執行回調。
//add a function with callback function
var callbackTestFunc = GlobalObject.AddFunction("callbackTest");
callbackTestFunc.Execute += (func,args)=> {
var callback = args.Arguments.FirstOrDefault(p => p.IsFunction);
if(callback != null)
{
var callbackArgs = CfrV8Value.CreateObject(new CfrV8Accessor());
callbackArgs.SetValue("success", CfrV8Value.CreateBool(true), CfxV8PropertyAttribute.ReadOnly);
callbackArgs.SetValue("text", CfrV8Value.CreateString("Message from C#"), CfxV8PropertyAttribute.ReadOnly);
callback.ExecuteFunction(null, new CfrV8Value[] { callbackArgs });
}
};
在Console中執行callbackTest(function(result){ console.log(result); })將執行匿名回調,並獲取到C#回傳的result對象。
> callbackTest(function(result){ console.log(result); })
Object {success: true, text: "Message from C#"}
在大多數情況下,在Javascript中回調都是因為執行了一些非同步的操作,那麼如果這些非同步的操作是在C#執行也是可行的,只是實現起來就比較複雜。下麵將演示如何實現一個非同步回調。
//add a function with async callback
var asyncCallbackTestFunc = GlobalObject.AddFunction("asyncCallbackTest");
asyncCallbackTestFunc.Execute += async (func, args) => {
//save current context
var v8Context = CfrV8Context.GetCurrentContext();
var callback = args.Arguments.FirstOrDefault(p => p.IsFunction);
//simulate async methods.
await Task.Delay(5000);
if (callback != null)
{
//get render process context
var rc = callback.CreateRemoteCallContext();
//enter render process
rc.Enter();
//create render task
var task = new CfrTask();
task.Execute += (_, taskArgs) =>
{
//enter saved context
v8Context.Enter();
//create callback argument
var callbackArgs = CfrV8Value.CreateObject(new CfrV8Accessor());
callbackArgs.SetValue("success", CfrV8Value.CreateBool(true), CfxV8PropertyAttribute.ReadOnly);
callbackArgs.SetValue("text", CfrV8Value.CreateString("Message from C#"), CfxV8PropertyAttribute.ReadOnly);
//execute callback
callback.ExecuteFunction(null, new CfrV8Value[] { callbackArgs });
v8Context.Exit();
//lock task from gc
lock (task)
{
Monitor.PulseAll(task);
}
};
lock (task)
{
//post task to render process
v8Context.TaskRunner.PostTask(task);
}
rc.Exit();
GC.KeepAlive(task);
}
在Console中執行asyncCallbackTest(function(result){ console.log(result); })將執行匿名回調,大約5秒後獲取到C#回傳的result對象。
> asyncCallbackTest(function(result){ console.log(result); })
Object {success: true, text: "Message from C#"}
以上,您已經簡單瞭解了使用NanUI如何做到C#和Javascript的相互通信。NanUI基於開源項目ChromiumFX開發,因此C#與Javascript的交互與ChomiumFX保持一致,如果需要開發更加複雜的功能,請自行搜索和參考ChromiumFX的相關API及示例。
示例源碼
git clone https://github.com/NetDimension/NanUI-Examples-04-Communicate-Between-CSharp-And-JS.git
社群和幫助
GitHub
https://github.com/NetDimension/NanUI/
交流群QQ群
521854872
贊助作者
如果你喜歡我的工作,並且希望NanUI持續的發展,請對NanUI項目進行捐助以此來鼓勵和支持我繼續NanUI的開發工作。你可以使用微信或者支付寶來掃描下麵的二維碼進行捐助。