本項目環境:使用VS2010(C#)編寫的WPF程序,通過CefSharp在程序的窗體中打開網頁。需要能夠實現網頁后台JS代碼中調用的方法,從網頁接收數據,並能返回數據給網頁。運行程序的電腦不允許上網,要求通過局域網內一個指定的代理服務器聯網,並且只有該程序能通過代理服務器打開網頁,直接用瀏覽器或其他應用程序仍然不允許上網(因此不能直接更改本機的LAN設置)。
首先介紹一下CefSharp,它是基於Google瀏覽器的一個組件,是可以用在WPF/WinForm客戶端軟件中的嵌入式瀏覽器。
如果你只需要通過客戶端軟件的一個窗體打開現成的網頁,那么方法還是比較簡單的,你可以直接參考http://www.w2bc.com/Article/54798這篇文章。
1、配置環境,加載CefSharp動態庫
首先需要下載NuGet插件,它是免費、開源的包管理開發工具,專注於在 .NET 應用開發過程中,簡單地合並第三方的組件庫。使用該工具可以將CefSharp的完整程序包加載到工程中。下載地址:http://xz.cr173.com/soft2/nuget.tools.zip。
本項目是用VS2010編寫的,因此運行NuGet.Tools for vs2010,安裝后,在VS的“工具”菜單下會多出一項“NuGet程序包管理器”,見下圖所示:

新建工程文件EmbeddedWebBrowser。點擊“管理解決方案的NuGet程序包”,打開如下圖所示的窗體:
在右上角聯機搜索“CefSharp”,下載CefSharp.Wpf和CefSharp.Common。如果你用的不是WPF,而是WinForm,則需要下載CefSharp.WinForms和CefSharp.Common。下載的過程比較慢,網速不佳的情況下大概要一個小時左右。安裝完成后,在工程文件所在目錄下會多出“packages”目錄。
2、MainWindow窗體:用於輸入網頁地址和初始化Cef
在工程文件中的MainWindow窗體中,添加一個用於輸入網頁地址的編輯框,和一個用於打開網頁的按鈕。MainWindow.xaml的代碼如下所示:
<Window x:Class="EmbeddedWebBrowser.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="800" WindowStartupLocation="CenterScreen" Loaded="Window_Loaded"> <Grid> <DockPanel> <StackPanel DockPanel.Dock="Top" Orientation="Horizontal"> <TextBlock Text="網頁地址:" Margin="5"/> <TextBox x:Name="txtAddress" Width="350" Margin="5"/> <Button Content="Go" Margin="5" Click="OnGoClick" IsDefault="True"/> </StackPanel> <Grid x:Name="MainGrid"> </Grid> </DockPanel> </Grid> </Window>
該頁面的后台代碼MainWindow.xaml.cs如下所示:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using CefSharp;
namespace EmbeddedWebBrowser { /// <summary> /// MainWindow.xaml 的交互邏輯 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } /// <summary> /// 窗體加載事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Window_Loaded(object sender, RoutedEventArgs e) {
//默認打開的頁面 txtAddress.Text = "http://www.baidu.com/"; //txtAddress.Text = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName.Replace("EmbeddedWebBrowser.vshost.exe", "index.html");
//設置代理服務器 var setting = new CefSharp.CefSettings(); setting.CefCommandLineArgs.Add("--proxy-server", "http://192.168.0.105:3128");
CefSharp.Cef.Initialize(setting, true, false); } /// <summary> /// “Go”按鈕單擊事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnGoClick(object sender, RoutedEventArgs e) { //獲取要打開的頁面地址 string url = txtAddress.Text; if (!string.IsNullOrWhiteSpace(url)) { //打開網頁 WebPageViewer viewer = new WebPageViewer(url); MainGrid.Children.Insert(0, viewer); } } } }
這里最關鍵的就是:setting.CefCommandLineArgs.Add("--proxy-server", "http://192.168.0.105:3128");這是用來設置代理服務器的IP和端口的。
CefSharp.Cef.Initialize(setting, true, false); 用來初始化CEF,即嵌入式瀏覽器。
2016-04-18補充:CefSharp.Cef.Initialize方法在程序里只能使用一次,如果把它寫在一個會多次打開的子頁面,就會報錯:“只能初始化一次”。因此需要將這個方法寫在主程序App.cs里。並且在程序退出前使用:CefSharp.Cef.Shutdown();否則程序關閉之后,可能會由於CefSharp.Cef未關閉,而使得該程序仍然存在后台進程中(可在任務管理器中查看),影響再次打開程序使用。
3、WebPageViewer.xaml:用於打開和顯示網頁
創建一個用戶控件WebPageViewer.xaml,用於打開和顯示網頁,代碼如下所示:
<UserControl x:Class="EmbeddedWebBrowser.WebPageViewer" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:EmbeddedWebBrowser" xmlns:uc="clr-namespace:EmbeddedWebBrowser" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid x:Name="MainGrid"> <uc:MaskLoading x:Name="maskLoading"/> </Grid> </UserControl>
這里的“MaskLoading ”是用來顯示網頁加載時的等待畫面的,下面會進行介紹。
后台代碼WebPageViewer.xaml.cs如下所示:
using CefSharp; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Net; using CefSharp.Wpf; namespace EmbeddedWebBrowser { /// <summary> /// 用於顯示網頁的控件 /// </summary> public partial class WebPageViewer : UserControl, IRequestHandler { /// <summary> /// 用於顯示網頁的自定義控件,構造函數 /// </summary> /// <param name="url">網頁地址</param> public WebPageViewer(String url) { InitializeComponent(); var webView = new CefSharp.Wpf.ChromiumWebBrowser(); //注冊一個JS對象 webView.RegisterJsObject("hy", new CallbackObjectForJs()); //注冊網頁加載事件:在頂級導航完成且內容加載到 WebView 控件中時發生 webView.Loaded += new RoutedEventHandler(webView_Loaded); MainGrid.Children.Insert(0, webView); webView.Address = url; } /// <summary> /// 網頁加載完畢的事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void webView_Loaded(object sender, RoutedEventArgs e) { maskLoading.Visibility = Visibility.Collapsed; //隱藏等待畫面(正在加載...) } public bool GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback) { throw new NotImplementedException(); } public bool OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, bool isRedirect) { throw new NotImplementedException(); } public CefReturnValue OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback) { throw new NotImplementedException(); } public bool OnCertificateError(IWebBrowser browserControl, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback) { throw new NotImplementedException(); } public bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture) { throw new NotImplementedException(); } public void OnPluginCrashed(IWebBrowser browserControl, IBrowser browser, string pluginPath) { throw new NotImplementedException(); } public bool OnProtocolExecution(IWebBrowser browserControl, IBrowser browser, string url) { throw new NotImplementedException(); } public bool OnQuotaRequest(IWebBrowser browserControl, IBrowser browser, string originUrl, long newSize, IRequestCallback callback) { throw new NotImplementedException(); } public void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) { throw new NotImplementedException(); } public void OnRenderViewReady(IWebBrowser browserControl, IBrowser browser) { throw new NotImplementedException(); } public void OnResourceLoadComplete(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength) { throw new NotImplementedException(); } public void OnResourceRedirect(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, ref string newUrl) { throw new NotImplementedException(); } public bool OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response) { throw new NotImplementedException(); } } /// <summary> /// 網頁JS中調用的方法 /// </summary> public class CallbackObjectForJs { public string action(string message) { //MessageBox.Show("測試"); return "收到網頁消息:" + message; } } }
該類實現IRequestHandler接口,是為了能夠和JS網頁進行交互,從GetAuthCredentials到OnResourceResponse的那幾個空方法都是為了實現這個接口。當然這些在打開百度時是用不到的,接下來會用一個自己編寫的JS網頁做講解。
4、與JS網頁之間進行交互(如果只是打開百度等現成的網頁,可以忽略這一項)
先編寫一個網頁index.html,如下所示:
<html> <head> <title>Test Page</title> </head> <body> <p style="color: red">Hello, World!</p> <button onclick = helloWebkit()>test</button> <div><p> :) </p></div> <script type="text/javascript"> function helloWebkit() { alert("input:123"); var x = hy.action("123"); alert(x); } </script> </body> </html>
在該網頁上有一個按鈕test,點擊后會彈出提示“input:123”,並調用hy類中的action方法,傳入參數"123"。
將該網頁放到DEBUG目錄下,並將第2步MainWindow.xaml.cs中的網頁地址改為該網頁的本地地址:
txtAddress.Text = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName.Replace("EmbeddedWebBrowser.vshost.exe", "index.html");
其中EmbeddedWebBrowser為工程文件名稱。
在第3步的WebPageViewer.xaml.cs中,注冊了一個JS對象"hy": webView.RegisterJsObject("hy", new CallbackObjectForJs());在 CallbackObjectForJs 類中實現了action方法(該方法名全為小寫字母),接收網頁上傳來的參數,並將處理后的數據返回給網頁。
5、在網頁加載時顯示等待畫面
第三步的代碼中有用到“MaskLoading ”,MaskLoading.xaml的代碼如下所示:
<UserControl x:Class="EmbeddedWebBrowser.MaskLoading" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:EmbeddedWebBrowser" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> <Grid Opacity=".85"> <TextBlock Text="頁面加載中..." VerticalAlignment="Center" HorizontalAlignment="Center"/> </Grid> </UserControl>
該頁面沒有任何邏輯,只是為了在網頁未加載完成時顯示“頁面加載中...”的提示信息。后台代碼MaskLoading.xaml.cs如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace EmbeddedWebBrowser { /// <summary> /// Interaction logic for MaskLoading.xaml /// </summary> public partial class MaskLoading : UserControl { /// <summary> /// 顯示等待畫面“頁面加載中...” /// </summary> public MaskLoading() { InitializeComponent(); } } }
6、其他注意事項
如果編譯的時候報錯,提示:“無法加載CefSharp.Core.dll組件”,則需要確保電腦上有VC++2013版本的運行庫(2010版本是不行的)。運行庫的下載地址:https://www.microsoft.com/en-us/download/details.aspx?id=40784。點擊紅色的DownLoad按鈕,選擇vcredist_x86.exe。
如果仍然報錯,檢查代碼的生成屬性:在工程文件上右擊,選擇屬性——生成,打開如下圖所示的界面:

目標平台需要選擇“x86”。
2016-04-18補充:若平台下拉框中找不到“x86”,需要按下列步驟進行添加。
若選擇了平台x86,運行時仍然報錯,則需要將原有的x86平台刪除(按下面的步驟打開配置管理器,在活動解決方案平台中選“編輯”,移除x86),再按下面的步驟重新添加。
右擊解決方案--屬性--配置屬性--配置管理器,打開如下圖所示的窗體:
新建x86活動平台后,編譯程序時,可能目標文件會輸出到bin\x86\Debug下。此時需要在工程的屬性里,將輸出路徑改為bin\Debug。

如果是64位機,上述所有的x86都需要改為x64,運行庫也需要下載vcredist_x64.exe才行。
Winform應用程序的寫法可能會和WPF的有所不同,這里不做討論。
