一:
上周去北京出差,給國家電網的項目做架構方案,每天都很晚睡,客戶那邊的副總也這樣拼命工作。
累的不行了,直接導致第四篇文章沒有按時發出來。
二:
在這篇文章中,我們主要實現下面三個功能:
瀏覽器地址欄、瀏覽器窗口大小變化、瀏覽器下載文件
為了實現這三個功能,我們新創建了一個工程,
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