【问题标题】:Get specific window handle using Office interop使用 Office 互操作获取特定窗口句柄
【发布时间】:2012-01-30 05:13:30
【问题描述】:

我正在使用 Office 互操作创建一个新的 Word 实例,方法是这样做:

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

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

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

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

【问题讨论】:

  • 是的,不要那样做。无论你想用那个手柄做什么,肯定有更好的方法。
  • Word 2013 及更高版本具有 Application.Hwnd 属性

标签: .net office-interop window-handles


【解决方案1】:

不确定您为什么需要 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() );
    }
  }

获得句柄后,您可以根据需要恢复字幕。

【讨论】:

  • 是的,为什么我需要这样做也是另一回事。但我认为你的建议不仅让我知道了我要去的地方,而且实际上通过自定义标题使我要去的整个体验变得更好。
  • 同意 Eddie Paz 但有一个变化:你应该检查 if( p.MainWindowTitle.Contains( "My Word" ) ) 因为 word 在开头添加了一些其他字母。
【解决方案2】:

我将保留我选择的正确答案,因为这是我在写这篇文章时发现的。从那以后,我需要为不同的项目做类似的事情,并发现尝试更新应用程序标题似乎不太可靠(Office 2013 vs. 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】:

    您已经获得了所有 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 中可用于较早的帖子。我在自己的代码中使用过这个类,没有任何抱怨。

    【讨论】:

    • 仅供参考,链接的问题有另一个答案,似乎比 ProcessExtensions 更好。
    【解决方案4】:

    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() 轻松更改它以使用标准互操作库。

    【讨论】:

      【解决方案5】:

      从 2013 年开始,您可以使用从 Application 公开的 WindowHwnd 属性

      var windowHandle = wordApplication.ActiveWindow.Hwnd;
      

      Hwnd 返回一个 Integer,表示指定窗口的窗口句柄。 有了这个int,您可以使用NativeWindow,它提供窗口句柄的低级封装。

      var nativeWindow = new NativeWindow();
      nativeWindow.AssignHandle(new IntPtr(windowHandle));
      

      【讨论】:

      • 没有文档打开时会失败。
      • 确保使用 ActiveWINdow 而不是 ActiveDocument
      【解决方案6】:

      另一种方法,利用注入的宏直接在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;
              }
          }
      
      }
      

      【讨论】:

        猜你喜欢
        • 2011-08-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-28
        • 2012-11-22
        • 1970-01-01
        • 2011-02-26
        • 1970-01-01
        相关资源
        最近更新 更多