Detect when a specific window in another process opens or closes
You can use either of these options:
- Using SetWinEventHook method
- Handling UI Automation Events (Preferred) (Suggested by Hans in comments)
Solution 1 - Using SetWinEventHook method
Using SetWinEventHook
you can listen to some events from other processes and register a WinEventProc
callback method to receive the event when the event raised.
Here EVENT_SYSTEM_FOREGROUND
can help us.
We limit the event receiver to receive this event from a specific process and then we check if the text of the window which causes the event is equals to Page Setup
then we can say the Page Setup
window in the target process is open, otherwise we can tell the Page Setup
dialog is not open.
To keep things simple in below example I supposed a notepad
instance is open when your application starts, but you can also use Win32_ProcessStartTrace
to detect when a notepad
application runs.
To be more specific and say when the dialog is closed, you can listen to EVENT_OBJECT_DESTROY
and detect if the message is for the window which we are interested in.
public const uint EVENT_SYSTEM_FOREGROUND = 0x0003;
public const uint EVENT_OBJECT_DESTROY = 0x8001;
public const uint WINEVENT_OUTOFCONTEXT = 0;
public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd,
int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
[DllImport("user32.dll")]
public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
uint idThread, uint dwFlags);
[DllImport("user32.dll")]
public static extern bool UnhookWinEvent(IntPtr hWinEventHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
IntPtr hook = IntPtr.Zero;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var p = System.Diagnostics.Process.GetProcessesByName("notepad").FirstOrDefault();
if (p != null)
hook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND,
IntPtr.Zero, new WinEventDelegate(WinEventProc),
(uint)p.Id, 0, WINEVENT_OUTOFCONTEXT);
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
UnhookWinEvent(hook);
base.OnFormClosing(e);
}
void WinEventProc(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
string s = "Page Setup";
StringBuilder sb = new StringBuilder(s.Length + 1);
GetWindowText(hwnd, sb, sb.Capacity);
if (sb.ToString() == s)
this.Text = "Page Setup is Open";
else
this.Text = "Page Setup is not open";
}
Solution 2 - Handling UI Automation Events
As suggested in comments by Hans, you can use UI Automation APIs to subscribe for WindowOpenedEvent
and WindowClosedEvent
.
In below example I supposed there is an open instance of notepad
and detected opening and closing of its Page Setup
dialog:
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
var notepad = System.Diagnostics.Process.GetProcessesByName("notepad")
.FirstOrDefault();
if (notepad != null)
{
var notepadMainWindow = notepad.MainWindowHandle;
var notepadElement = AutomationElement.FromHandle(notepadMainWindow);
Automation.AddAutomationEventHandler(
WindowPattern.WindowOpenedEvent, notepadElement,
TreeScope.Subtree, (s1, e1) =>
{
var element = s1 as AutomationElement;
if (element.Current.Name == "Page Setup")
{
//Page setup opened.
this.Invoke(new Action(() =>
{
this.Text = "Page Setup Opened";
}));
Automation.AddAutomationEventHandler(
WindowPattern.WindowClosedEvent, element,
TreeScope.Subtree, (s2, e2) =>
{
//Page setup closed.
this.Invoke(new Action(() =>
{
this.Text = "Closed";
}));
});
}
});
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
Automation.RemoveAllEventHandlers();
base.OnFormClosing(e);
}
Don't forget to add reference to UIAutomationClient
and UIAutomationTypes
assemblies and add using System.Windows.Automation;
.
You need to use user32.dll imports for this i would say.
Firstly, in your usings ensure you have:
using System.Runtime.InteropServices;
using System.Linq;
Then, in your class at the top insert this code to import methods from the DLL.
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);
Now, in your own method, the following code should work:
var _notepadProcess = System.Diagnostics.Process.GetProcesses().Where(x => x.ProcessName.ToLower().Contains("notepad")).DefaultIfEmpty(null).FirstOrDefault();
if ( _notepadProcess != null )
{
var _windowHandle = FindWindow(null, "Page Setup");
var _parent = GetParent(_windowHandle);
if ( _parent == _notepadProcess.MainWindowHandle )
{
//We found our Page Setup window, and it belongs to Notepad.exe - yay!
}
}
This should get you started.
***** EDIT ******
Okay so you have got this:
mew.EventArrived += (sender, args) => { AppStarted(); };
This will ensure that the AppStarted() method gets fired when the notepad.exe process has started.
You then wait for 300ms (for some reason?!) and then call PopInFront:
async void AppStarted()
{
await Task.Delay(300);
BeginInvoke(new System.Action(PoPInFront));
}
Inside PopInFront() you attempt to find the "Page Setup" window
var _windowHandle = FindWindow(null, "Page Setup");
However, my query here is: within the 0.3 seconds that have passed, can you safly say that you have been able to open notepad, wait for the GUI to init and navigate to the File -> Page Setup menu in .3 of a second for the next code area to find the window? - My guess is not, what you need here is a loop.
What you should be doing is:
- WMI Query Event Fired
- Start a background worker with a while loop that loops while the process notepad.exe is alive
- In the while loop, keep checking for the Page Setup window
- Once it's found, popup your own dialog, mark another variable to keep track that your dialog is shown
- Once the page setup dialog is no longer shown (FindWindow will return zero), re-mark the variale in stage 4 and allow for the Page Setup window to be found again.