NanUI文檔 - 如何實現C#與Javascript的相互通信

来源:https://www.cnblogs.com/linxuanchen/archive/2018/01/12/NanUI-Examples-04-Communicate-Between-CSharp-And-JS.html
-Advertisement-
Play Games

NanUI文檔目錄 "NanUI簡介" "開始使用NanUI" "打包並使用內嵌式的HTML/CSS/JS資源" "使用網頁來設計整個視窗" "如何實現C 與Javascript的相互通信" 如何處理NanUI中的下載過程 DonwloadHandler的使用 (待更新。。。) 如何處理NanUI中 ...


NanUI文檔目錄

如何實現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,以及showCSharpMessageBoxgetArrayFromCSharpgetObjectFromCSharp三個函數。

//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的開發工作。你可以使用微信或者支付寶來掃描下麵的二維碼進行捐助。

Screen Shot


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 創建響應式WinForm應用程式並不那麼簡單。 響應式佈局,我們在此指的是在不同屏幕解析度下的可用性。 對於WinForm應用程式,我們需要明確地根據解析度來調整控制項的大小和重新定位。 雖然在使用WPF時有相關的實踐應用,通過使用控制項的docking和anchoring,或使用panels等方法,但... ...
  • BlockingCollection集合是一個擁有阻塞功能的集合,它就是完成了經典生產者消費者的演算法功能。一般情況下,我們可以基於 生產者 - 消費者模式來實現併發。BlockingCollection<T> 類是最好的解決方案 剛結束的物聯網卡項目,我需要調用移動的某個具有批量獲取物聯網卡數據的接 ...
  • 概述 在之前寫的一篇關於async和await的前世今生的文章之後,大家似乎在async和await提高網站處理能力方面還有一些疑問,博客園本身也做了不少的嘗試。今天我們再來回答一下這個問題,同時我們會做一個async和await在WinForm中的嘗試,並且對比在4.5之前的非同步編程模式APM/E ...
  • 一個網頁,它是顯示圖片,但在一些瀏覽器,它卻顯示如下: Insus.NET猜,不是瀏覽器不相容,就是代碼有問題。 在代碼中,只是輸出數據流,圖片格式很多種,如jpg,png,bmp等,沒有指定,程式也不清楚要顯示什麼格式的圖片。因此,Insus.NET把代碼改為如下: context.Respons ...
  • 為什麼要學習表達式樹?表達式樹是將我們原來可以直接由代碼編寫的邏輯以表達式的方式存儲在樹狀的結構里,從而可以在運行時去解析這個樹,然後執行,實現動態的編輯和執行代碼。LINQ to SQL就是通過把表達式樹翻譯成SQL來實現的,所以瞭解表達樹有助於我們更好的理解 LINQ to SQL,同時如果你有 ...
  • 快樂的Lambda表達式 上一篇 背後的故事之 - 快樂的Lambda表達式(一)我們由淺入深的分析了一下Lambda表達式。知道了它和委托以及普通方法的區別,並且通過測試對比他們之間的性能,然後我們通過IL代碼深入瞭解了Lambda表達式,以及介紹瞭如何在.NET中用Lambda表達式來實現Jav ...
  • 快樂的Lambda表達式(二) 自從Lambda隨.NET Framework3.5出現在.NET開發者眼前以來,它已經給我們帶來了太多的欣喜。它優雅,對開發者更友好,能提高開發效率,天啊!它還有可能降低發生一些潛在錯誤的可能。LINQ包括ASP.NET MVC中的很多功能都是用Lambda實現的。 ...
  • C#集體類型( Collections in C#) 集合是.NET FCL(Framework Class Library)中很重要的一部分,也是我們開發當中最常用到的功能之一,幾乎是無處不在。俗話說知其然,知其所以然,平常看到IEnumerable,IEnumerator,ICollection ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...