Get a special window handle using Interop

I am creating a new instance of Word using the interaction between Office by doing this:

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

I can get the window handle as follows:

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

The problem is that the code works under the assumption that another instance of Word does not exist. If there are several, he cannot guarantee that the returned handle for the instance that I started. I tried using GetForegroundWindow after detecting the WindowActivate event from my object, but it all works in a WPF application that runs as the topmost window, so I just get a WPF window handle. Are there other ways to get the handle to my instance of the word?

+6
source share
5 answers

Not sure why you need a Word handle, but one of the ways I've done this before is to actually change the title of the Word window and search for it. I did this because I wanted to host the Word application inside the control, but that's a different 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 get the handle, you can restore the signature if you want.

+6
source

You already get a list of all Word processes. You can go through this list, get the parent identifier of each process, and the match is against the current process, that is, your own application that created the Word instance. This is roughly what I mean:

 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 answer for an earlier post. I used this class in my code and did not complain.

+1
source

I will leave the answer that I chose as correct, since that was what I found to work when I wrote it. Since then, I needed to do something similar for another project and found that trying to update the application caption looks less reliable (Office 2013 vs. 2010, who knows ...). Here, the new solution that I came up with leaves the window title uncovered.

 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; 

The following custom mapper is used here to ensure process matching (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(); } } 
+1
source

This answer explains how to get a Word.Application object from hwnd, which means that we can scroll through all the active Word processes and check that their Word.Application matches our own Word.Application object. Thus, you do not need to do anything with the window label.

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

 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 change it to work with standard interop libraries by editing the using statement and making Marshal.ReleaseComObject () instead of Word.Application.Dispose ().

0
source

Another method using the fact that the entered 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; } } } 
0
source

Source: https://habr.com/ru/post/904704/


All Articles