最近項目中涉及到瀏覽器整頁截屏的功能,有點復雜,研究了一天,終於在IE瀏覽器下實現,至於其他瀏覽器,以后再研究。
所謂整頁截屏,就是說把整個頁面全部截進去,包括通過滾動才能看到的部分。
在網上搜了一下,大家用的都是同一種辦法:通過滾動頁面,截取每一屏的圖片,然后再合並成一張整的圖片。
方法是好的,悲催的是,沒有一個代碼是能正常運行的,相信很多人都有同感!沒辦法,自己動手,豐衣足食。
我需要用.NET來實現。分析一下,主要有以下幾個技術點:
1、如何取得瀏覽器對象。首先要確定IE版本,我用的是IE8瀏覽器,對象結構和IE6、IE7有點區別。這個可以通過Win32API中的FindWindow函數來實現。
2、對指定控件截屏。這個可以通過Win32API中的PrintWindow函數來實現,這個函數有一個優點:即使瀏覽器被其它窗口擋住,也可以正常截屏。但是,如果瀏覽器窗口最小化了,那就漆黑一片了。。。
3、合並圖片。這個用GDI+可以很方便地實現。在內存中創建一個大的畫布,然后將圖片從上至下依次畫上去即可。
開始動手。
首先,創建一個Console應用程序(用Form程序也沒關系)。
(1)添加對System.Drawing和System.Windows.Forms的引用。
(2)添加對兩個COM組件的引用:SHDocVw、MSHTML,並設置屬性“嵌入互操作類型”為False(這個很重要,否則無法接下來的程序無法編譯通過)。
(3)將程序入口Main方法標記為[STAThread](這個也很重要,否則接下來的程序會運行失敗)。
然后,加入Win32API類,該類對幾個重要的API進行了封裝,接下來我們會用到這些API。代碼如下:
using System; using System.Runtime.InteropServices; namespace IECapture { /// <summary> /// Win32API封裝類。 /// </summary> internal static class Win32API { //User32 [DllImport("User32.dll")] public static extern int FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle); [DllImport("user32.dll")] public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect); [DllImport("user32.dll")] public static extern IntPtr GetWindowDC(IntPtr hWnd); [DllImport("User32.dll")] public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, int nFlags); //gdi32 [DllImport("gdi32.dll")] public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop); [DllImport("gdi32.dll")] public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, int nHeight); [DllImport("gdi32.dll")] public static extern IntPtr CreateCompatibleDC(IntPtr hDC); [DllImport("gdi32.dll")] public static extern bool DeleteDC(IntPtr hDC); [DllImport("gdi32.dll")] public static extern bool DeleteObject(IntPtr hObject); [DllImport("gdi32.dll")] public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject); public struct RECT { public int left; public int top; public int right; public int bottom; } } }
最后,加入主程序。代碼邏輯如下:
(1)獲取當前IE瀏覽器對象。
(2)獲取瀏覽器核心控件的矩形區域,計算整個頁面一共有多少屏。
(3)通過滾動窗口的方式,對每一屏的頁面進行截屏。
(4)將所有圖片合並為一張整的圖片。
主程序的源代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Drawing; using System.Windows.Forms; namespace IECapture { class Program { //必須指定COM線程模型為單線程 [STAThread] static void Main(string[] args) { //獲取瀏覽器對象 SHDocVw.ShellWindows shellWindows = new SHDocVw.ShellWindowsClass(); var webBrowser = shellWindows.Cast<SHDocVw.WebBrowser>().FirstOrDefault(c => c.Name == "Windows Internet Explorer"); if (webBrowser == null) { Console.WriteLine("當前未打開任何IE瀏覽器"); Console.ReadLine(); return; } //查找瀏覽器核心控件 IntPtr childHandle1 = Win32API.FindWindowEx(new IntPtr(webBrowser.HWND), IntPtr.Zero, "Frame Tab", IntPtr.Zero); IntPtr childHandle2 = Win32API.FindWindowEx(childHandle1, IntPtr.Zero, "TabWindowClass", IntPtr.Zero); IntPtr childHandle3 = Win32API.FindWindowEx(childHandle2, IntPtr.Zero, "Shell DocObject View", IntPtr.Zero); IntPtr childHandle4 = Win32API.FindWindowEx(childHandle3, IntPtr.Zero, "Internet Explorer_Server", IntPtr.Zero); if (childHandle4 == IntPtr.Zero) { Console.WriteLine("當前未打開任何IE瀏覽器"); Console.ReadLine(); return; } //獲取瀏覽器核心控件的矩形區域 Win32API.RECT rc = new Win32API.RECT(); Win32API.GetWindowRect(childHandle4, ref rc); int pageHeight = rc.bottom - rc.top; //獲取HTML文檔對象 mshtml.IHTMLDocument2 htmlDoc = (mshtml.IHTMLDocument2)webBrowser.Document; //計算總共有多少頁,以及最后一頁的高度 int pageCount = htmlDoc.body.offsetHeight / pageHeight; int lastPageHeight = htmlDoc.body.offsetHeight % pageHeight; if (lastPageHeight > 0) pageCount++; int scrollBarWidth = pageCount > 1 ? SystemInformation.VerticalScrollBarWidth : 0; //圖片列表,用於放置每一屏的截圖 List<Image> pageImages = new List<Image>(); //一頁一頁地滾動截圖 htmlDoc.parentWindow.scrollTo(0, 0); for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) { using (Image image = CaptureWindow(childHandle4)) { //去掉邊框,以及垂直滾動條的寬度 Rectangle innerRect = new Rectangle(2, 2, image.Width - scrollBarWidth - 4, image.Height - 4); if (pageCount > 1 && pageIndex == pageCount - 1 && lastPageHeight > 0) innerRect = new Rectangle(2, pageHeight - lastPageHeight + 2, image.Width - scrollBarWidth - 4, lastPageHeight - 4); pageImages.Add(GetImageByRect(image, innerRect)); } htmlDoc.parentWindow.scrollBy(0, pageHeight); } //拼接所有圖片 Image fullImage = MergeImages(pageImages); if (fullImage == null) { Console.WriteLine("截屏失敗,未獲得任何圖片"); Console.ReadLine(); return; } //將截屏圖片保存到指定目錄 try { string fileName = @"c:\IE整屏截圖.png"; fullImage.Save(fileName); Console.WriteLine("截屏成功,圖片位置:" + fileName); } finally { fullImage.Dispose(); } Console.ReadLine(); } /// <summary> /// 合並圖片。 /// </summary> /// <param name="imageList">圖片列表。</param> /// <returns>合並后的圖片。</returns> static Image MergeImages(List<Image> imageList) { if (imageList == null || imageList.Count == 0) return null; if (imageList.Count == 1) return imageList[0]; int pageWidth = imageList[0].Width; int totalPageHeight = imageList.Sum(c => c.Height); Bitmap fullImage = new Bitmap(pageWidth, totalPageHeight); using (Graphics g = Graphics.FromImage(fullImage)) { int y = 0; for (int i = 0; i < imageList.Count; i++) { g.DrawImageUnscaled(imageList[i], 0, y, imageList[i].Width, imageList[i].Height); y += imageList[i].Height; imageList[i].Dispose();//釋放圖片資源 } } return fullImage; } /// <summary> /// 獲取圖片的指定區域。 /// </summary> /// <param name="image">原始圖片。</param> /// <param name="rect">目標區域。</param> /// <returns></returns> static Image GetImageByRect(Image image, Rectangle rect) { if (image == null || rect.IsEmpty) return image; Bitmap bitmap = new Bitmap(rect.Width, rect.Height); using (Graphics g = Graphics.FromImage(bitmap)) { g.DrawImage(image, 0, 0, rect, GraphicsUnit.Pixel); } return bitmap; } /// <summary> /// 為指定窗口或控件截屏。 /// </summary> /// <param name="hWnd">句柄。</param> /// <returns>截屏圖片。</returns> static Image CaptureWindow(IntPtr hWnd) { IntPtr hscrdc = Win32API.GetWindowDC(hWnd); if (hscrdc == IntPtr.Zero) return null; Win32API.RECT windowRect = new Win32API.RECT(); Win32API.GetWindowRect(hWnd, ref windowRect); int width = windowRect.right - windowRect.left; int height = windowRect.bottom - windowRect.top; IntPtr hbitmap = Win32API.CreateCompatibleBitmap(hscrdc, width, height); IntPtr hmemdc = Win32API.CreateCompatibleDC(hscrdc); Win32API.SelectObject(hmemdc, hbitmap); Win32API.PrintWindow(hWnd, hmemdc, 0); Image bmp = Image.FromHbitmap(hbitmap); Win32API.DeleteDC(hscrdc); Win32API.DeleteDC(hmemdc); return bmp; } } }
【總結】
要想寫一個好的整頁截屏程序,還是很困難的。就拿本文的程序來說,就存在以下幾點不足之處:
(1)僅在IE8瀏覽器上測試通過,無法在FireFox、Chrome上運行。即使同是IE家族的IE6、IE7、IE9,我也不敢保證能正常運行,各位同學可以測試一下,並嘗試修改,歡迎交流。
(2)如果有浮動DIV隨着頁面一起滾動,在每一屏上都會被截屏。
(3)未考慮水平滾動條的影響。
(4)未考慮在多線程環境下的應用。
最后附上一個cnblogs.com首頁的截屏,看看效果:)