在开发中我们可曾有过这样的需求,将某个网页嵌入到.Net应用中来,但Winform自带的web browser不怎么理想。CefSharp可以让我们在.Net应用中嵌入一个Chromium。它提供了WPF和Winform版的web browser 控件,能很好的渲染出HTML5效果而且和宿主程序有很强的交互能力。 git地址:https://github.com/cefsharp/CefSharp 。
在WPF中使用
在Nugget中输入CefSharp,找到CefSharp.WPF 并按照到工程中。
cefsharp不支持anycup,还需要设置一下目标平台为x86或x64. 具体请移步:http://www.cnblogs.com/yuefei/p/4123597.html
渲染效果
加入一个css3的动画:转动的风车。 元素结构还是很清晰,但动画效果还是没有浏览器流畅。
交互方法
cefsharp支持JavaScript和C#方法相互调用。首先需要注册一个绑定对象:
private void MainWindow_OnLoaded(object sender, RoutedEventArgs e) { var wb = new ChromiumWebBrowser { Address = @"file:///D:/VS2012/Support/Main/Portal/Presentation/Portal.Client/Resources/BindingTest.html" }; wb.RegisterJsObject("bound", new BoundObject()); WBGrid.Children.Add(wb); }
BoundObject:
public class BoundObject { public int MyProperty { get; set; } public string MyReadOnlyProperty { get; internal set; } public Type MyUnconvertibleProperty { get; set; } public SubBoundObject SubObject { get; set; } public ExceptionTestBoundObject ExceptionTestObject { get; set; } public uint[] MyUintArray { get { return new uint[] { 7, 8 }; } } public int[] MyIntArray { get { return new[] { 1, 2, 3, 4, 5, 6, 7, 8 }; } } public Array MyArray { get { return new short[] { 1, 2, 3 }; } } public byte[] MyBytes { get { return new byte[] { 3, 4, 5 }; } } public BoundObject() { MyProperty = 42; MyReadOnlyProperty = "I'm immutable!"; IgnoredProperty = "I am an Ignored Property"; MyUnconvertibleProperty = GetType(); SubObject = new SubBoundObject(); ExceptionTestObject = new ExceptionTestBoundObject(); } public void TestCallback(IJavascriptCallback javascriptCallback) { const int taskDelay = 1500; Task.Run(async () => { await Task.Delay(taskDelay); await javascriptCallback.ExecuteAsync("This callback from C# was delayed " + taskDelay + "ms"); }); } public int EchoMyProperty() { return MyProperty; } public string Repeat(string str, int n) { string result = String.Empty; for (int i = 0; i < n; i++) { result += str; } return result; } public string EchoParamOrDefault(string param = "This is the default value") { return param; } public void EchoVoid() { } public Boolean EchoBoolean(Boolean arg0) { return arg0; } public Boolean? EchoNullableBoolean(Boolean? arg0) { return arg0; } public SByte EchoSByte(SByte arg0) { return arg0; } public SByte? EchoNullableSByte(SByte? arg0) { return arg0; } public Int16 EchoInt16(Int16 arg0) { return arg0; } public Int16? EchoNullableInt16(Int16? arg0) { return arg0; } public Int32 EchoInt32(Int32 arg0) { return arg0; } public Int32? EchoNullableInt32(Int32? arg0) { return arg0; } public Int64 EchoInt64(Int64 arg0) { return arg0; } public Int64? EchoNullableInt64(Int64? arg0) { return arg0; } public Byte EchoByte(Byte arg0) { return arg0; } public Byte? EchoNullableByte(Byte? arg0) { return arg0; } public UInt16 EchoUInt16(UInt16 arg0) { return arg0; } public UInt16? EchoNullableUInt16(UInt16? arg0) { return arg0; } public UInt32 EchoUInt32(UInt32 arg0) { return arg0; } public UInt32? EchoNullableUInt32(UInt32? arg0) { return arg0; } public UInt64 EchoUInt64(UInt64 arg0) { return arg0; } public UInt64? EchoNullableUInt64(UInt64? arg0) { return arg0; } public Single EchoSingle(Single arg0) { return arg0; } public Single? EchoNullableSingle(Single? arg0) { return arg0; } public Double EchoDouble(Double arg0) { return arg0; } public Double? EchoNullableDouble(Double? arg0) { return arg0; } public Char EchoChar(Char arg0) { return arg0; } public Char? EchoNullableChar(Char? arg0) { return arg0; } public DateTime EchoDateTime(DateTime arg0) { return arg0; } public DateTime? EchoNullableDateTime(DateTime? arg0) { return arg0; } public Decimal EchoDecimal(Decimal arg0) { return arg0; } public Decimal? EchoNullableDecimal(Decimal? arg0) { return arg0; } public String EchoString(String arg0) { return arg0; } // TODO: This will currently not work, as it causes a collision w/ the EchoString() method. We need to find a way around that I guess. //public String echoString(String arg) //{ // return "Lowercase echo: " + arg; //} public String lowercaseMethod() { return "lowercase"; } public string ReturnJsonEmployeeList() { return "{\"employees\":[{\"firstName\":\"John\", \"lastName\":\"Doe\"},{\"firstName\":\"Anna\", \"lastName\":\"Smith\"},{\"firstName\":\"Peter\", \"lastName\":\"Jones\"}]}"; } [JavascriptIgnore] public string IgnoredProperty { get; set; } [JavascriptIgnore] public string IgnoredMethod() { return "I am an Ignored Method"; } public string ComplexParamObject(object param) { if (param == null) { return "param is null"; } return "The param type is:" + param.GetType(); } public SubBoundObject GetSubObject() { return SubObject; } }
JavaScript调用C#方法并执行回调函数:
<p> Javscript Callback Test <br /> <script type="text/javascript"> function callback(s) { var result = document.getElementById('cbresult'); result.innerText += "Callback: " + s+ "" + Date(); } function testCallback() { bound.testCallback(callback); var result = document.getElementById('cbresult'); result.innerText = "The function has returned: " + Date() + "\n"; } script> <button onclick="testCallback()">Test Callbackbutton> <br /> <span id="cbresult">span> p>
这里的bound就是我们注册的C#对象。其中包含一个TestCallback的方法。调用的时候不区分大小写。
public void TestCallback(IJavascriptCallback javascriptCallback) { const int taskDelay = 1500; Task.Run(async () => { await Task.Delay(taskDelay); using (javascriptCallback) { await javascriptCallback.ExecuteAsync("This callback from C# was delayed " + taskDelay + "ms"); } }); }
执行结果:
先执行了testCallback方法,然后执行了callback,返回了后台传递过来的参数。但如果再执行JavaScript之后页面跳转了,是不会再执行C#里面的回调函数的。
function testDisposedCallback() { bound.testCallback(callback); //这里的方法不会执行了。 var result = document.getElementById('disposedcbresult'); result.innerText = "The function has returned: " + Date() + "\n"; window.location.assign("http://www.baidu.com"); }
JavaScript执行有参数的C#方法
BoundObject有一个Repeat方法
public string Repeat(string str, int n) { string result = String.Empty; for (int i = 0; i < n; i++) { result += str; } return result; }
JavaScript调用:
<p> Result of calling bound.repeat("hi ", 5) = <script type="text/javascript"> var result = bound.repeat("hi ", 5); document.write('"' + result + '"'); if (result === "hi hi hi hi hi ") { document.write(" SUCCESS"); } else { document.write(" FAIL!"); } script> p>
执行结果:
委托C#方法
将绑定对象的方法作为参数传递给JavaScript方法。
<script type="text/javascript"> function myFunction(functionParam) { return functionParam(); } document.write("委托输出属性结果: " + myFunction(bound.echoMyProperty)); script>
echoMyProperty方法:
public int EchoMyProperty() { return MyProperty;//初始化为42 }
返回C#对象
BoundObject含有一个子对象 SubBoundObject。通过GetObject返回。
public SubBoundObject GetSubObject() { return SubObject; }
SubBoundObject:
public class SubBoundObject { public string SimpleProperty { get; set; } public SubBoundObject() { SimpleProperty = "这是子对象属性"; } public string GetMyType() { return "My Type is " + GetType(); } public string EchoSimpleProperty() { return SimpleProperty; } }
JavaScript调用:
<script type="text/javascript"> document.write("bound.getSubObject().simpleProperty result: " + bound.getSubObject().simpleProperty); script>
执行结果:
获取bound对象的所有方法和属性
'bound的'所有方法:<br /> <ul> <script type="text/javascript"> for (var name in bound) { if (bound[name].constructor.name != 'Function') continue; document.write("" + name + ""); } script> ul> 'bound的'所有属性:<br /> <ul> <script type="text/javascript"> for (var name in bound) { if (bound[name].constructor.name === 'Function') continue; document.write("" + name + ""); if (typeof bound[name] == "object" && bound[name] !== null) {
//展示子对象属性 for (var sub in bound[name]) { var type = bound[name][sub].constructor.name === 'Function' ? "Function" : "Property"; document.write("" + name + "." + sub + "(" + type + ")" + ""); } } } script> ul>
可以在C#对象中忽略掉属性和方法,这样就不会显示出来。
[JavascriptIgnore] public string IgnoredProperty { get; set; } [JavascriptIgnore] public string IgnoredMethod() { return "I am an Ignored Method"; }
整个测试页面:
DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Binding Testtitle>
head>
<body>
<p>
Async Binding Test
<span id="asyncresult">span>
<script type="text/javascript">
var asResult = document.getElementById('asyncresult');
function writeAsyncResult(call, end)
{
var p = document.createElement('p');
var br = document.createElement('br');
var br2 = document.createElement('br');
var title = document.createTextNode('Async Call: ');
var callText = document.createTextNode(call);
var endText = document.createTextNode(end);
p.appendChild(title);
p.appendChild(br);
p.appendChild(callText);
p.appendChild(br2);
p.appendChild(endText);
asResult.appendChild(p);
}
function asyncError()
{
var call = "Async call (Throw Exception): " + Date();
boundAsync.error().catch(function (e)
{
var end = "Error: " + e + "(" + Date() + ")";
writeAsyncResult(call, end);
});
}
function asyncDivOk()
{
var call = "Async call (Divide 16 / 2): " + Date();
boundAsync.div(16, 2).then(function (res)
{
var end = "Result: " + res + "(" + Date() + ")";
writeAsyncResult(call, end);
});
}
function asyncDivFail()
{
var call = "Async call (Divide 16 /0): " + Date();
boundAsync.div(16, 0).then(function (res)
{
var end = "Result: " + res + "(" + Date() + ")";
writeAsyncResult(call, end);
},
function (e)
{
var end = "Error: " + e + "(" + Date() + ")";
writeAsyncResult(call, end);
});
}
function asyncHello()
{
var call = "Async call (Hello): " + Date();
boundAsync.hello('CefSharp').then(function (res)
{
var end = "Result: " + res + "(" + Date() + ")";
writeAsyncResult(call, end);
});
}
function asyncDoSomething()
{
var call = "Async call (Long Running Task): " + Date();
boundAsync.doSomething().then(function (res)
{
var end = "Result: " + res + "(" + Date() + ")";
writeAsyncResult(call, end);
});
}
asyncError();
asyncDivOk();
asyncDivFail();
asyncDoSomething();
script>
p>
<p>
Javscript Callback Test
<br />
<script type="text/javascript">
function callback(s) {
var result = document.getElementById('cbresult');
result.innerText += "Callback: " + s + "" + Date();
}
function testCallback()
{
bound.testCallback(callback);
var result = document.getElementById('cbresult');
result.innerText = "The function has returned: " + Date() + "\n";
}
script>
<button onclick="testCallback()">Test Callbackbutton>
<br />
<span id="cbresult">span>
p>
<p>
Disposed Javscript Callback Test (navigates to www.google.com before callback fires)
<br />
<script type="text/javascript">
function disposedCallback(s)
{
// This callback should be disposed and should not be called
window.alert("This callback should not have been called");
var result = document.getElementById('disposedcbresult');
result.innerText += "Callback: " + s + "" + Date();
}
function testDisposedCallback()
{
bound.testCallback(callback);
var result = document.getElementById('disposedcbresult');
result.innerText = "The function has returned: " + Date() + "\n";
window.location.assign("http://www.baidu.com");
}
script>
<button onclick="testDisposedCallback()">Test Disposed Callbackbutton>
<br />
<span id="disposedcbresult">span>
p>
<p>
Result of calling bound.repeat("hi ", 5) =
<script type="text/javascript">
var result = bound.repeat("hi ", 5);
document.write('"' + result + '"');
if (result === "hi hi hi hi hi ")
{
document.write(" SUCCESS");
} else
{
document.write(" FAIL!");
}
script>
p>
<p>
委托c# 方法
<br />
<script type="text/javascript">
function myFunction(functionParam)
{
return functionParam();
}
document.write("委托输出属性结果: " + myFunction(bound.echoMyProperty));
script>
p>
<p>
Function returning complex type
<br />
<script type="text/javascript">
document.write("bound.getSubObject().simpleProperty result: " + bound.getSubObject().simpleProperty);
script>
p>
<p>
Stress Test
<br />
<script type="text/javascript">
var stressTestCallCount = 1000;
for (var i = 1; i <= stressTestCallCount; i++)
{
bound.repeat("hi ", 5);
}
document.write("Stress Test done with : " + stressTestCallCount + " call to bound.repeat(\"hi \", 5)");
script>
p>
<p>
JSON Serializer Test
<br />
<script type="text/javascript">
var json = bound.returnJsonEmployeeList();
var jsonObj = JSON.parse(json);
document.write("Employee Count : " + jsonObj.employees.length + "
");
for (var i = 0; i < jsonObj.employees.length; i++)
{
var employee = jsonObj.employees[i];
document.write("Employee : " + employee.firstName + " " + employee.lastName + "
");
}
script>
p>
'bound的'所有方法:<br />
<ul>
<script type="text/javascript">
for (var name in bound)
{
if (bound[name].constructor.name != 'Function') continue;
document.write("" + name + "");
}
script>
ul>
'bound的'所有属性:<br />
<ul>
<script type="text/javascript">
for (var name in bound)
{
if (bound[name].constructor.name === 'Function') continue;
document.write("" + name + "");
if (typeof bound[name] == "object" && bound[name] !== null)
{
//展示子对象属性
for (var sub in bound[name])
{
var type = bound[name][sub].constructor.name === 'Function' ? "Function" : "Property";
document.write("" + name + "." + sub + "(" + type + ")" + "");
}
}
}
script>
ul>
body>
html>
WebGL的渲染效果
WebGL是一种3D绘图标准,这种绘图技术标准允许把JavaScript和OpenGL ES 2.0结合在一起,通过增加OpenGL ES 2.0的一个JavaScript绑定,WebGL可以为HTML5 Canvas提供硬件3D加速渲染,这样Web开发人员就可以借助系统显卡来在浏览器里更流畅地展示3D场景和模型了,还能创建复杂的导航和数据视觉化。
测试页面:http://webglsamples.org/aquarium/aquarium.html
这个效果还是不错的。
小结:以上只是简单的测试程序,CEFSharp对html5和JavaScript的支持确实不错。后续有机会做更多分享。