[英]Detect when a specific window in another process opens or closes
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. 所以我做了一个win应用程序,每当我打开记事本中的“页面设置”并且每当我关闭记事本时关闭它,我都希望它在屏幕前弹出。
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. 到目前为止,我的应用程序弹出屏幕前,每当我打开记事本时设置TopMost = true
,而不是记事本的“页面设置”,每当我关闭记事本时它都会移回屏幕的一角。
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. 我想应用程序移动到屏幕的中心,并设置TopMost = true
每当“页面设置”打开,回到角落, TopMost = false
当“页面设置”关闭。
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 解决方案1 - 使用SetWinEventHook方法
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. 使用SetWinEventHook
您可以侦听来自其他进程的某些事件 ,并注册WinEventProc
回调方法,以便在引发事件时接收事件。
Here EVENT_SYSTEM_FOREGROUND
can help us. 这里EVENT_SYSTEM_FOREGROUND
可以帮助我们。
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. 我们限制事件接收器从特定进程接收此事件,然后我们检查导致事件的窗口文本是否等于Page Setup
然后我们可以说目标进程中的Page Setup
窗口是打开的,否则我们可以告诉Page Setup
对话框未打开。
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. 为了简化下面的示例,我假设应用程序启动时打开了一个notepad
实例,但您也可以使用Win32_ProcessStartTrace
来检测notepad
应用程序何时运行。
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. 更具体地说,当对话框关闭时,您可以收听EVENT_OBJECT_DESTROY
并检测该消息是否适用于我们感兴趣的窗口。
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 解决方案2 - 处理UI自动化事件
As suggested in comments by Hans, you can use UI Automation APIs to subscribe for WindowOpenedEvent
and WindowClosedEvent
. 正如Hans的评论中所建议的那样,您可以使用UI Automation API订阅WindowOpenedEvent
和WindowClosedEvent
。
In below example I supposed there is an open instance of notepad
and detected opening and closing of its Page Setup
dialog: 在下面的示例中,我假设有一个打开的notepad
实例,并检测到其Page Setup
对话框的打开和关闭:
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;
不要忘记添加对UIAutomationClient
和UIAutomationTypes
程序集的引用,并using System.Windows.Automation;
添加using System.Windows.Automation;
. 。
You need to use user32.dll imports for this i would say. 你需要使用user32.dll导入。
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. 然后,在顶部的类中插入此代码以从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. 这将确保在notepad.exe进程启动时触发AppStarted()方法。
You then wait for 300ms (for some reason?!) and then call PopInFront: 然后等待300毫秒(由于某种原因?!)然后调用PopInFront:
async void AppStarted()
{
await Task.Delay(300);
BeginInvoke(new System.Action(PoPInFront));
}
Inside PopInFront() you attempt to find the "Page Setup" window 在PopInFront()内部,您尝试查找“页面设置”窗口
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? 但是,我的查询是:在0.3秒内,你可以安全地说你已经能够打开记事本,等待GUI初始化并导航到文件 - >页面设置菜单.3秒为下一个代码区找到窗口? - My guess is not, what you need here is a loop. - 我的猜测不是,你需要的是一个循环。
What you should be doing is: 你应该做的是:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.