外部应用程序窗口移动时移动窗口

时间:2018-02-13 12:53:02

标签: c# winforms winapi pinvoke user32

我有一个总是在顶部的应用程序(基本上是一个状态显示),我想跟随另一个程序,并始终坐在最小化按钮的左侧。

我可以使用以下代码得到代表“目标”过程的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消息的解决方案都很有用。

关于如何进行的任何想法?

2 个答案:

答案 0 :(得分:11)

这个展示了如何将Windows窗体挂钩到另一个进程(在这种情况下是记事本)并遵循进程主窗口的移动,以创建可以与进程交互的工具栏。

使用的主要API函数是SetWinEventHook()

结果的直观表示:

SetWinEventHook sample image

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
}
}