基于.net开发chrome核心浏览器【四】
一:
上周去北京出差,给国家电网的项目做架构方案,每天都很晚睡,客户那边的副总也这样拼命工作。
累的不行了,直接导致第四篇文章没有按时发出来。
二:
在这篇文章中,我们主要实现下面三个功能:
浏览器地址栏、浏览器窗口大小变化、浏览器下载文件
为了实现这三个功能,我们新创建了一个工程,
program.cs文件里的内容没有任何变动;
dll文件夹里的内容没有任何变动;
资源的引用,程序集的配置,都没有做任何变动;
三:
我们在解决方案中创建一个bs文件夹,这个文件夹中放置与浏览器相关的类。
首先在这个文件夹中创建一个叫BsDownloadHandler的类
顾名思义,这个类是为下载文件而创建的。
如果想实现下载文件,首先要让这个类继承CefDownloadHandler
然后重写父类的OnBeforeDownload和OnDownloadUpdated两个方法
重写的代码如下:
protected override void OnBeforeDownload(CefBrowser browser, CefDownloadItem downloadItem, string suggestedName, CefBeforeDownloadCallback callback)
{
callback.Continue(string.Empty, true);
}
protected override void OnDownloadUpdated(CefBrowser browser, CefDownloadItem downloadItem, CefDownloadItemCallback callback)
{
if (downloadItem.IsComplete)
{
MessageBox.Show("下载成功");
if (browser.IsPopup && !browser.HasDocument)
{
browser.GetHost().ParentWindowWillClose();
browser.GetHost().CloseBrowser();
}
}
}
OnBeforeDownload方法,在浏览器开始下载文件之前被调用,CEF在默认情况下屏蔽了所有文件下载的事件
如果希望CEF处理下载事件,那么就要调用callback参数的Continue事件。
suggested_name参数是下载文件的建议名称,也就是保存文件对话框出来之后,文件名称文本框里的内容。
--------------------------
OnDownloadUpdated方法,CEF在下载文件过程中会多次调用此方法,并不是只有完成下载了之后再调用这个方法。
如果你想终止下载,也可以调用callback参数的Cancel方法
--------------------------
downloadItem是描述下载文件的类型的实例,
他里面有一系列的属性,
包括:是否完成、是否取消、当前速度、完成的百分比、完成了多少比特、一共有多少比特、开始时间、结束时间等等
不要试图在上面两个方法之外,引用这个实例。
----------------------------
因为浏览器下载文件有很多方式,有可能是通过window.open(js)的方式打开一个路径来下载文件
这时,我们要关掉被打开的窗口,(CEF不会自动帮我们关掉这类窗口)所以在获知下载完成之后,又执行了如下两句代码:
if (browser.IsPopup && !browser.HasDocument)
{
browser.GetHost().ParentWindowWillClose();
browser.GetHost().CloseBrowser();
}
四:
为了能使浏览器的窗口大小,随着容器的窗口大小变化而变化。
我们必须要知道,浏览器的窗口句柄何时创建成功,何时被加入到父窗口中去了。
在上一篇文章中,我们提到CefBrowserHost.CreateBrowser方法是异步的。
我们要想一些办法,来获取这个方法执行成功后,所创建的浏览器窗口的句柄。
首先,我们要在bs文件夹内创建一个名为BsLifeSpanHandler的类
这个类继承自CefLifeSpanHandler,我们在这个类中重写了OnAfterCreated方法
整个类的代码如下:
public class BsLifeSpanHandler : CefLifeSpanHandler
{
private BsClient bClient;
public BsLifeSpanHandler(BsClient bc)
{
bClient = bc;
}
protected override void OnAfterCreated(Xilium.CefGlue.CefBrowser browser)
{
base.OnAfterCreated(browser);
bClient.Created(browser);
}
}
创建这个类的实例,必须传入一个BsClient的实例,关于BsClient的内容,我们稍后再讲
基类CefLifeSpanHandler中有很多方法可供重载。
包括:弹窗之前的事件、浏览器窗口创建成功后的事件、执行模态窗的事件、关闭窗口之前的事件
(虽然这里叫事件,但其实是方法,只不过CEF会自动调用这些方法)
我们在这个类中重写了OnAfterCreated方法(浏览器窗口创建成功后的事件),
在这个方法中,我们调用了BsClient实例的Created方法,
并且把browser实例当作参数传递给了这个方法
这里的browser其实就是我们创建出来的浏览器核心,可以通过它获取浏览器的窗口句柄。
五:
我们在上一篇文章中,创建了一个BrowserClient类,这个类继承自CefClient。
但我们并没有给这个类任何实现,只是在调用CefBrowserHost.CreateBrowser方法时,传递了这个类的一个实例
现在,我们把这个类放到bs文件夹中去,并改名为BsClient,为这个类增加如下实现代码:
public class BsClient:CefClient
{
public event EventHandler OnCreated;
private readonly CefLifeSpanHandler lifeSpanHandler;
private readonly CefDownloadHandler downloadHandler;
public BsClient()
{
lifeSpanHandler = new BsLifeSpanHandler(this);
downloadHandler = new BsDownloadHandler();
}
protected override CefLifeSpanHandler GetLifeSpanHandler()
{
return lifeSpanHandler;
}
protected override CefDownloadHandler GetDownloadHandler()
{
return downloadHandler;
}
public void Created(CefBrowser bs)
{
if (OnCreated != null)
{
OnCreated(bs, EventArgs.Empty);
}
}
}
在这个类中,我们重写了GetLifeSpanHandler和GetDownloadHandler方法,
这样,我们前面创建的BsLifeSpanHandler和BsDownloadHandler才会物尽其用。
还可以在这里重写很多方法,
比如:GetContextMenuHandler(右键菜单句柄),GetDialogHandler(对话框句柄)GetKeyboardHandler(键盘句柄)等
在这个类中我们还公开了一个OnCreated的事件,Created方法会调用这个事件。
而这个方法,是在BsLifeSpanHandler类中被调用的。
也就是说,当浏览器创建完成时,OnCreated事件会被触发。
六:
我们在bs文件夹下创建一个类BsCtl
这个类就是我们的浏览器控件,
先来看一下这个类唯一的构造函数:
public BsCtl(Control ctl)
{
parent = ctl;
var cwi = CefWindowInfo.Create();
cwi.SetAsChild(parent.Handle, new CefRectangle(, , parent.Width, parent.Height));
var bc = new BsClient();
bc.OnCreated += bc_OnCreated;
var bs = new CefBrowserSettings() { };
CefBrowserHost.CreateBrowser(cwi, bc, bs, "http://www.cnblogs.com/liulun");
parent.SizeChanged += parent_SizeChanged;
}
你会发现,上一篇文章中的几行核心代码,都搬到这里来了。
构造函数的参数ctl,是一个windows控件,一般是个panel之类的容器控件,
我们创建的浏览器窗口就将呈现在这个容器控件内
同时,我们为这个容器控件注册了SizeChanged事件
也为BsClient注册了OnCreated事件。
我们在创建默认浏览器的时候,指定了它的默认主页(就是我的博客)
你如果不想这么办,你可以指定它为:about:blank
----------------------------
下面我们看一下这两个事件的实现代码
void bc_OnCreated(object sender, EventArgs e)
{
bs = (CefBrowser)sender;
var handle = bs.GetHost().GetWindowHandle();
ResizeWindow(handle,parent.Width,parent.Height);
} void parent_SizeChanged(object sender, EventArgs e)
{
if (bs != null)
{
var handle = bs.GetHost().GetWindowHandle();
ResizeWindow(handle, parent.Width, parent.Height);
}
}
在浏览器创建成功的事件中,我们把浏览器的实例保存成了私有属性
他是一个核心对象,以后有很多地方会用到。
我们通过bs.GetHost().GetWindowHandle();来获得浏览器窗口的句柄。
有了这个句柄,我们就可以重置浏览器窗口的大小,使他随着主窗体的大小变化而变化
--------------------------------
下面来看一下ResizeWindow方法的代码:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
private void ResizeWindow(IntPtr handle, int width, int height)
{
if (handle != IntPtr.Zero)
{
NativeMethod.SetWindowPos(handle, IntPtr.Zero,
, , width, height,
0x0002 | 0x0004
);
}
}
在ResizeWindow方法中,通过PInvoke方式调用了windows api的方法,来设置浏览器窗口的位置和大小
其中0x0002相当于SWP_NOMOVE;0x0004相当于SWP_NOZORDER
---------------------------
在这个类中,还有一个方法,代码如下:
public void LoadUrl(string url)
{
bs.GetMainFrame().LoadUrl(url);
}
这个方法负责切换浏览器的地址
七:
现在我们来设计主窗口

一些必要的布局代码如下,就不多做解释了:
this.AddressContainer.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
this.GoBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.AddressTB.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
this.BrowserContainer.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
this.StatusContainer.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
------------------------------------------
经过前面的一系列工作,我们主窗口的代码就精简了很多
public partial class Demo : Form
{
BsCtl bs;
public Demo()
{
InitializeComponent();
this.Name = "CefBrowser";
this.Text = "最简单的实现";
}
private void CefBrowser_Load(object sender, EventArgs e)
{
bs = new BsCtl(BrowserContainer);
} private void GoBtn_Click(object sender, EventArgs e)
{
if (!string.IsNullOrWhiteSpace(AddressTB.Text))
{
bs.LoadUrl(AddressTB.Text);
}
}
}
在窗口加载成功后,我们创建了浏览器的实例(创建时把浏览器容器传递给了构造函数),
当点击GO按钮的时候,切换了浏览器的地址。
-------------------------------------------------
最终的效果如下(浏览器窗口的大小会随着主窗口的大小而变化)

源码下载:
http://files.cnblogs.com/liulun/CefDemo2.zip
基于.net开发chrome核心浏览器【四】的更多相关文章
- 基于.net开发chrome核心浏览器【七】
这是一个系列的文章,前面六篇文章的地址如下: 基于.net开发chrome核心浏览器[六] 基于.net开发chrome核心浏览器[五] 基于.net开发chrome核心浏览器[四] 基于.net开发 ...
- 基于.net开发chrome核心浏览器
本文转载自:http://www.cnblogs.com/liulun/archive/2013/04/20/3031502.html 一: 上一篇的链接: 基于.net开发chrome核心浏览器[一 ...
- 基于.net开发chrome核心浏览器【三】
原文:基于.net开发chrome核心浏览器[三] 本篇我们讲解怎么用CefGlue开发一个最简单的浏览器 一: CefGlue是建立在Cef项目之上的,Cef项目是C/C++的项目:CefGlue只 ...
- 基于.net开发chrome核心浏览器【二】
原文:基于.net开发chrome核心浏览器[二] 一: 上一篇的链接: 基于.net开发chrome核心浏览器[一] 二: 相关资源介绍: chrome Frame: 让IE有一颗chrome的心, ...
- 基于.net开发chrome核心浏览器【一】
原文:基于.net开发chrome核心浏览器[一] 说明: 这是本系列的第一篇文章,我会尽快发后续的文章. 源起 1.加快葬送IE6浏览器的进程 世界上使用IE6浏览器最多的地方在中国 中国使用IE6 ...
- 基于.net开发chrome核心浏览器【五】
一:本篇将解决的问题 本章主要为了解决一下几个问题: 1.JsDialog的按钮错位的问题 我们开发出的浏览器,在有些操系统上调用alert,confirm之类的对话框时,确定和取消按钮会出现错位的情 ...
- 基于.net开发chrome核心浏览器【一】(转)
http://www.cnblogs.com/liulun/archive/2013/03/18/2874276.html 说明: 这是本系列的第一篇文章,我会尽快发后续的文章. 源起 1.加快葬送I ...
- 基于.net开发chrome核心浏览器【六】
写在前面: 距离发这个系列的上一篇文章已经过去两个多月了 因为工作上不涉及这一部分的内容,兼且琐事缠身,一直无力动笔写这个系列的第六篇文章 然而,有很多朋友都关注这个系列,希望我能再写写. 写文章有人 ...
- 黄聪:C# 开发Chrome内核浏览器(WebKit.net)
WebKit.net是对WebKit的.Net封装,使用它.net程序可以非常方便的集成和使用webkit作为加载网页的容器.这里介绍一下怎么用它来显示一个网页这样的一个最简单的功能. 第一步: 下载 ...
随机推荐
- 14.8.1 Enabling File Formats
14.8 InnoDB File-Format Management 14.8.1 Enabling File Formats 14.8.2 Verifying File Format Compati ...
- 做SEO推广必须要做的9件事儿
SEO推广是由网站优化网络运营媒体宣传结合的一种技术,而现在恰好就是媒体最为流行,真因为如此很多的站长之知道利用自媒体推广网站,结果推广了几年网站权重只有2到3而已,导致和谐问题的关键就是没有结合其他 ...
- 积累的VC编程小技巧之列表框
1.列表框中标题栏(Column)的添加 创建一个List Control,其ID为IDC_LIST,在其Styles属性项下的View项里选择Report.Align项里选择Top.Sort项里选择 ...
- Spring MVC 数据验证——validate注解方式
1.说明 学习注解方式之前,应该先学习一下编码方式的spring注入.这样便于理解验证框架的工作原理.在出错的时候,也能更好的解决这个问题.所以本次博客教程也是基于编码方式.仅仅是在原来的基础加上注解 ...
- QS Network(最小生成树)
题意:若两个QS之间要想连网,除了它们间网线的费用外,两者都要买适配器, 求使所有的QS都能连网的最小费用. 分析:这个除了边的权值外,顶点也有权值,因此要想求最小价值,必须算边及顶点的权值和. 解决 ...
- hdu 3488 Tour
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3488 题意:给你一个N个顶点M条边的带权有向图,要你把该图分成1个或多个不相交的有向环.且所有定点都只 ...
- Eclipse插件引入jar包的方法
搞了两天,终于找到解决办法了.原来 Eclipse 插件项目引入外面的jar包不能用 build path---->add external jars的方法. 先说明两个概念:类加载器,O ...
- JEECG开源团队招募新成员 2014年
JEECG开源团队招募新成员 2014年 截止日期:2014-06-01 JEECG开源项目 是一款基于代码生成器的微云高速开发平台.提供企业高速开发和採用微信实现移动应用的解决方式.J ...
- 小猪的Android入门之路 day 1
小猪的Android入门之路 Day 1 Android相关背景与开发环境的搭建 ------转载请注明出处:coder-pig 本节引言: 随着社会经济的发展,移动互联网的越来越热,手机APP开发显 ...
- Python编程中常用的12种基础知识总结
原地址:http://blog.jobbole.com/48541/ Python编程中常用的12种基础知识总结:正则表达式替换,遍历目录方法,列表按列排序.去重,字典排序,字典.列表.字符串互转,时 ...