So I made a win app and I want it to pop in front of the screen whenever I open the "Page Setup" from notepad and close it whenever I close the notepad.
I tried this:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("User32", CharSet = CharSet.Auto)]
public static extern int ShowWindow(IntPtr hWnd, int cmdShow);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsIconic(IntPtr hwnd);
[DllImport("user32.dll")]
public static extern int SetForegroundWindow(IntPtr hWnd);
ManagementEventWatcher watcher;
public Form1()
{
InitializeComponent();
var query = new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName = 'Notepad.exe'");
var mew = new ManagementEventWatcher(query) { Query = query };
mew.EventArrived += (sender, args) => { AppStarted(); };
mew.Start();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
watcher = new ManagementEventWatcher("Select * From Win32_ProcessStopTrace");
watcher.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
watcher.Start();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
watcher.Stop();
watcher.Dispose();
base.OnFormClosed(e);
}
void watcher_EventArrived(object sender, EventArrivedEventArgs e)
{
// var _windowHandle = FindWindow(null, "Page Setup");
if ((string)e.NewEvent["ProcessName"] == "notepad.exe")
{
Invoke((MethodInvoker)delegate
{
TopMost = false;
Location = new System.Drawing.Point(1000, 1);
});
}
}
async void AppStarted()
{
await Task.Delay(300);
BeginInvoke(new System.Action(PoPInFront));
}
void PoPInFront()
{
var _notepadProcess = 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)
{
Invoke((MethodInvoker)delegate
{
Location = new System.Drawing.Point(550, 330);
TopMost = true;
});
}
}
//var ExternalApplication = Process.GetProcessesByName("Notepad").FirstOrDefault(p => p.MainWindowTitle.Contains("Page Setup"));
//Location = new System.Drawing.Point(550, 330);
//TopMost = true;
}
Until now my app pops in front of the screen and sets TopMost = true
whenever I open notepad,not the "Page Setup" of the notepad and it moves back to the corner of the screen whenever I close the notepad.
I would like to move the app to the center of the screen and set TopMost = true
whenever the "Page Setup" is opened and back to the corner and TopMost = false
when the "Page Setup" is closed.
Location = new System.Drawing.Point(550, 330);
- for center of the screen
Location = new System.Drawing.Point(1000, 1);
- for corner
I tried also this but no luck.
void PoPInFront()
{
//IntPtr hWnd = IntPtr.Zero;
//foreach (Process pList in Process.GetProcesses())
//{
// if (pList.MainWindowTitle.Contains("Page Setup"))
// {
// hWnd = pList.MainWindowHandle;
// Location = new System.Drawing.Point(550, 330);
// TopMost = true;
// }
//}
var ExternalApplication = Process.GetProcessesByName("Notepad").FirstOrDefault(p => p.MainWindowTitle.Contains("Page Setup"));
Location = new System.Drawing.Point(550, 330);
TopMost = true;
}
I think the problem is here.I don't know how to do something like this:
var query = new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace WHERE ProcessName = 'Notepad.exe' AND MainWindowTitle = 'Page Setup'");
AND MainWindowTitle = 'Page Setup'
You can use either of these options:
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:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With