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 application.
At this moment I solved by getting the hWnd of the window and querying periodically in a thread the location of the observed window, moving the observer window accordingly.
However this is a very inelegant solution. What I would like to do is to listen to the resize event of the observed window so that the observer will r开发者_运维知识库eact only when necessary.
I assume I should use a hook, and I found plenty of ways of doing it, but my lack of knowledge of the C WinAPI is blocking me in understanding which hook I need to create and how (pinvoke/parameters/etc).
I'm pretty sure this is quite trivial, and some of you familiar with C/C++ and WinAPI will have the answer ready at hand ;)
Thanks
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
}
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.
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
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);
}
精彩评论