基于.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(0, 0, 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,
                    0, 0, 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按钮的时候,切换了浏览器的地址。

-------------------------------------------------

最终的效果如下(浏览器窗口的大小会随着主窗口的大小而变化)

源码下载

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术换美食换不换

Hello Qt! 卡诺图开发

9720
来自专栏企鹅号快讯

Pycharm使用技巧总结

pycharm是我经常使用的python IDE,补全功能、代码格式化等非常好用, 最近整理一下经常用的小技巧。 配置灰色主题 菜单栏File Settins...

64370
来自专栏非著名程序员

倍数提高工作效率的 Android Studio 奇技

这是从Philippe Breault的系列文章《Android Studio Tips Of the Day》中提取出来的自认为精华的部分。这些技巧在实际应用...

24890
来自专栏xingoo, 一个梦想做发明家的程序员

重叠(Overlapped)IO模型

基本思想:允许应用程序使用重叠数据结构一次投递一个或者多个异步IO请求。 提交IO请求完成后,与之关联的重叠数据结构中的事件对象受信,应用程序便可使用WSAVe...

21550
来自专栏GIS讲堂

Arcgis Add-In开发入门实例

作为一个本科侧重于应用,工作之后却做了开发的程序员来说,做GIS,开发应该是一门必修课,只是,苦于各种原因吧,做GIS应用的人会开发的很少,做GIS开发的大部分...

26450
来自专栏happyJared

IDEA快捷键拆解系列(十八):Live Templates篇

  首先,我们要知道Live Template是在哪里定义的,先按Ctrl + Shift + S进入设置,接着在输入框输入“Live Template”就可以...

9530
来自专栏逍遥剑客的游戏开发

Nebula3嵌入WPF

16120
来自专栏黄Java的地盘

提高开发效率之VS Code基础配置篇

VS Code可以通过名为代码片段的功能像编辑器中插入一段指定的文本,具体操作步骤为首选项->用户代码片段->新建全局代码片段。

28620
来自专栏微信公众号:Java团长

Intellij IDEA神器那些让人爱不释手的小技巧

之前写了一篇介绍IntellIJ IDEA的文章,主要是列出一些平时大家可能没用过或者没怎么用,但是又非常好用的IntellIJ IDEA小技巧。由于篇幅原因,...

8920
来自专栏逸鹏说道

Jupyter ~ 像写文章般的 Coding

这次选Markdown模式(关于Markdown基础可以看之前写的Markdown Base)

13330

扫码关注云+社区

领取腾讯云代金券