最近几天都被基于cefSharp封装的浏览器控件搞疯了!对于cefSharp基本满足当前所做项目的需求,但是有一个问题一直困扰我,那就是系统中偶尔会出现输入法不能转换到中文。而且这个问题似乎没有什么规律。
【项目需求】
先说一下项目对浏览器控件的需求,如果没有需要做听音这个功能,其实项目可以是一个B/S架构。但是由于现在需要听音功能,所以决定使用C/S的架构。其中客户端采用浏览器控件来实现网页加载。客户端主窗口内嵌一个浏览器控件,然后页面有需要听音的时候可以点击听音然后弹出听音窗口。听音窗口又是由一个音频播放器+浏览器控件组成。
【最初实现】
最初使用的是webBrowser控件,能够实现功能,但是webBrowser有太多坑,不断出现内存泄露和根据客户端IE版本进行样式渲染,HTML5兼容性差等等,诸多问题。于是痛下狠心,决定换基于webkit的浏览器控件。
【痛苦的尝试】
.Net中 基于webKit 的浏览器控件还是很多,于是做了一下各个版本的尝试:
- webKit.Net 0.5 : 该控件内核比较老,目前最新还是2010,该控件唯一的优势是和webBrowser使用比较接近。但是项目中使用了Angularjs,在每次取数据超过100条时,速度变慢。果断pass。
- OpenWebKitSharp :该项目是基于webKit.Net的一次封装。该控件对系统的功能能很好的满足,但是与我们的系统有一个致命的不兼容性那就是内存泄露。
- cefSharp:基于cef1,能够满足项目的基本需求所以选择了该控件。
【输入法异常】
选择了cefSharp后,在使用中出现一个让人崩溃的问题,那就是输入法偶尔出现不能转换到中文。而且不好定位问题出现在什么位置。裤裤折腾一周终于找到原因。
基于cefSharp1自己封装了一个浏览器控件,代码如下(有删减):
namespace XXXXXX.UserCtrol { using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Data; using System.Linq; using System.Text; using System.Windows.Forms; using CefSharp.WinForms; using CefSharp; using System.IO; public partial class WebKitBrowser : UserControl { #region 字段 /// <summary> /// webView /// </summary> private WebView _Core; /// <summary> /// 网页地址 /// </summary> private string _Url; /// <summary> /// CEF环境设置 /// </summary> private static Settings cefSettings; #endregion #region 构造函数 /// <summary> /// 构造函数 /// </summary> public WebKitBrowser() : this("about:blank") { } /// <summary> /// 构造函数 /// </summary> /// <param name="url">地址</param> public WebKitBrowser(string url) : this(url, new BrowserSettings()) { } /// <summary> /// 构造函数 /// </summary> /// <param name="url">地址</param> /// <param name="settings">浏览器设置</param> public WebKitBrowser(string url, BrowserSettings settings) { InitializeComponent(); _Url = url; this._Core = new WebView(url, settings); this._Core.Dock = DockStyle.Fill; this.Controls.Add(this._Core); BindEvents(); } #endregion #region 方 法 #region 公有方法 /// <summary> /// 页面导航 /// </summary> /// <param name="url">地址</param> public void Navigate(string url) { if (string.IsNullOrWhiteSpace(url)) { this._Core.Load("about:blank"); } else { this._Core.Load(url); } } /// <summary> /// JS交互对象 /// </summary> /// <param name="obj">交互对象</param> public void ObjectForScript(object obj) { this._Core.RegisterJsObject("external", obj); } /// <summary> /// 调用JS方法 /// </summary> /// <param name="funcName">函数名</param> /// <param name="args">参数</param> /// <param name="isAsync">是否异步</param> /// <returns>返回值</returns> public object CallJavaScriptMethod(string funcName, object[] args, bool isAsync = false) { StringBuilder sparam = new StringBuilder(); sparam.Append(funcName).Append("("); if (args != null) { for (int i = 0; i < args.Length; i++) { object o = args[i]; if (i > 0) { sparam.Append(","); } if (o is string) { sparam.Append("\"").Append(o.ToString().Replace("\"", "‘")).Append("\""); } else { sparam.Append(o); } } } sparam.Append(")"); try { if (isAsync) { this._Core.ExecuteScript(sparam.ToString()); return null; } else { return this._Core.EvaluateScript(sparam.ToString()); } } catch (Exception ex) { return null; } } /// <summary> /// 绑定下载控制器 /// </summary> /// <param name="hanler">下载控制类</param> public void BindDownLoadHandler(IRequestHandler hanler) { this._Core.RequestHandler = hanler; } /// <summary> /// 绑定快捷键菜单 /// </summary> /// <param name="Handler">菜单控制类</param> public void BindMenuHandler(IMenuHandler handler) { this._Core.MenuHandler = handler; } /// <summary> /// 绑定页面加载控制器 /// </summary> /// <param name="handler">控制器</param> public void BindLoadHandler(ILoadHandler handler) { this._Core.LoadHandler = handler; } /// <summary> /// 绑定JS弹出框控制器 /// </summary> /// <param name="handler">控制器</param> public void BindJSDialogHandler(IJsDialogHandler handler) { this._Core.JsDialogHandler = handler; } /// <summary> /// 资源释放 /// </summary> public void Dispose() { if(this._Core !=null) { this._Core.Dispose(); } } #endregion #region 私有方法 /// <summary> /// 绑定事件 /// </summary> private void BindEvents() { this._Core.LoadCompleted += new LoadCompletedEventHandler(_Core_LoadCompleted); this._Core.PropertyChanged += new PropertyChangedEventHandler(_Core_PropertyChanged); } #endregion #endregion #region 事 件 /// <summary> /// 浏览器加载完成事件 /// </summary> public EventHandler<LoadCompletedEventArgs> DocumentCompletedEvent; /// <summary> /// 属性改变事件 /// </summary> public EventHandler<PropertyChangedEventArgs> DocumentPropertyChangedEvent; /// <summary> /// 加载完成事件 /// </summary> /// <param name="sender"></param> /// <param name="url"></param> private void _Core_LoadCompleted(object sender, LoadCompletedEventArgs url) { if (DocumentCompletedEvent != null) { DocumentCompletedEvent(sender, url); } } /// <summary> /// 属性改变事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void _Core_PropertyChanged(object sender, PropertyChangedEventArgs e) { if (DocumentPropertyChangedEvent != null) { DocumentPropertyChangedEvent(sender, e); } } #endregion #region 属 性 /// <summary> /// 获取浏览器地址 /// </summary> public string Url { get { return this._Url; } } /// <summary> /// tooltip内容 /// </summary> public string TooltipText { get { return this._Core.TooltipText; } set { this._Core.TooltipText = value; } } #endregion } }
主窗口使用了该控件,听音窗口同样使用了该控件。
【异常规律】
在不断测试使用系统中发现,输入法只有在win7下出现异常,在Xp系统下正常。且出现输入法失败,基本都是在打开听音窗口后关闭窗口系统就出现输入法异常。而关闭子窗口调用了自己封装的Dispose()方法,及:
/// <summary> /// 资源释放 /// </summary> public void Dispose() { if(this._Core !=null) { this._Core.Dispose(); } }
去除该代码后。关闭听音窗口调用控件自带Dispose()方法,异常解决。O(∩_∩)O~
至于显示释放资源为什么在win7系统下就出现异常,还需去看看WebView 的Dispose()方法到底释放了什么资源,导致输入法出错,也希望群里面大牛知道的告诉一下。