Listening to another window resize events in C#

后端 未结 4 685
Happy的楠姐
Happy的楠姐 2020-12-04 20:01

I am implementing a small application (observer) that needs to \"attach\" itself to the bottom of another window (observed). The latter is not a window inside the applicatio

相关标签:
4条回答
  • 2020-12-04 20:23

    Expanding on Chris Taylor's answer: Instead of doing the native interop yourself, you can use ManagedWinApi, which contains a Hook class.

    EDIT: To use ManagedWinApi. Somewhere in your code:

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

    For the callback, reference CallWndProc and 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
    }
    
    0 讨论(0)
  • 2020-12-04 20:24

    I ran into the same thing in some code I was working with and discovered I couldn't inject a managed .DLL into the process.

    Not wanting to write a C++ unmanaged DLL that used SetWindowsHook, I went with a combination of SetWinEventHook, which signals when a window starts and ends a move event and a Timer to poll the window while it's moving to give it the appearance of following the window while it moves.

    Here's a (very simplified) version of a class that can do that (just replace the TODO's with code to move the window or raise an event).

    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);
    }
    
    0 讨论(0)
  • 2020-12-04 20:32

    A WH_CALLWNDPROC hook would probably suffice, this will allow you to monitor all messages destined for the window of interest.

    Are you asking how to create a global hook using C# or are you happy to create the hook in C++ and then interop with that from .NET? The second option is the route I would go.

    Basically off the top of my head, what I would do is the following

    1- Create global hook in C, and export functions to InstallHook and UninstallHook, which can be called from your C# app using Interop. InstallHook take an hwnd of the window in your C# application.

    2- Have the installed hook function post a custom message to the C# window provided in the call to InstallHook when ever there is a message you are interested in like WM_SIZE in your case.

    3- In the C# application your window that receives the posted messages from the hook will override WndProc to handle the custom message.

    That is an outline of one approach.

    0 讨论(0)
  • 2020-12-04 20:36

    I suggest you use 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);
    

    See also:event hooks

    0 讨论(0)
提交回复
热议问题