Получить конкретный дескриптор окна с помощью взаимодействия Office - PullRequest
6 голосов
/ 30 декабря 2011

Я создаю новый экземпляр Word с помощью взаимодействия Office, выполнив следующее:

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

Я могу получить дескриптор окна следующим образом:

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

Проблема в том, что код работает, исходя из предположения, что другой экземпляр Word не запущен. Если их несколько, это не может гарантировать, что дескриптор, который он возвращает, относится к тому экземпляру, который я запустил. Я пытался использовать GetForegroundWindow после обнаружения события WindowActivate из моего объекта, но все это выполняется в приложении WPF, которое настроено для запуска в качестве самого верхнего окна, поэтому я просто получаю дескриптор окна WPF. Есть ли другие способы получить ручку для моего экземпляра слова?

Ответы [ 5 ]

6 голосов
/ 30 декабря 2011

Не знаю, зачем вам нужен дескриптор 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 голос
/ 03 января 2017

Этот ответ объясняет, как получить объект Word.Application из hwnd, что означает, что мы можем циклически пройти через все активные процессы 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 ().

1 голос
/ 09 мая 2014

Я оставлю ответ, который я выбрал, как правильный, так как это было то, что я нашел, чтобы работать, когда я написал это.С тех пор мне нужно было сделать что-то похожее для другого проекта, и я обнаружил, что попытка обновить заголовок приложения кажется менее надежной (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;

Для обеспечения соответствия процессов используется следующий пользовательский компаратор (найдено здесь ).

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 голос
/ 30 декабря 2011

Вы уже получаете список всех процессов Word.Вы можете перебирать этот список, получать родительский идентификатор каждого процесса и сопоставлять его с текущим процессом, т.е. с вашим собственным приложением, создавшим экземпляр 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 доступен в этот превосходный ответ на более раннюю публикацию.Я использовал этот класс в своем собственном коде и не жаловался.

0 голосов
/ 04 января 2017

Другой метод, использующий тот факт, что внедренные макросы запускаются непосредственно внутри процесса 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;
        }
    }

}
...