实现当前IE的当前页面截图

由于最近工作比较忙没什么时间写文章,正好今天抽出点时间来写写博客。说实话做测试真是一个无聊的事情,哪怕它很重要但是真的很无聊。昨天英语课上还有人问什么才是Creative Bug, I think nobody find it expect you. This is the creative bug。总之在微软报bug也事件不容易的事情,特别你是flashman. 不过总是来也半年终于在最近的一个月报了11个bug,体验一把真正测试的feeling。废话少说,进入正题:
最近正在忙一个tool的发布准备,这个tool是一个实习生开发的,走后留给我来开发,其实功能比较简单。大家都知道键盘上有一个PrScrn的键,right! 截屏,我们这个工具也是干这个活的。不过呢不是Capture Screen, 而是Capture IE web page。这个工具的产生也主要是因为我们的automation case中需要这个功能,不过现在my boss want to public it to toolbox(a webstie in microsoft to public tool). 大家知道IE页面有的带滚动条有的不带,如果你通过系统的截屏功能那么只能截取到一部分的页面。What a pity! 不过现在网路上也有这样的工具来截整屏了,但是如果你要用在你的Program里,impossible! 所以我们开发了这样一个接口用于完成这个工作。截屏的原理很简单,
1. 获取页面高度和宽度
2. 截取当前页面的显示部分,画到一个graphic上
3. 滚动页面,重复2,直到结束
4. 保存成图片
这些工作我们可以使用.Net中的Bitmap, Graphic, HDC, IO 还有一些API来完成。
            body.setAttribute("scroll""yes"0);
            
int webHeight = (int)body.getAttribute("scrollHeight"0);
            
int webWidth = (int)body.getAttribute("scrollWidth"0);

            
int screenHeight = (int)body.getAttribute("clientHeight"0);
            
int screenWidth =  (int)body.getAttribute("clientWidth"0);
.
.
.
                    g 
= Graphics.FromImage(screenFragment);
                    
//handle to the device context
                    hdc = g.GetHdc();
                    body.setAttribute(
"scrollTop", (screenHeight - 5* i, 0);
                    brwTop 
= (int)body.getAttribute("scrollTop"0);
                    
//copies a visual window into the specified device context(DC)
                    
//hwnd - Handle to the window that will be copied
                    
//hdc - Handle to the device context
                    
//0 - specifies the drawing options.

                    IntPtr hPageWin 
= hCurDisWin;
                    IEAttachAssist.PrintWindow(hPageWin, hdc, 
0);
                    g.ReleaseHdc(hdc);
                    g.Flush();

                    screenFrag 
= Image.FromHbitmap(screenFragment.GetHbitmap());
                    
try
                    
{
                        gTarget.DrawImage(screenFrag, brwLeft, brwTop);
                    }

                    
catch (System.ArgumentNullException ex)
                    
{
                        Console.WriteLine(
"Error occurred in drawImage " + ex.ToString());
                    }

.
.
.
这里是一部分代码片段。
比较麻烦的是,如何获取当前显示的浏览器还要兼容IE6和IE7。因为IE6是没有Tab页面标签的而IE7有。So, how do it?
还有对于html和DTD 声明的html处理的方式也是不一样的,这也是个问题。而我碰到最最麻烦的问题是在IE7里对每一个Tab页面都是一个单独的WebBroswer对象,但是呢,他们的ShellWindow句柄是一样的。如何获取当前显示页面句柄和对应的HtmlDocument?
这两天搞的我是头昏眼花,不过努力没有白费,问题逐一解决。So, next I will explain how to resolve these problem.
first. 兼容IE6和IE7? 这个问题主要是因为IE6的Window handle tree结构和IE7的不同。我通过编写各自的函数来解决,同时你要在架构是解决对象调用的透明性,我使用了工厂模式。
以下是获取IE7当前显示窗口句柄函数:
        /// <summary>
        
/// Get the current visiable window handle in the Tab IE.
        
/// This function is used to support IE7
        
/// </summary>
        
/// <param name="handle">IE7 handle</param>
        
/// <param name="title">The title about the current display window</param>
        
/// <returns>Current visiable window handle</returns>

        static public IntPtr GetCurPageFromTabWindow(IntPtr handle, ref StringBuilder title)
        
{
            
try
            
{
                
// browser handle is emtpy
                if (IntPtr.Zero == handle)
                    
return IntPtr.Zero;
                
                
// Get the first child of the browser as the searching start position.
                IntPtr hwnd = GetWindow(handle, GW_CHILD);
                StringBuilder sb 
= new StringBuilder(STRING_LENGTH);

                
// Search the visiable webpage in the browser.
                while (IntPtr.Zero != hwnd)
                
{
                    
// Search the "TabWindowClass" in the browser.In the tab browser, the "TabWindowClass" 
                    
// handle is the available handle for the tab webpage.
                    GetClassName(hwnd.ToInt32(), sb, STRING_LENGTH);
                    
if (sb.ToString().Contains("TabWindowClass"))
                    
{
                        StringBuilder childsb 
= new StringBuilder(STRING_LENGTH);
                        IntPtr hChildWnd 
= GetWindow(hwnd, GW_CHILD);

                        
// The tab windows in the web browser are a tree structure, 
                        
// the leaf is "Internet Explorer Server" which is a child of 
                        
// "Shell DocObject View"
                        while (IntPtr.Zero != hChildWnd)
                        
{
                            GetClassName(hChildWnd.ToInt32(), childsb, STRING_LENGTH);
                            
if (childsb.ToString().Contains("Shell DocObject View"))
                            
{
                                
// Get the specific webpage window handle
                                IntPtr visWin = FindWindowEx(hChildWnd, IntPtr.Zero, "Internet Explorer_Server", IntPtr.Zero);
                                
                                
// Return the current visiable window handle. There is only one visiable window to user in one
                                
// browser.
                                if (IsWindowVisible(visWin))
                                
{
                                    
// Get the parent title, we will need use it to find the identify Web Browser.
                                    GetWindowText(hwnd, title, STRING_LENGTH);
                                    
return visWin;
                                }

                            }

                            hChildWnd 
= GetWindow(hChildWnd, GW_HWNDNEXT);
                        }

                    }

                    hwnd 
= GetWindow(hwnd, GW_HWNDNEXT);
                }


                
// If there search the visiable window failed, return the empty handle.
                return IntPtr.Zero;
            }

            
catch (Exception e)
            
{
                Console.WriteLine(e.ToString());
                
throw new Exception("Not exist IE7 handle.", e);
            }

        }
second. 对html和DTD什么的html处理?这里我们需要用到mshtml这个名称空间,因为对于html使用IHtmlDocument2接口来处理,对于进行了DTD声明的html要使用IHtmlDocument3来处理。why? 因为在滚动页面的时候要用到ScrollTop这个操作,但是进行了DTD什么的页面该操作需要通过IHtmlDocument3的DocumentElement来实现。so,we need know current document is which type. 我通过比较发现着两种页面的html code 主要区别在于,DTD html 页面最上方多了这么一条
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
如果你认为只要匹配这个字符串,那么你就错了,再看一个页面
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
发现什么没有? Right! 虽然他妈后面使用的html标准不同,但是都使用了W3C DTD声明,我们可以用这个来匹配。
        /// <summary>
        
/// Check the document is html format or xhtml format
        
/// </summary>
        
/// <param name="document"></param>
        
/// <returns></returns>

        static public bool IsDTDDocument(object document)
        
{
            
// XHtml declare flag string
            const string NewW3CStandardDeclare = @"-//W3C//DTD";
            mshtml.IHTMLDocument3 doc 
= (mshtml.IHTMLDocument3)document;
            mshtml.IHTMLDOMChildrenCollection ec 
= (mshtml.IHTMLDOMChildrenCollection)doc.childNodes;
            mshtml.IHTMLDOMNode domNode 
= (mshtml.IHTMLDOMNode)ec.item(0);
            
return domNode.nodeValue.ToString().Contains(NewW3CStandardDeclare);         
        }
third. 最麻烦其实也是最简单的问题,处理入口相同但是页面不同的问题? 先获得当前页面的显示窗体句柄,同时保存title, 那这个title跟捕获的到WebBrowser对象的LocationName比较。这样就可以获取到对应的HtmlDocument对象。

之间要遇到了如何获取当前显示的IE问题.解决方案如下;
 
        /// <summary>
        
/// Get the opened and active IE handle
        
/// </summary>
        
/// <returns>the current active IE handle</returns>

        static public IntPtr GetActiveIEHandle()
        
{
            IntPtr pActiveWin 
= GetProcessMainWindowHandle(@"Windows Internet Explorer");
            SetForegroundWindow(pActiveWin);
            SetFocus(pActiveWin);

            System.Threading.Thread.Sleep(
1000);
            
return GetForegroundWindow();
        }


        
static private IntPtr GetProcessMainWindowHandle(string title)
        
{
            Process[] curProcesses 
= Process.GetProcesses();

            
foreach (Process p in curProcesses)
            
{
                
if (p.MainWindowTitle.Contains(title))
                    
return p.MainWindowHandle;
            }


            
return IntPtr.Zero;
        }

通过这次的开发,确实对IE对网页的处理加深了不少认识。同时我也发现了firefox的缺点,就是很难在程序中去操作firefox。how terrible!
希望这篇文章对大家有帮助。
这里是一个Demo工程,编译环境是.net2.0.
posted @ 2007-11-01 20:01  moonz-wu  阅读(3996)  评论(2编辑  收藏  举报