Get specific window handle using Office interop
Asked Answered
K

6

8

I'm creating a new instance of Word using the Office interop by doing this:

var word = Microsoft.Office.Interop.Word.Application();
word.Visible = true;
word.Activate;

I can get a window handle like this:

var wordHandle = Process.GetProcessesByName("winword")[0].MainWindowHandle;

The problem is that code works on the assumption that there's no other instance of Word running. If there are multiple, it can't guarantee that the handle it returns is for the instance that I've launched. I've tried using GetForegroundWindow after detecting a WindowActivate event from my object but this is all running within a WPF application that's set to run as the topmost window, so I just get the handle to the WPF window. Are there any other ways to get the handle for my instance of word?

Kanazawa answered 29/12, 2011 at 21:33 Comment(2)
Yes, don't do that. Whatever you want to do with that handle, surely there's a better way.Comintern
Word 2013 and later has an Application.Hwnd propertyAisha
M
7

Not sure why you need the handle to Word, but one way I've done this before is to actually change the Word window caption and search for it. I did this because I wanted to host the Word application inside a control, but that's another story. :)

  var word = new Microsoft.Office.Interop.Word.Application(); 
  word.Visible = true; 
  word.Activate();
  word.Application.Caption = "My Word";

  foreach( Process p in Process.GetProcessesByName( "winword" ) )
  {
    if( p.MainWindowTitle == "My Word" )
    {
      Debug.WriteLine( p.Handle.ToString() );
    }
  }

Once you got the handle, you can restore the caption if you like.

Modestamodeste answered 29/12, 2011 at 21:56 Comment(2)
Yeah, why I need to do this is another story as well. But I think your suggestion not only gets me where I'm going but actually makes the whole experience I'm going for a little nicer with the custom caption.Kanazawa
Agree with Eddie Paz but with one change: you shoud check if( p.MainWindowTitle.Contains( "My Word" ) ) because word adds some other letters to the beginnig of that.Oration
K
3

I'll leave the answer I've selected as correct, since it was what I found to work back when I wrote this. I've since needed to do do something similar for a different project and found that trying to update the application caption seemed to be less reliable (Office 2013 vs. 2010? Who knows...). Here's the new solution I've come up with that leaves window captions intact.

var startingProcesses = Process.GetProcessesByName("winword").ToList();

var word = new Microsoft.Office.Interop.Word.Application(); 

var allProcesses = Process.GetProcessesByName("winword").ToList();

var processDiff = allProcesses.Except(startingProcesses, new ProcessComparer());

var handle = processDiff.First().MainWindowHandle;

This uses the following custom comparer to ensure the processes match (found here).

class ProcessComparer : IEqualityComparer<Process>
{
    public bool Equals(Process x, Process y)
    {
        if (ReferenceEquals(x, y))
        {
            return true;
        }

        if (x == null || y == null)
        {
            return false;
        }

        return x.Id.Equals(y.Id);
    }

    public int GetHashCode(Process obj)
    {
        return obj.Id.GetHashCode();
    }
}
Kanazawa answered 8/5, 2014 at 21:39 Comment(0)
C
2

Since word 2013 you can use the Hwnd property of Window that is exposed from the Application

var windowHandle = wordApplication.ActiveWindow.Hwnd;

The Hwnd returns an Integer that indicates the window handle of the specified window. With this int you can use the NativeWindow that provides a low-level encapsulation of a window handle.

var nativeWindow = new NativeWindow();
nativeWindow.AssignHandle(new IntPtr(windowHandle));
Complaisance answered 2/12, 2019 at 20:24 Comment(2)
This fails when no document is open.Berkley
Make sure to use ActiveWIndow and not ActiveDocumentComplaisance
V
1

You are already getting a list of all Word processes. You can iterate through this list, get the parent ID of each process and match is against the current process i.e. your own application that created a Word instance. This is roughly what I have in mind:

IntPtr getChildProcess(string childProcessName)
{
    var currentProcess = Process.GetCurrentProcess();

    var wordProcesses = Process.GetProcessesByName(childProcessName);
    foreach (var childProcess in wordProcesses)
    {
        var parentProcess = ProcessExtensions.Parent(childProcess);
        if (currentProcess.Id == parentProcess.Id)
            return currentProcess.Handle;
    }

    return IntPtr.Zero;
}

The ProcessExtensions class is available in this excellent response to an earlier post. I have used this class in my own code and have had no complaints.

Vaporimeter answered 29/12, 2011 at 22:33 Comment(1)
FYI the linked question has another answer that appears to be a better option than ProcessExtensions.Flatways
A
1

This answer explains how to get the Word.Application object from a hwnd, which means we can loop through all active Word processes and check if their Word.Application matches our own Word.Application object. This way, you don't need to do anything with the window caption.

Please note that you can only obtain the process of a Word.Application that is visible and has one or more documents open (the code opens a temporary empty document in the latter case):

using System;
using System.Linq;
using System.Text;
using Word = NetOffice.WordApi;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Diagnostics;

namespace WordHwnd
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var app = new Word.Application() { Visible = true })
            {
                Console.WriteLine(WordGetter.GetProcess(app).MainWindowHandle);
            }

            Console.ReadLine();
        }
    }

    class WordGetter
    {
        [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00020400-0000-0000-C000-000000000046")]
        private interface IDispatch
        {
        }

        private const uint OBJID_NATIVEOM = 0xFFFFFFF0;
        private static Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");

        [DllImport("Oleacc.dll")]
        private static extern int AccessibleObjectFromWindow(int hwnd, uint dwObjectID, byte[] riid, out IDispatch ptr);

        private delegate bool EnumChildCallback(int hwnd, ref int lParam);

        [DllImport("User32.dll")]
        private static extern bool EnumChildWindows(int hWndParent, EnumChildCallback lpEnumFunc, ref int lParam);

        [DllImport("User32.dll")]
        private static extern int GetClassName(int hWnd, StringBuilder lpClassName, int nMaxCount);

        private static bool Find_WwG(int hwndChild, ref int lParam)
        {
            if (GetClassName(hwndChild) == "_WwG")
            {
                lParam = hwndChild;
                return false;
            }
            return true;
        }

        private static string GetClassName(int hwndChild)
        {
            var buf = new StringBuilder(128);
            GetClassName(hwndChild, buf, 128);
            return buf.ToString();
        }

        public static Process GetProcess(Word.Application app)
        {
            Word.Document tempDoc = null;

            //This only works if there is a document open
            if (app.Documents.Count == 0)
                tempDoc = app.Documents.Add();

            var processes = Process.GetProcessesByName("WINWORD");

            var appsAndProcesses = processes
                .Select(p => new { Process = p, App = WordGetter.GetWordApp(p) })
                .Where(x => !Equals(x.App, null));

            Process process = null;

            foreach (var appAndProcess in appsAndProcesses)
            {
                if (appAndProcess.App == app)
                {
                    process = appAndProcess.Process;
                    break;
                }
                else
                {
                    appAndProcess.App.Dispose();
                }
            }

            tempDoc?.Close(false);

            return process;
        }

        public static Word.Application GetWordApp(Process process)
        {
            return GetWordApp(process.MainWindowHandle);
        }

        public static Word.Application GetWordApp(IntPtr hwnd)
        {
            return GetWordApp((int)hwnd);
        }

        public static Word.Application GetWordApp(int hwnd)
        {
            var wwG_Hwnd = 0;

            var callback = new EnumChildCallback(Find_WwG);

            EnumChildWindows(hwnd, callback, ref wwG_Hwnd);

            if (wwG_Hwnd != 0)
            {
                IDispatch iDispatch;

                var result = AccessibleObjectFromWindow(wwG_Hwnd, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), out iDispatch);

                if (result >= 0)
                {
                    var obj = iDispatch.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, iDispatch, null);

                    return new Word.Application(null, obj);
                }

                return null;
            }

            return null;
        }
    }
}

I use NetOffice in this example, but you can easily alter it to work with the standard interop libraries by editing the using statement and doing Marshal.ReleaseComObject() instead of Word.Application.Dispose().

Aisha answered 2/1, 2017 at 23:31 Comment(0)
A
0

Another method, making use of the fact that injected macros are run directly inside the WINWORD process:

using System;
using Word = NetOffice.WordApi;
using System.Diagnostics;

namespace WordHwnd
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var app = new Word.Application() { Visible = true })
            {
                var process = GetProcess(app);
                Console.WriteLine(process.MainWindowHandle);

                app.Quit();

            }

            Console.ReadLine();
        }

        private static Process GetProcess(Word.Application app)
        {
            var tempDocument = app.Documents.Add();
            var project = tempDocument.VBProject;
            var component = project.VBComponents.Add(NetOffice.VBIDEApi.Enums.vbext_ComponentType.vbext_ct_StdModule);
            var codeModule = component.CodeModule;
            codeModule.AddFromString("#If Win64 Then\r\n   Declare PtrSafe Function GetCurrentProcessId Lib \"kernel32\" () As Long\r\n#Else\r\n   Declare Function GetCurrentProcessId Lib \"kernel32\" () As Long\r\n#End If");

            var result = app.Run("GetCurrentProcessId");

            var process = Process.GetProcessById((int)result);

            tempDocument.Close(false);

            return process;
        }
    }

}
Aisha answered 4/1, 2017 at 11:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.