本项目环境:使用VS2010(C#)编写的WPF程序,通过CefSharp在程序的窗体中打开网页。需要能够实现网页后台JS代码中调用的方法,从网页接收数据,并能返回数据给网页。运行程序的电脑不允许上网,要求通过局域网内一个指定的代理服务器联网,并且只有该程序能通过代理服务器打开网页,直接用浏览器或其他应用程序仍然不允许上网(因此不能直接更改本机的LAN设置)。
首先介绍一下CefSharp,它是基于Google浏览器的一个组件,是可以用在WPF/WinForm客户端软件中的嵌入式浏览器。
如果你只需要通过客户端软件的一个窗体打开现成的网页,那么方法还是比较简单的,你可以直接参考http://***/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的有所不同,这里不做讨论。