Восстановление из системного трея, изначально не принадлежащего окну
TL;DR; В конце концов
Я пытаюсь сделать крошечный планировщик для своей жены, чтобы автоматизировать некоторые задачи, и у меня возникают некоторые проблемы с взаимодействием с другим приложением, когда оно свернуто в системный трей.
До тех пор, пока я запускаю другое приложение в своей программе с помощью
System.Diagnostics.Process.Start(Path\App)
Я могу достать MainWindowHandle
и тогда мне все равно, где он будет позже, я могу взаимодействовать с ним, используя ранее сохраненный дескриптор без каких-либо проблем.Другое приложение-MDI, но я не хочу иметь более одного экземпляра одновременно, чтобы избежать проблем (например, запись Блокнота в разных экземплярах в один и тот же файл), поэтому я начинаю свои проверки с:
Process[] processes = Process.GetProcessesByName("AppFriendlyName"); if (processes.Length == 0) { Process.Start(@"Path\App.exe"); } else { if (processes.Length > 1) { for (int i = 1; i < processes.Length; i++) { processes[i].CloseMainWindow()) processes[i].Close(); } } } // I have the [DllImport("user32.dll")] and the needed Signatures. IntPtr wndIntPtr = User32.FindWindow(null, "Caption"); User32.ShowWindow(parentIntPtr, User32.ShowWindowEnum.ShowNormal); User32.SetForegroundWindow(wndIntPtr);
Моя проблема в том, что:
Если они (возможные несколько экземпляров другого приложения) были использованы, а затем свернуты в системный трей, название окна может быть изменено, поэтому я не могу быть на 100% уверен, какую строку я должен дать, чтобы найти окно.
Что я уже пробовал:
Чтобы решить проблему с динамическим заголовком, я нацелился на дочернюю кнопку, которая всегда находится там и является неизменяемой, а затем я могу передать дескриптор родительскому элементу с помощью
GetAncestor
от по WinAPI.чНо проблема сохраняется, когда она свернута в системный трей.
Вернувшийся
Process.MainWindowHandle
это 0x00000000, если в SystemTray.Я использовал Spy++ и вижу, что дескрипторы вообще не меняются (и это не так
IntPtr.Zero
как сказано в Processes[]
), независимо от того, сколько раз я сворачиваю в системный трей и восстанавливаю вручную. Они просто серые, когда находятся в systemtray. Введя обрабатывать вручную, так как тест в:IntPtr wndIntPtr = (IntPtr) 0x000D0216; User32.ShowWindow(parentIntPtr, User32.ShowWindowEnum.ShowNormal); User32.SetForegroundWindow(wndIntPtr);или
с помощью другого метода от ребенка:
IntPtr parentIntPtr = User32.GetAncestor((IntPtr)0x000C0556, User32.GetAncestorFlags.GetParent);
Тогда он работает как шарм, я могу восстановить и продолжать использовать их, как я хочу.
Поэтому я начал искать альтернативы:
Я уже пробовал
FindWindowExA
тоже но пока мне этого сделать не удалось IntPtr childIntPtr = User32.FindWindowExA(IntPtr.Zero, IntPtr.Zero, null, "Help");верните что-нибудь полезное.
В spy++ процесс содержит два потока, и один из них управляет окнами,
[+]Process ID AppFriendlyName [+] Thread ID AppFriendlyName [] Window Handle1 Caption1 #32770 (DialogField) [] Window Handle2 Caption2 Button [] Window Handle3 Help Button <<<< Child for the workaround above [] Window Handle4 Caption4 Button ...
Я подумал, что, возможно, если я получу ThreadsCollection, то позже смогу получить список "управляемых" окон... но нет, это не работает так. Я не могу получить никакой полезной информации для моего дела, как только у меня есть нити.
Я заглянул в него.
ProcessModule
но я не нашел ничего полезного для своей проблемы.Я нашел информацию MSDN для
EnumThreadWindows
но пока не мог проверить это насквозь.Я знаю, что это возможно, так и должно быть. Но я изо всех сил пытаюсь найти правильную функцию, чтобы получить то, что я хочу (или, может быть, FindWindowExA правильный, но я использую его неправильно)
TL;DR;
1) если я открываю другую программу, то сохраняю дескриптор в самом начале и никаких проблем вообще нет
2) если другая программа (независимо от того, сколько экземпляров) не изменила заголовок главного окна, я все равно могу найти их и взаимодействовать с ними
3) Если другая программа изменила название и свернута в трей... вот где я борюсь и нуждаюсь в помощи
Какой-нибудь намек? На каком методе я должен сосредоточиться / искать?
Что я попробую сделать дальше:
Я полагаю, что лучший вариант, который у меня есть, - это использовать
System.Diagnostics.Process
чтобы получить первый список, работайте с ним до тех пор, пока не останутся только интенции, дающие мне проблемы. Затем Process.Threads
чтобы получить все потоки каждого процесса. А потом ... EnumThreadWindows
чтобы найти все не дочерние окна и повторить итерацию с помощью EnumChildWindows
чтобы попытаться найти мою неизменяемую кнопку "XXX - Help", чтобы разорвать все петли, сохраняя активные ручки для продолжения работы.Является ли это правильным подходом? Или я чересчур его переоцениваю?
Nelek
На всякий случай это не совсем понятно в вопросе. Свернутый в лоток-это нет мое приложение-это то, что я хочу найти и показать для взаимодействия.
honey the codewitch
I think you should try your next approach. It's what I would have suggested. Keep in mind you're trying to interact on some level with an app that wasn't designed to interact with your app so anything you do is going to be somewhat ugly. Windows has crappy window finding APIs - that's the bottom line. Usually an app will be designed for interaction and they will publish an API into the Running Object Table, obviating the need for window based interaction. Even if they relied on windowing for IPC the server app would publish its window in some way known to the client so the client knew how to get to it later. You don't have luxury. Given all that, you're on the right track.
Nelek
Спасибо за подтверждение, по крайней мере, я знаю, что это не неправильное направление.
Решение уродливо на всех уровнях (зацепление клавиш и щелчков мыши с помощью Win32), но это единственный способ, который я мог придумать, нет API, и я не нашел никакой другой возможности взаимодействовать с ним (я даже спрашивал их, но ничего).
honey the codewitch
Самое лучшее, на что вы можете надеяться, - это сделать из этого фасад и выставить для него API, чтобы вы не загрязняли больше своего кода, чем это абсолютно необходимо.
Nelek
Не уверен, что я понял вашу точку зрения, извините.
Моя идея состоит в том, чтобы иметь класс, который обрабатывает все вещи Win32, еще один с необходимой логикой для обработки правильных клавиш / щелчков и небольшим графическим интерфейсом для выбора необходимых задач. (То есть временные метки, чтобы начать что-то, функциональность сна и еще несколько вещей). Я знаю риски, связанные с "работой" с моделированием клавиш / мыши, но поскольку это только для частного локального использования, я думаю, что стоит попробовать (кроме того... Я тоже учусь в этом процессе ;))
honey the codewitch
Вы создаете именно то, о чем я говорю. Не беспокойся об этом. =)
Я имел в виду, что лучше всего иметь все хаки за чем-то настолько "чистым", насколько вы можете создать, но похоже, что вы делаете это.
Nelek
По крайней мере, я на это надеюсь.
Не уверен, что мне следует собрать все вместе и написать что-то, когда я закончу, большинство частей на самом деле являются вещами, которые можно найти в сети, но нет места со всей информацией сразу.
honey the codewitch
Я должен добавить, что EnumWindows() и его производные не дают вам условия остановки. Они будут продолжать звать вас обратно с окнами, пока их больше не будет, но вы не узнаете, когда их больше не будет, потому что они никогда не скажут вам. Я устанавливаю тайм-аут для этого.
Nelek
Я уже читал о функции обратного вызова. Я не знаю, правильно ли я его понял, но во время исполнения он доставляет IntPtr hWnd
, поэтому я могу попытаться проверить, что это такое, и изменить возврат на false сам, если текущий дескриптор принадлежит кнопке, которую я ищу (игнорируя остальные окна, которые еще не перечислены).
Затем получите его родителя, поработайте с ним, закройте его и начните все сначала, если в списке GetProcess есть еще другие экземпляры.