何以实现C

NanUI文档目录,与JS执行相关的回调函数被暴露是通过CefRenderProcessHandler接口实现

NanUI文档目录

一、介绍

Google浏览器和CEF使用V8JavaScript
Engine作为内容的JavaScript实现。在浏览器中的每个窗口都有它自己在的JS上下文提供效能域和在窗口中安全的履行JS代码。CEF暴露大量JS效率集成在客户端应用程序。
CEF3的Webkit和JS在独立的渲染进程中运作。在渲染进程的主线程中采取TID_RENDERER
作为唯一标识。所有V8的履行必须放置在这一个线程中。
与JS执行有关的回调函数被爆出是透过CefRenderProcessHandler接口贯彻。当一个新的渲染进程被初叶化时经过CefApp::GetRenderProcessHandler()函数获取这些接口。

怎么实现C#与Javascript的相互通信

通过从前的篇章,相信您曾经对NanUI有了起首的打听。但到近期截至,大家拔取NanUI仅仅只是作为呈现HTML界面的器皿,并未涉嫌CEF与C#间数据的互动。那么本文将简单介绍怎么着在NanUI中使用C#调用Javascript的函数以及咋样在Javascript注入C#的目的、属性和形式。

二、执行JavaScript

C#调用Javascript函数

在客户端应用程序中简易的方法执行JS使用CefFrame::ExecuteJavaScript()函数,这一个函数在浏览进程一和渲染进程中都可用。在一个JS上下文外可安全使用。

不需要取得再次回到值的情况

假如页面中似乎下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函数。


CefRefPtr<CefBrowser> browser = …;
CefRefPtr<CefFrame> frame = browser->GetMainFrame(); 
frame->ExecuteJavaScript(“alert(‘ExecuteJavaScript
works!’);”, frame->GetURL(), 0);

亟待取得重临值的情形

下边的例证中经过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间的通信都是由这一个类型来成功的。


下边的引起alert(‘ExecuteJavaScript works!’)在浏览器的主窗口中执行。

Javascript调用C#目的及方法

在窗口的JS上下文中ExecuteJavaScript函数可用以函数和变量交互。是为着从JS到客户端应用程序重临值使用窗口绑定和扩张。

简短的运用示范

上边的随笔中示范了怎么用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目的的详细消息。

澳门蒲京 1

执行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

 窗口绑定允许客户端应用程序系上一个值到窗口的window对象上,窗口绑定的落实应用CefRenderProcessHandler::OnContextCreated()函数。

社群和声援

GitHub
https://github.com/NetDimension/NanUI/

交流群QQ群
521854872

支援作者

一旦你喜欢自己的做事,并且期望NanUI持续的迈入,请对NanUI项目展开帮衬以此来鼓励和援助自己连续NanUI的开销工作。你可以动用微信或者支付宝来围观下边的二维码举行帮衬。

澳门蒲京 2

如:

void MyRenderProcessHandler::OnContextCreated(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefV8Context> context) {
  // Retrieve the context's window object.
  CefRefPtr<CefV8Value> object = context->GetGlobal();

  // Create a new V8 string value. See the "Basic JS Types" section below.
  CefRefPtr<CefV8Value> str = CefV8Value::CreateString("My Value!");

  // Add the string to the window object as "window.myval". See the "JS Objects" section below.
  object->SetValue("myval", str, V8_PROPERTY_ATTRIBUTE_NONE);
}

JavaScript框架可以与之并行的窗口绑定。

<script language="JavaScript">
alert(window.myval); // Shows an alert box with "My Value!"
</script>

窗口绑定是每一趟重复加载一个框架加载给客户端应用程序在必要时更改绑定的时机。例如,不同的框架通过修改绑定框架的窗口对象值可以访问不同的特点。

四、扩展

恢宏像window绑定一样除了为每个框架
加载到上下文之外,一旦加载就不可能修改,当一个恢弘已经加载并意欲在扩大加载中访问DOM就会现出DOM不存在的crash。增加应该在CefRenderProcessHandler::OnWeb基特(Kit)Initialized()函数中应用CefRegisterExtension函数注册。

void MyRenderProcessHandler::OnWebKitInitialized() {
  // Define the extension contents.
  std::string extensionCode =
    "var test;"
    "if (!test)"
    " test = {};"
    "(function() {"
    " test.myval = 'My Value!';"
    "})();";

  // Register the extension.
  CefRegisterExtension("v8/test", extensionCode, NULL);
}

通过extensionCode描述的字符串可以是另外有效的代码,JS框架可以和扩大代码举行互相

<script language="JavaScript">
alert(test.myval); // Shows an alert box with "My Value!"
</script>

五、基本JS类型

CEF协理焦点数据类型的创导,包括:undefined, null, bool, int, double,
date 和
string.那一个骨干数据类型使用CefV8Value::Create*()体系静态函数创造。如创设一个JS字符串:CefRefPtr<CefV8Value> str = CefV8Value::CreateString(“My
Value!”);

要旨数据类型可在其他地方创造和在所关联的上下文中不用开始化。如:

CefRefPtr<CefV8Value> val = …;
if (val.IsString())
{ // The value is a string. }

使用Get*Value()系列函数获取值:CefString strVal = val.GetStringValue();

六、JS数组

动用CefV8Value::CreateArray()静态函数并传递一个长度作为参数成立数组。数组只好在上下文内部创造并应用。如:

CefRefPtr<CefV8Value> arr = CefV8Value::CreateArray(2);

澳门蒲京, 值赋给一个数组使用SetValue()方法的变体,以一个索引作为第一个参数。
arr->SetValue(0, CefV8Value::CreateString(“My First
String!”));
arr->SetValue(1, CefV8Value::CreateString(“My Second
String!”));

IsArray()函数测试CefV8Value是否为数组,GetArrayLength()函数获取数据的长度,从数组中拿走一个值使用GetValue()变体函数。

七、JS对象

行使CefV8Value::CreateObject静态函数可带一个可选的CefV8Accessor参数创造JS对象。对象也不得不在js上下文中创制并拔取。

 CefRefPtr<CefV8Value> obj = CefV8Value::CreateObject(NULL);

应用SetValue()变体函数并以字符串key作为第一参数给目的分配值。

八、对象的访问者

JS对象可挑选使用一个与之提到的CefV8Accessor以提供一个源生的getting和setting值的落实。
CefRefPtr<CefV8Accessor> accessor = …;
CefRefPtr<CefV8Value> obj = CefV8Value::CreateObject(accessor);

CefV8Accessor接口的一个兑现,必须由客户端应用程序提供。

class MyV8Accessor : public CefV8Accessor {
public:
  MyV8Accessor() {}

  virtual bool Get(const CefString& name,
                   const CefRefPtr<CefV8Value> object,
                   CefRefPtr<CefV8Value>& retval,
                   CefString& exception) OVERRIDE {
    if (name == "myval") {
      // Return the value.
      retval = CefV8Value::CreateString(myval_);
      return true;
    }

    // Value does not exist.
    return false;
  }

  virtual bool Set(const CefString& name,
                   const CefRefPtr<CefV8Value> object,
                   const CefRefPtr<CefV8Value> value,
                   CefString& exception) OVERRIDE {
    if (name == "myval") {
      if (value.IsString()) {
        // Store the value.
        myval_ = value.GetStringValue();
      } else {
        // Throw an exception.
        exception = "Invalid value type";
      }
      return true;
    }

    // Value does not exist.
    return false;
  }

  // Variable used for storing the value.
  CefString myval_;

  // Provide the reference counting implementation for this class.
  IMPLEMENT_REFCOUNTING(MyV8Accessor);
};

为了将值传递给访问者必须利用SetValue()变体函数设置,接受AccessControl和PropertyAttribute参数

九、JS函数

CEF帮助JS函数创设和地点实现,