基於.net開發chrome核心瀏覽器【四】


一:

上周去北京出差,給國家電網的項目做架構方案,每天都很晚睡,客戶那邊的副總也這樣拼命工作。

累的不行了,直接導致第四篇文章沒有按時發出來。

希望虛心學習1小俠客等關注我的朋友們原諒我。

二:

在這篇文章中,我們主要實現下面三個功能:

瀏覽器地址欄瀏覽器窗口大小變化瀏覽器下載文件

為了實現這三個功能,我們新創建了一個工程,

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按鈕的時候,切換了瀏覽器的地址。

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

最終的效果如下(瀏覽器窗口的大小會隨着主窗口的大小而變化)

源碼下載:

http://files.cnblogs.com/liulun/CefDemo2.zip


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM