Подтвердить что ты не робот

Прослушивание другого окна изменения размера событий в С#

Я реализую небольшое приложение (наблюдатель), которое должно "прикрепить" себя к нижней части другого окна (наблюдается). Последнее не является окном внутри приложения.

В этот момент я решил, получив hWnd окна и периодически запрашивая в потоке местоположение наблюдаемого окна, соответствующим образом перемещая окно наблюдателя.

Однако это очень неэлегантное решение. То, что я хотел бы сделать, это прослушать событие изменения размера наблюдаемого окна, чтобы наблюдатель реагировал только тогда, когда это необходимо.

Я предполагаю, что должен использовать крючок, и я нашел много способов сделать это, но моя нехватка знаний о C WinAPI блокирует меня в понимании того, какой крюк мне нужно создать и как (pinvoke/parameters/etc).

Я уверен, что это довольно тривиально, и некоторые из вас, знакомые с C/С++ и WinAPI, получат ответ под рукой;)

Спасибо

4b9b3361

Ответ 1

Расширение ответа Криса Тейлора: вместо того, чтобы выполнять собственный перебор, вы можете использовать ManagedWinApi, который содержит класс Hook.

EDIT: использовать ManagedWinApi. Где-то в вашем коде:

Hook MyHook = new Hook(HookType.WH_CALLWNDPROC, false, false);
MyHook.Callback += MyHookCallback;
MyHook StartHook();

Для обратного вызова ссылка CallWndProc и CWPSTRUCT:

private static int MyHookCallback(int code, IntPtr wParam, IntPtr lParam, ref bool callNext)
{
    if (code >= 0)
    {
        // You will need to define the struct
        var message = (CWPSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPSTRUCT));
        // Do something with the data
    }
    return 0; // Return value is ignored unless you set callNext to false
}

Ответ 2

A WH_CALLWNDPROC hook, вероятно, будет достаточным, это позволит вам отслеживать все сообщения, предназначенные для интересующего окна.

Вы спрашиваете, как создать глобальный крючок с помощью С#, или вы счастливы создать крючок на С++, а затем взаимодействовать с ним с .NET? Второй вариант - маршрут, по которому я поеду.

В основном от верхней части головы, я бы сделал следующее

1- Создайте глобальный крючок в C и экспортируйте функции в InstallHook и UninstallHook, которые можно вызвать из вашего приложения С# с помощью Interop. InstallHook возьмет hwnd окна в вашем приложении С#.

2. Устанавливает ли установленная функция hook сообщение пользовательского сообщения в окно С#, указанное в вызове InstallHook, когда в вашем случае есть сообщение, которое вам интересно, например WM_SIZE.

3- В приложении С# ваше окно, которое получает отправленные сообщения с крючка, переопределит WndProc для обработки настраиваемого сообщения.

Это схема одного подхода.

Ответ 3

Я предлагаю вам использовать WinEvents:

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);

Смотрите также: перехватчики событий

Ответ 4

Я столкнулся с одним и тем же в некотором коде, с которым я работал, и обнаружил, что не мог внедрить управляемый .DLL в этот процесс.

Не желая писать С++ неуправляемую DLL, которая использовала SetWindowsHook, я пошел с комбинацией SetWinEventHook, которая сигнализирует, когда начинается окно и заканчивается событие move, и Timer, чтобы опросить окно, пока оно перемещается, чтобы дать это появление следующего окна во время его перемещения.

Здесь (очень упрощенная) версия класса, которая может это сделать (просто замените TODO на код, чтобы переместить окно или поднять событие).

public class DockingHelper
{
    private readonly uint m_processId, m_threadId;

    private readonly IntPtr m_target;

    // Needed to prevent the GC from sweeping up our callback
    private readonly WinEventDelegate m_winEventDelegate;
    private IntPtr m_hook;

    private Timer m_timer;

    public DockingHelper(string windowName, string className)
    {
        if (windowName == null && className == null) throw new ArgumentException("Either windowName or className must have a value");

        m_target = FindWindow(className, windowName);
        ThrowOnWin32Error("Failed to get target window");

        m_threadId = GetWindowThreadProcessId(m_target, out m_processId);
        ThrowOnWin32Error("Failed to get process id");

        m_winEventDelegate = WhenWindowMoveStartsOrEnds;
    }

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern bool UnhookWinEvent(IntPtr hWinEventHook);

    [DllImport("user32.dll")]
    private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    private void ThrowOnWin32Error(string message)
    {
        int err = Marshal.GetLastWin32Error();
        if (err != 0)
            throw new Win32Exception(err, message);
    }

    private RECT GetWindowLocation()
    {
        RECT loc;
        GetWindowRect(m_target, out loc);
        if (Marshal.GetLastWin32Error() != 0)
        {
            // Do something useful with this to handle if the target window closes, etc.
        }
        return loc;
    }

    public void Subscribe()
    {
        // 10 = window move start, 11 = window move end, 0 = fire out of context
        m_hook = SetWinEventHook(10, 11, m_target, m_winEventDelegate, m_processId, m_threadId, 0);
    }

    private void PollWindowLocation(object state)
    {
        var location = GetWindowLocation();
        // TODO: Reposition your window with the values from location (or fire an event with it attached)
    }

    public void Unsubscribe()
    {
        UnhookWinEvent(m_hook);
    }

    private void WhenWindowMoveStartsOrEnds(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        if (hwnd != m_target) // We only want events from our target window, not other windows owned by the thread.
            return;

        if (eventType == 10) // Starts
        {
            m_timer = new Timer(PollWindowLocation, null, 10, Timeout.Infinite);
            // This is always the original position of the window, so we don't need to do anything, yet.
        }
        else if (eventType == 11)
        {
            m_timer.Dispose();
            m_timer = null;
            var location = GetWindowLocation();
            // TODO: Reposition your window with the values from location (or fire an event with it attached)
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int Left, Top, Right, Bottom;
    }

    private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
}