Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I control optical zoom in a running "Internet Explorer_Server" instance?

Windows Live Writer hosts an Internet Explorer control for editing, but there's no zoom control. I'd like to be able to send a zoom command to it. Since other apps host the IE browser, I'd think a utility that can send zoom commands to specific IE browser instances would be really handy, but I haven't found one.

I've seen there's a command, OLECMDID_OPTICAL_ZOOM, but I'm not sure how to send that command to it. Ideally I'd like to do this from C# or Powershell.

Note: The question is asking about controlling the zoom in a web browser control in a running application which I did not create, the main example being the editor surface in Windows Live Writer.

like image 524
Jon Galloway Avatar asked May 26 '11 22:05

Jon Galloway


1 Answers

Short version: I'm not entirely sure you can do what you're attempting to do.

I have some code that actually gets a handle on the HTML document inside the WLW window, and that works, but what I'm finding is that I can't actually get a reference to the parent window of the document. I'm no native Windows guy, but I've done some PInvoke in my time. It may be just that my lack of native Windows knowledge is stopping me from bridging that last gap.

From what I've put together, the general process for getting a reference to an IE window is:

  1. Get a reference for the top-level window.
  2. Find the window containing the HTML document in there (recursive search).
  3. Get the HTML document object from that window.
  4. Get the parent window of the HTML document (which is the IE window).

Once you have that parent window, you can call the IWebBrowser2.ExecWB method to run your OLE zoom command.

The problem is that the IHTMLDocument2.parentWindow property always seems to throw an InvalidCastException when you try to access it. From what I've read, that's the deal when you try to grab the parent window from a thread other than the one the document is running on.

So, anyway, I'll drop the code on you that gets the HTML document reference and if you can bridge that tiny last step of the way, you'll have your answer. I just couldn't figure it out myself.

This is a console app. You'll need a reference to Microsoft.mshtml for the IHTMLDocument2 interface.

using System;
using System.Runtime.InteropServices;

namespace ControlInternetExplorerServer
{
    ////////////////////////
    // LOTS OF PINVOKE STUFF
    ////////////////////////

    [Flags]
    public enum SendMessageTimeoutFlags : uint
    {
        SMTO_NORMAL = 0x0,
        SMTO_BLOCK = 0x1,
        SMTO_ABORTIFHUNG = 0x2,
        SMTO_NOTIMEOUTIFNOTHUNG = 0x8
    }

    public static class NativeMethods
    {
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        public static extern IntPtr FindWindow(
            string lpClassName,
            string lpWindowName);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(
            IntPtr hwndParent,
            IntPtr hwndChildAfter,
            string lpszClass,
            string lpszWindow);

        [DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "GetWindow", SetLastError = true)]
        public static extern IntPtr GetNextWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.U4)] int wFlag);

        [DllImport("oleacc.dll", PreserveSig = false)]
        [return: MarshalAs(UnmanagedType.Interface)]
        public static extern object ObjectFromLresult(
            IntPtr lResult,
            [MarshalAs(UnmanagedType.LPStruct)] Guid refiid,
            IntPtr wParam);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern uint RegisterWindowMessage(string lpString);

        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        public static extern IntPtr SendMessageTimeout(
            IntPtr windowHandle,
            uint Msg,
            IntPtr wParam,
            IntPtr lParam,
            SendMessageTimeoutFlags flags,
            uint timeout,
            out IntPtr result);

        public static IntPtr FindWindowRecursive(IntPtr parent, string windowClass, string windowCaption)
        {
            var found = FindWindowEx(parent, IntPtr.Zero, windowClass, windowCaption);
            if (found != IntPtr.Zero)
            {
                return found;
            }

            var child = FindWindowEx(parent, IntPtr.Zero, null, null);
            while (child != IntPtr.Zero)
            {
                found = FindWindowRecursive(child, windowClass, windowCaption);
                if (found != IntPtr.Zero)
                {
                    return found;
                }
                child = GetNextWindow(child, 2);
            }
            return IntPtr.Zero;
        }
    }


    //////////////////////
    // THE INTERESTING BIT
    //////////////////////

    public class Program
    {
        public static void Main(string[] args)
        {
            // First parameter is the class name of the window type - retrieved from Spy++
            // Second parameter is the title of the window, which you'll
            // probably want to take in via command line args or something.
            var wlwWindow = NativeMethods.FindWindow("WindowsForms10.Window.8.app.0.33c0d9d", "Untitled - Windows Live Writer");
            if (wlwWindow == IntPtr.Zero)
            {
                Console.WriteLine("Unable to locate WLW window.");
                return;
            }

            // Since you don't know where in the tree it is, you have to recursively
            // search for the IE window. This will find the first one it comes to;
            // ostensibly there's only one, right? RIGHT?
            var ieWindow = NativeMethods.FindWindowRecursive(wlwWindow, "Internet Explorer_Server", null);
            if (ieWindow == IntPtr.Zero)
            {
                Console.WriteLine("Unable to locate IE window.");
                return;
            }

            // Get a handle on the document inside the IE window.
            IntPtr smResult;
            var message = NativeMethods.RegisterWindowMessage("WM_HTML_GETOBJECT");
            NativeMethods.SendMessageTimeout(ieWindow, message, IntPtr.Zero, IntPtr.Zero, SendMessageTimeoutFlags.SMTO_ABORTIFHUNG, 1000, out smResult);
            if (smResult== IntPtr.Zero)
            {
                Console.WriteLine("Unable to locate the HTML document object.");
            }

            // Cast the document to the appropriate interface.
            var htmlDoc = (mshtml.IHTMLDocument2)NativeMethods.ObjectFromLresult(smResult, typeof(mshtml.IHTMLDocument2).GUID, IntPtr.Zero);

            // Here's where you would normally get htmlDoc.parentWindow and call ExecWB
            // to execute the zoom operation, but htmlDoc.parentWindow throws an InvalidCastException.
        }
    }
}
like image 77
2 revs Avatar answered Oct 30 '22 17:10

2 revs