使用Office互操作获取特定的窗口句柄

时间:2011-12-29 21:33:47

标签: .net office-interop window-handles

我正在使用Office互操作创建一个新的Word实例:

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

我可以得到这样的窗口句柄:

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

问题是代码的工作原理是假设没有其他Word运行实例。如果有多个,则无法保证它返回的句柄是针对我已启动的实例。我从我的对象检测到GetForegroundWindow事件后尝试使用WindowActivate,但这一切都在WPF应用程序中运行,该应用程序设置为作为最顶层的窗口运行,因此我只获取WPF窗口的句柄。有没有其他方法来获取我的单词实例的句柄?

6 个答案:

答案 0 :(得分:6)

不确定为什么需要Word的句柄,但我以前做过的一种方法是实际更改Word窗口标题并搜索它。我这样做是因为我想在控件中托管Word应用程序,但这是另一个故事。 :)

  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() );
    }
  }

获得句柄后,如果愿意,可以恢复标题。

答案 1 :(得分:1)

您已经获得了所有Word进程的列表。您可以遍历此列表,获取每个进程的父ID,并匹配当前进程,即您自己创建Word实例的应用程序。这大致是我的想法:

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;
}

ProcessExtensions类在this excellent response中可用于较早的帖子。我在自己的代码中使用过这个类,没有任何抱怨。

答案 2 :(得分:1)

我将选择的答案保留为正确,因为这是我在写这篇文章时发现的。我之后需要为不同的项目做类似的事情,并发现尝试更新应用程序标题似乎不太可靠(Office 2013与2010年?谁知道......)。这就是我提出的新解决方案,它可以保持窗口字幕不变。

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;

这使用以下自定义比较器来确保进程匹配(找到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();
    }
}

答案 3 :(得分:1)

这个answer解释了如何从hwnd获取Word.Application对象,这意味着我们可以遍历所有活动的Word进程并检查他们的Word.Application是否与我们自己的Word.Application对象匹配。这样,您就不需要对窗口标题执行任何操作。

请注意,您只能获取可见的Word.Application进程并打开一个或多个文档(在后一种情况下,代码会打开一个临时的空文档):

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;
        }
    }
}

我在这个例子中使用NetOffice,但你可以通过编辑using语句并使用Marshal.ReleaseComObject()代替Word.Application.Dispose()来轻松改变它以使用标准的互操作库。

答案 4 :(得分:0)

另一种方法,利用注入的宏直接在WINWORD进程中运行的事实:

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;
        }
    }

}

答案 5 :(得分:0)

自2013年以来,您就可以使用Hwnd公开的Window的{​​{1}}属性

Application

Hwnd返回一个整数,该整数指示指定窗口的窗口句柄。 借助此var windowHandle = wordApplication.ActiveWindow.Hwnd; ,您可以使用int来提供窗口句柄的低级封装。

NativeWindow