Archive for September 2009

Programmatically Taking a Full Web Page Screenshot

I needed to programmatically take a screenshot of a full web page, and not just the visible area. When I set out to accomplish this task, I didn’t realize how tough it was actually going to be.

The reason it was tough wasn’t because it was hard to do, but that there were many different ways of doing it, and each had different results. Some ways would only capture the visible screen. Some would be missing backgrounds. Some wouldn’t capture pages that loaded with JavaScript, such as google.com.

I was able to find a way that has worked on every site I’ve tested so far that is a combination of many of the other methods I’ve found. This way also does not require any assembly outside of the System namespace.

The code is pretty small and self explanatory, so here is how I did it.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class ScreenShot
{
    [ComImport]
    [InterfaceType( ComInterfaceType.InterfaceIsIUnknown )]
    [Guid( "0000010d-0000-0000-C000-000000000046" )]
    private interface IViewObject
    {
        [PreserveSig]
        int Draw( [In] [MarshalAs( UnmanagedType.U4 )] int dwDrawAspect, int lindex, IntPtr pvAspect,
                  [In] /*tagDVTARGETDEVICE*/ IntPtr ptd, IntPtr hdcTargetDev, IntPtr hdcDraw,
                  [In] /*COMRECT*/ Rectangle lprcBounds, [In] /*COMRECT*/ IntPtr lprcWBounds, IntPtr pfnContinue,
                  [In] int dwContinue );

        [PreserveSig]
        int GetColorSet( [In] [MarshalAs( UnmanagedType.U4 )] int dwDrawAspect, int lindex, IntPtr pvAspect,
                         [In] /*tagDVTARGETDEVICE*/ IntPtr ptd, IntPtr hicTargetDev, [Out] /*tagLOGPALETTE*/ IntPtr ppColorSet );

        [PreserveSig]
        int Freeze( [In] [MarshalAs( UnmanagedType.U4 )] int dwDrawAspect, int lindex, IntPtr pvAspect, [Out] IntPtr pdwFreeze );

        [PreserveSig]
        int Unfreeze( [In] [MarshalAs( UnmanagedType.U4 )] int dwFreeze );

        void SetAdvise( [In] [MarshalAs( UnmanagedType.U4 )] int aspects, [In] [MarshalAs( UnmanagedType.U4 )] int advf,
                        [In] [MarshalAs( UnmanagedType.Interface )] /*IAdviseSink*/ IntPtr pAdvSink );

        void GetAdvise( [In] [Out] [MarshalAs( UnmanagedType.LPArray )] int[] paspects,
                        [In] [Out] [MarshalAs( UnmanagedType.LPArray )] int[] advf,
                        [In] [Out] [MarshalAs( UnmanagedType.LPArray )] /*IAdviseSink[]*/ IntPtr[] pAdvSink );
    }

    public static Bitmap Create( string url )
    {
        using( var webBrowser = new WebBrowser() )
        {
            webBrowser.ScrollBarsEnabled = false;
            webBrowser.ScriptErrorsSuppressed = true;
            webBrowser.Navigate( url );

            while( webBrowser.ReadyState != WebBrowserReadyState.Complete )
            {
                Application.DoEvents();
            }

            webBrowser.Width = webBrowser.Document.Body.ScrollRectangle.Width;
            webBrowser.Height = webBrowser.Document.Body.ScrollRectangle.Height;

            var bitmap = new Bitmap( webBrowser.Width, webBrowser.Height );
            var graphics = Graphics.FromImage( bitmap );
            var hdc = graphics.GetHdc();

            var rect = new Rectangle( 0, 0, webBrowser.Width, webBrowser.Height );

            var viewObject = (IViewObject)webBrowser.Document.DomDocument;
            viewObject.Draw( 1, -1, (IntPtr)0, (IntPtr)0, (IntPtr)0, hdc, rect, (IntPtr)0, (IntPtr)0, 0 );

            graphics.ReleaseHdc( hdc );

            return bitmap;
        }
    }
}

From here, it’s pretty simple to start adding features like min/max width/height, a capture delay for pages that have Flash on them, etc.

Note: For Flash to work properly, you need to set the project type to x86. If running on a 64-bit system and the project is set to “Any CPU”, Flash won’t work because it’s not compatible with 64-bit systems.

Dynamically Loading Partial Views with the Spark View Engine

Loading a partial view is easy with Spark. There are basically two ways:

<use file="MyPartialView" />

And if you have name your files using the _MyPartialView.spark convention:

<MyPartialView />

Now what if you don’t know the name of the partial file until runtime and need to dynamically render the view? As far as I know, there is no way of doing this in Spark. How you can get around this is by doing some inline code using ASP.NET MVCs RenderPartial method.

#Html.RenderPartial( myPartialViewVariable );

This is assuming myPartialViewVariable is a string variable with the name of the view that needs to be rendered.

Update:

When you use ASP.NET’s Html.RenderPartial, your data isn’t automatically passed along to the spark view. To pass your data along, use the ViewData dictionary, like you would in a controller.

<ul>
    <li each="var item in items">
        #ViewData["item"] = item;
        #Html.RenderPartial( "path/to/" + item.Name );
    </li>
</ul>

In the partial view, use the <viewdata /> spark attribute like you normally would in a spark file.

<viewdata item="Item" />