CefSharp初識--把網頁移到桌面的神器
在開發中我們可曾有過這樣的需求,將某個網頁嵌入到.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;
}
}
View Code 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 Callback</button>
<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;
}
}
View Code 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("<li>" + name + "</li>");
}
</script>
</ul>
'bound的'所有屬性:<br />
<ul>
<script type="text/javascript">
for (var name in bound)
{
if (bound[name].constructor.name === 'Function') continue;
document.write("<li>" + name + "</li>");
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("<li>" + name + "." + sub + "(" + type + ")" + "</li>");
}
}
}
</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 Test</title>
</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 Callback</button>
<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 Callback</button>
<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 + "<br/>");
for (var i = 0; i < jsonObj.employees.length; i++)
{
var employee = jsonObj.employees[i];
document.write("Employee : " + employee.firstName + " " + employee.lastName + "<br/>");
}
</script>
</p>
'bound的'所有方法:<br />
<ul>
<script type="text/javascript">
for (var name in bound)
{
if (bound[name].constructor.name != 'Function') continue;
document.write("<li>" + name + "</li>");
}
</script>
</ul>
'bound的'所有屬性:<br />
<ul>
<script type="text/javascript">
for (var name in bound)
{
if (bound[name].constructor.name === 'Function') continue;
document.write("<li>" + name + "</li>");
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("<li>" + name + "." + sub + "(" + type + ")" + "</li>");
}
}
}
</script>
</ul>
</body>
</html>
View Code 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的支持確實不錯。后續有機會做更多分享。