我有一个总是在顶部的应用程序(基本上是一个状态显示),我想跟随另一个程序,并始终坐在最小化按钮的左侧。
我可以使用以下代码得到代表“目标”过程的Rect
,然后我可以将其与偏移量配对以生成叠加层的初始位置。
获取HWnd IntPtr:
private IntPtr HWnd = Process.GetProcessesByName("targetapplication")[0].MainWindowHandle;
从user32.dll
声明函数:
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
适当的struct
:
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
然后按需调用它。
但是,我想避免不断轮询这个值,所以我想挂钩目标应用程序并在目标窗口移动时做出响应。
查看user32.dll
文档,我能看到的唯一方法是使用SetWindowsHookEx()
。我不完全确定如何从这里拦截一个事件。
我认为目标应用程序是基于WinForms
构建的,但我无法确定。因此,让我响应目标的Move
事件或直接响应某些Windows消息的解决方案都很有用。
关于如何进行的任何想法?
答案 0 :(得分:11)
这个展示了如何将Windows窗体挂钩到另一个进程(在这种情况下是记事本)并遵循进程主窗口的移动,以创建可以与进程交互的工具栏。
使用的主要API函数是SetWinEventHook()
结果的直观表示:
Form类初始化过程:
extension UIColor{
func greenRedProgress(percent: Int) -> UIColor{
let modVal = CGFloat((Double(percent).truncatingRemainder(dividingBy: 50) / 50) * 255)
if percent <= 0{
return UIColor(red: 1.0, green: 0, blue: 0, alpha: 1)
}else if percent >= 100{
return UIColor(red: 0, green: 1.0, blue: 0, alpha: 1)
}else{
switch percent{
case 1..<50: return UIColor(red: 1.0, green: (modVal/255), blue: 0, alpha: 1)
case 51..<100: return UIColor(red: (255 - modVal)/255, green: 1.0, blue: 0, alpha: 1)
case 50: return UIColor(red: 1.0, green: 1.0, blue: 0, alpha: 1)
default: return UIColor(red: 0, green: 1.0, blue: 0, alpha: 1)
}
}
}}
用于引用Windows API方法的支持类:
public partial class Form1 : Form
{
private IntPtr NotepadhWnd;
private IntPtr hWinEventHook;
private Process Target;
private RECT rect = new RECT();
protected Hook.WinEventDelegate WinEventDelegate;
static GCHandle GCSafetyHandle;
public Form1()
{
InitializeComponent();
WinEventDelegate = new Hook.WinEventDelegate(WinEventCallback);
GCSafetyHandle = GCHandle.Alloc(WinEventDelegate);
try
{
Target = Process.GetProcessesByName("notepad").FirstOrDefault(p => p != null);
if (Target != null)
{
NotepadhWnd = Target.MainWindowHandle;
uint TargetThreadId = Hook.GetWindowThread(NotepadhWnd);
if (NotepadhWnd != IntPtr.Zero)
{
hWinEventHook = Hook.WinEventHookOne(Hook.SWEH_Events.EVENT_OBJECT_LOCATIONCHANGE,
WinEventDelegate,
(uint)Target.Id,
TargetThreadId);
rect = Hook.GetWindowRect(NotepadhWnd);
this.Location = new Point(rect.Right, rect.Top);
}
}
}
catch (Exception ex)
{
//ErrorManager.Logger(this, this.InitializeComponent(), ex.HResult, ex.Data, DateTime.Now);
throw ex;
}
}
protected void WinEventCallback(IntPtr hWinEventHook,
Hook.SWEH_Events eventType,
IntPtr hWnd,
Hook.SWEH_ObjectId idObject,
long idChild,
uint dwEventThread,
uint dwmsEventTime)
{
if (hWnd == NotepadhWnd &&
eventType == Hook.SWEH_Events.EVENT_OBJECT_LOCATIONCHANGE &&
idObject == (Hook.SWEH_ObjectId)Hook.SWEH_CHILDID_SELF)
{
rect = Hook.GetWindowRect(hWnd);
this.Location = new Point(rect.Right, rect.Top);
}
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
GCSafetyHandle.Free();
Hook.WinEventUnhook(hWinEventHook);
}
private void Form1_Shown(object sender, EventArgs e)
{
if (Target == null)
{
this.Hide();
MessageBox.Show("Notepad not found!", "Target Missing", MessageBoxButtons.OK, MessageBoxIcon.Hand);
this.Close();
}
else
{
this.Size = new Size(50, 140);
}
}
答案 1 :(得分:1)
感谢@Jimi在这里的帮助。以下方法有效。
首先,存储对目标流程的引用:
Process _target = Process.GetProcessesByName("target")[0];
然后获取主窗口的句柄:
IntPtr _tagetHWnd = _target.MainWindowHandle;
然后初始化钩子:
SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, IntPtr.Zero, TargetMoved, (uint)_foxview.Id,
GetWindowThreadProcessId(_foxview.MainWindowHandle, IntPtr.Zero), WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS | WINEVENT_SKIPOWNTHREAD);
SetWinEventHook
声明为:
[DllImport("user32.dll")]
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
所涉及的常数是:
private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
private const int HT_CAPTION = 0x2;
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
private const uint WINEVENT_SKIPOWNPROCESS = 0x0002;
private const uint WINEVENT_SKIPOWNTHREAD = 0x0001;
private const int WM_NCLBUTTONDOWN = 0xA1;
然后在我的TargetMoved
方法中,我会检查新的窗口位置并移动我的叠加层。
private void TargetMoved(IntPtr hWinEventHook, uint eventType, IntPtr lParam, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
Rect newLocation = new Rect();
GetWindowRect(_foxViewHWnd, ref newLocation);
Location = new Point(newLocation.Right - (250 + _currentUser.Length * 7), newLocation.Top + 5);
}
GetWindowRect()
定义于:
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, ref Rect lpRect);
Rect
定义为:
[StructLayout(LayoutKind.Sequential)]
private struct Rect
{
public readonly int Left;
public readonly int Top;
public readonly int Right;
public readonly int Bottom;
}
所以当你把它们放在一起时,整个班级现在看起来像这样:
使用System; 使用System.Diagnostics; 使用System.Drawing; 使用System.Runtime.InteropServices; 使用System.Windows.Forms; 使用UserMonitor;
namespace OnScreenOverlay
{
public partial class Overlay : Form
{
#region Public Fields
public const string UserCache = @"redacted";
#endregion Public Fields
#region Private Fields
private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
private const uint WINEVENT_SKIPOWNPROCESS = 0x0002;
private const uint WINEVENT_SKIPOWNTHREAD = 0x0001;
private readonly Process _foxview;
private readonly IntPtr _foxViewHWnd;
private readonly UserMon _monitor;
private string _currentUser;
#endregion Private Fields
#region Public Constructors
public Overlay()
{
InitializeComponent();
_target= Process.GetProcessesByName("target")[0];
if (_foxview == null)
{
MessageBox.Show("No target detected... Closing");
Close();
}
_targetHWnd = _target.MainWindowHandle;
InitializeWinHook();
StartPosition = FormStartPosition.Manual;
Location = new Point(Screen.PrimaryScreen.Bounds.Left + 20, Screen.PrimaryScreen.Bounds.Bottom - 20);
ShowInTaskbar = false;
_monitor = new UserMon(UserCache);
_monitor.UserChanged += (s, a) =>
{
_currentUser = a.Value;
if (pictBox.InvokeRequired)
{
pictBox.Invoke((MethodInvoker)delegate { pictBox.Refresh(); });
}
};
_currentUser = _monitor.GetUser();
}
#endregion Public Constructors
#region Private Delegates
private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
#endregion Private Delegates
#region Private Methods
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, ref Rect lpRect);
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
[DllImport("user32.dll")]
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
uint idThread, uint dwFlags);
private void InitializeWinHook()
{
SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, IntPtr.Zero, TargetMoved, (uint)_foxview.Id,
GetWindowThreadProcessId(_foxview.MainWindowHandle, IntPtr.Zero), WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS | WINEVENT_SKIPOWNTHREAD);
}
private void Overlay_FormClosing(object sender, FormClosingEventArgs e)
{
_monitor.Dispose();
}
private void pictBox_Paint(object sender, PaintEventArgs e)
{
using (Font myFont = new Font("Arial", 8))
{
e.Graphics.DrawString($"User: {_currentUser}", myFont, Brushes.LimeGreen, new Point(2, 2));
}
}
private void TargetMoved(IntPtr hWinEventHook, uint eventType, IntPtr lParam, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
Rect newLocation = new Rect();
GetWindowRect(_foxViewHWnd, ref newLocation);
Location = new Point(newLocation.Right - (250 + _currentUser.Length * 7), newLocation.Top + 5);
}
#endregion Private Methods
#region Private Structs
[StructLayout(LayoutKind.Sequential)]
private struct Rect
{
public readonly int Left;
public readonly int Top;
public readonly int Right;
public readonly int Bottom;
}
#endregion Private Structs
}
}