[转载]IE浏览器整页截屏程序 -Mikel

[转载]IE浏览器整页截屏程序

2012年5月26日 分类: 开发笔记, C#

[转载]IE浏览器整页截屏程序 – 卡卡西村长 – 博客园.

最近项目中涉及到浏览器整页截屏的功能,有点复杂,研究了一天,终于在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)未考虑在多线程环境下的应用。


标签: , , ,
本文的评论功能被关闭了.
备案信息冀ICP 0007948