在windows开发界面时,使用浏览器来请求和显示网页内容,是比较常见的。
但是在请求网页内容时,因网速或者前端功能复杂加载较慢,亦或者加载时遇到各种问题,如空白/黑屏/加载不完整/证书问题等。
因此需要一个加载进度/加载失败的显示界面。
加载进度显示
界面显示
1. 界面显示,加载进度样式可参考: 绕圈进度条
2. 添加Loading状态枚举。不加载/加载中/加载失败
1 public enum LoadingState 2 { 3 NotLoading,//正常的网页内容界面 4 Loading, //加载进度显示 5 Error, //加载失败界面 6 }
在控件内添加LoadingState附加属性,前端界面通过绑定此附加属性来确定是否显示,LoadingState的变更时,界面显示则直接变更。
进度显示处理
在封装相应浏览器后,针对三个事件DocumentCompleted、ProgressChanged、NavigateError作进度显示的处理。
结束加载进度
- DocumentCompleted文档加载完成后,结束加载进度。此事件只有Navigate调用后,才会触发
- ProgressChanged进度变更通知,Navigate、Refresh调用后都会触发。因为Navigate调用后,同一进度会重复触发ProgressChanged,ProgressChanged在Navigate调用时触发并没有任何意义,因此在DocumentCompleted之后再添加事件的订阅,ProgressChanged只开放给Refresh方法。
- 当前进度大于0时,立即结束Loading,减少延时。
1 /// <summary> 2 /// 文档加载完成 3 /// </summary> 4 /// <remark>Navigate方法触发,Refresh方法不会触发</remark> 5 /// <remark>在首次加载时,添加DocumentCompleted订阅</remark> 6 /// <param name="sender"></param> 7 /// <param name="e"></param> 8 private void Browser_OnDocumentCompleted(object sender, HtmlDocumentCompletedEventArgs e) 9 { 10 //当前Loading状态不是Error的情况下,才结束加载 11 if (LoadingState == LoadingState.Loading) 12 { 13 LoadingState = LoadingState.NotLoading; 14 } 15 16 //显示网页内容首次加载后,再订阅加载进度事件 17 _browser.ProgressChanged -= Browser_ProgressChanged; 18 _browser.ProgressChanged += Browser_ProgressChanged; 19 OnDocumentCompleted(e); 20 } 21 22 /// <summary> 23 /// 加载进度事件 24 /// </summary> 25 /// <remark>Navigate会触发多次ProgressChanged事件,所以此事件订阅不应开放给Navigate</remark> 26 /// <remark>Refresh调用后,会触发一次</remark> 27 /// <param name="sender"></param> 28 /// <param name="e"></param> 29 private void Browser_ProgressChanged(object sender, WebBrowserProgressChangedEventArgs e) 30 { 31 //当前进度大于0,且当前Loading状态是Loading的情况下,才结束Loading动画 32 if (e.CurrentProgress > 0 && LoadingState == LoadingState.Loading) 33 { 34 LoadingState = LoadingState.NotLoading; 35 } 36 }
值得注意的是,如果按照如上设置,当IE8环境升级到IE11后,因WebBrowserProgressChangedEventArgs事件参数返回异常,不会结束Loading。
原因:调用refresh方法。经调试发现ProgressedChanged的事件参数中,MaximumProgress一直等于0。
但是,正常情况下,同样的电脑环境,win7 IE8或者IE11下,refresh方法,返回的MaximumProgress不为0。
推荐分析:升级后,原有IE版本的注册项遗留,导致冲突。
解决方案:添加e.CurrentProgress == e.MaximumProgress的条件判断。
1 private void Browser_ProgressChanged(object sender, WebBrowserProgressChangedEventArgs e) 2 { 3 //当以下俩种条件符合时,才结束Loading动画 4 //1.当前Loading状态是Loading的情况下 5 //2.当前进度大于0,或者当前进度等于进度上限阀值 6 if (LoadingState == LoadingState.Loading && (e.CurrentProgress > 0 || e.CurrentProgress == e.MaximumProgress)) 7 { 8 LoadingState = LoadingState.NotLoading; 9 } 10 }
PS:微软小组成员推荐使用WebView,然而这个只为Win10的Microsoft Edge开发的控件,只能在win10上运行且只支持.NET4.6.2及以上,限制多多。
加载出错
1 private void Browser_NavigateError(object sender, BrowserExtendedNavigateErrorEventArgs e) 2 { 3 e.Cancel = true; 4 LoadingState = LoadingState.Error; 5 6 //当前不是网络问题的异常,记录异常日志 7 if (e.StatusCode != NavigationErrorHttpStatusCode.INET_E_RESOURCE_NOT_FOUND) 8 { 9 Console.WriteLine($"WebBrowser无法连接服务器:{e.Url},异常信息为:{e.StatusCode},异常Code为:{ (int)e.StatusCode }"); 10 } 11 }
浏览器事件处理
针对如上三个事件,DocumentCompleted、ProgressChanged、NavigateError
winform版IE浏览器
1 /// <summary> 2 /// 在 <see cref="T:System.Windows.Forms.WebBrowser" /> 控件完成加载文档时发生。 3 /// </summary> 4 [SRCategory("CatBehavior")] 5 [SRDescription("WebBrowserDocumentCompletedDescr")] 6 public event WebBrowserDocumentCompletedEventHandler DocumentCompleted;
1 /// <summary> 2 /// 在 <see cref="T:System.Windows.Forms.WebBrowser" /> 控件已更新有关要导航到的文档的下载进度的信息时发生。 3 /// </summary> 4 [SRCategory("CatAction")] 5 [SRDescription("WebBrowserProgressChangedDescr")] 6 public event WebBrowserProgressChangedEventHandler ProgressChanged;
NavigateError加载失败事件,需要重写CreateSink、DetachSink。在对应的cookie中添加额外事件的处理:(在此不详述,这个封装模块本人也不太了解~囧)
1 public void NavigateError(object pDisp, ref object url, ref object frame, ref object statusCode, ref bool cancel) 2 { 3 _browser?.NavigateError?.Invoke(this, new BrowserExtendedNavigateErrorEventArgs((string)url, (string)frame, (int)statusCode, cancel)); 4 }
Cef浏览器
1 private void Register() 2 { 3 _cefBrowser.LoadingStateChanged += CefBrowserOnLoadingStateChanged; 4 _cefBrowser.LoadError += CefBrowserOnLoadError; 5 } 6 private void CefBrowserOnLoadError(object sender, LoadErrorEventArgs args) 7 { 8 // Don't display an error for downloaded files where the user aborted the download. 9 if (args.ErrorCode == CefErrorCode.Aborted) 10 { 11 return; 12 } 13 _isLoadError = true; 14 DispatcherUtil.Invoke(() => 15 { 16 NavigateError?.Invoke(this, new BrowserExtendedNavigateErrorEventArgs(args.FailedUrl, args.Frame.Name, (int)args.ErrorCode, false)); 17 }); 18 } 19 20 private void CefBrowserOnLoadingStateChanged(object sender, LoadingStateChangedEventArgs args) 21 { 22 //isLoading为false,代表LoadingCompleted 23 //_isLoadError为false,代表未加载出错 24 if (!args.IsLoading && !_isLoadError) 25 { 26 DispatcherUtil.Invoke(() => 27 { 28 DocumentCompleted?.Invoke(this, new HtmlDocumentCompletedEventArgs(null)); 29 }); 30 } 31 }