为什么我检测不到发送到我的 CommonDialog 的 Windows 消息?拦截它们的正确方法是什么?
Why can't I detect Windows messages that are being sent to my CommonDialog? What is the right way of intercepting them?
我正在尝试检测用户何时单击 Form
和 CommonDialog
。
表格相当简单。我创建了一个 MessageFilter
class 来拦截消息:
class MessageFilter : IMessageFilter
{
private const int WM_LBUTTONDOWN = 0x0201;
public bool PreFilterMessage(ref Message message)
{
if (message.Msg == WM_LBUTTONDOWN)
{
Console.WriteLine("activity");
}
return false;
}
}
然后我注册了消息过滤器:
MessageFilter mf = new MessageFilter();
Application.AddMessageFilter(mf);
Form form = new Form();
form.ShowDialog();
Application.RemoveMessageFilter(mf)
当我 运行 我的控制台应用程序并单击 Form
时,我看到 "activity" 登录到控制台。
当我用 CommonDialog
替换 Form
时:
SaveFileDialog dialog = new SaveFileDialog();
dialog.ShowDialog();
我无法再检测到鼠标点击,即使我可以看到 Windows 消息被发送到 CommonDialog(FWIW,我无法检测到任何消息):
那我怎么截不到那些消息呢?
我想到的是,由于 Application.AddMessageFilter
是线程特定的,也许如果 CommonDialog 是在与调用 dialog.ShowDialog()
的线程不同的线程上创建的,我就不会得到这些消息中的任何一条。
但是,我做了一个快速测试,尝试向调用 dialog.ShowDialog()
的线程上的所有 CommonDialogs 发送一条 WM_CLOSE
消息,并且成功了:
int threadId = 0;
Thread thread = new Thread(() =>
{
threadId = NativeMethods.GetCurrentThreadIdWrapper();
SaveFileDialog dialog = new SaveFileDialog();
dialog.ShowDialog();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Thread.Sleep(2000);
NativeMethods.CloseAllWindowsDialogs(threadId);
Thread.Sleep(2000);
NativeMethods 看起来像:
static class NativeMethods
{
public static int GetCurrentThreadIdWrapper()
{
return GetCurrentThreadId();
}
public static void CloseAllWindowsDialogs(int threadId)
{
EnumThreadWndProc callback = new EnumThreadWndProc(CloseWindowIfCommonDialog);
EnumThreadWindows(threadId, callback, IntPtr.Zero);
GC.KeepAlive(callback);
}
private static bool CloseWindowIfCommonDialog(IntPtr hWnd, IntPtr lp)
{
if (IsWindowsDialog(hWnd))
{
UIntPtr result;
const int WM_CLOSE = 0x0010;
const uint SMTO_ABORTIFHUNG = 0x0002;
SendMessageTimeout(hWnd, WM_CLOSE, UIntPtr.Zero, IntPtr.Zero, SMTO_ABORTIFHUNG, 5000, out result);
}
return true;
}
private static bool IsWindowsDialog(IntPtr hWnd)
{
const int MAX_PATH_LENGTH = 260; // https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
StringBuilder sb = new StringBuilder(MAX_PATH_LENGTH);
GetClassName(hWnd, sb, sb.Capacity);
return sb.ToString() == "#32770";
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int GetCurrentThreadId();
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint msg, UIntPtr wp, IntPtr lp, uint fuFlags, uint timeout, out UIntPtr lpdwResult);
}
为什么我不能拦截 CommonDialog 消息?我能做些什么?
设置一个本地鼠标钩子如何?
在我的项目上效果很好。
public const int WM_LBUTTONDOWN = 0x0201;
// add other button messages if necessary
public const int WH_MOUSE = 7;
private IntPtr _hookHandle;
private void HookStart() {
int threadId = GetCurrentThreadId();
HookProc mouseClickHandler = new HookProc(MouseClickHandler);
_hookHandle = SetWindowsHookEx(WH_MOUSE, mouseClickHandler, IntPtr.Zero, (uint) threadId);
if (_hookHandle == IntPtr.Zero) throw new Exception("Hooking failed!");
}
private void HookStop() {
if (UnhookWindowsHookEx(_hookHandle) == IntPtr.Zero) throw new Exception("Unhooking failed!");
}
private IntPtr MouseClickHandler(int nCode, IntPtr wParam, IntPtr lParam) {
if (nCode >= 0 && wParam == (IntPtr) WM_LBUTTONDOWN) {
// user clicked
}
return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, uint threadId);
[DllImport("User32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int UnhookWindowsEx(IntPtr idHook);
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
我正在尝试检测用户何时单击 Form
和 CommonDialog
。
表格相当简单。我创建了一个 MessageFilter
class 来拦截消息:
class MessageFilter : IMessageFilter
{
private const int WM_LBUTTONDOWN = 0x0201;
public bool PreFilterMessage(ref Message message)
{
if (message.Msg == WM_LBUTTONDOWN)
{
Console.WriteLine("activity");
}
return false;
}
}
然后我注册了消息过滤器:
MessageFilter mf = new MessageFilter();
Application.AddMessageFilter(mf);
Form form = new Form();
form.ShowDialog();
Application.RemoveMessageFilter(mf)
当我 运行 我的控制台应用程序并单击 Form
时,我看到 "activity" 登录到控制台。
当我用 CommonDialog
替换 Form
时:
SaveFileDialog dialog = new SaveFileDialog();
dialog.ShowDialog();
我无法再检测到鼠标点击,即使我可以看到 Windows 消息被发送到 CommonDialog(FWIW,我无法检测到任何消息):
那我怎么截不到那些消息呢?
我想到的是,由于 Application.AddMessageFilter
是线程特定的,也许如果 CommonDialog 是在与调用 dialog.ShowDialog()
的线程不同的线程上创建的,我就不会得到这些消息中的任何一条。
但是,我做了一个快速测试,尝试向调用 dialog.ShowDialog()
的线程上的所有 CommonDialogs 发送一条 WM_CLOSE
消息,并且成功了:
int threadId = 0;
Thread thread = new Thread(() =>
{
threadId = NativeMethods.GetCurrentThreadIdWrapper();
SaveFileDialog dialog = new SaveFileDialog();
dialog.ShowDialog();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Thread.Sleep(2000);
NativeMethods.CloseAllWindowsDialogs(threadId);
Thread.Sleep(2000);
NativeMethods 看起来像:
static class NativeMethods
{
public static int GetCurrentThreadIdWrapper()
{
return GetCurrentThreadId();
}
public static void CloseAllWindowsDialogs(int threadId)
{
EnumThreadWndProc callback = new EnumThreadWndProc(CloseWindowIfCommonDialog);
EnumThreadWindows(threadId, callback, IntPtr.Zero);
GC.KeepAlive(callback);
}
private static bool CloseWindowIfCommonDialog(IntPtr hWnd, IntPtr lp)
{
if (IsWindowsDialog(hWnd))
{
UIntPtr result;
const int WM_CLOSE = 0x0010;
const uint SMTO_ABORTIFHUNG = 0x0002;
SendMessageTimeout(hWnd, WM_CLOSE, UIntPtr.Zero, IntPtr.Zero, SMTO_ABORTIFHUNG, 5000, out result);
}
return true;
}
private static bool IsWindowsDialog(IntPtr hWnd)
{
const int MAX_PATH_LENGTH = 260; // https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
StringBuilder sb = new StringBuilder(MAX_PATH_LENGTH);
GetClassName(hWnd, sb, sb.Capacity);
return sb.ToString() == "#32770";
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int GetCurrentThreadId();
private delegate bool EnumThreadWndProc(IntPtr hWnd, IntPtr lp);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool EnumThreadWindows(int tid, EnumThreadWndProc callback, IntPtr lp);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint msg, UIntPtr wp, IntPtr lp, uint fuFlags, uint timeout, out UIntPtr lpdwResult);
}
为什么我不能拦截 CommonDialog 消息?我能做些什么?
设置一个本地鼠标钩子如何?
在我的项目上效果很好。
public const int WM_LBUTTONDOWN = 0x0201;
// add other button messages if necessary
public const int WH_MOUSE = 7;
private IntPtr _hookHandle;
private void HookStart() {
int threadId = GetCurrentThreadId();
HookProc mouseClickHandler = new HookProc(MouseClickHandler);
_hookHandle = SetWindowsHookEx(WH_MOUSE, mouseClickHandler, IntPtr.Zero, (uint) threadId);
if (_hookHandle == IntPtr.Zero) throw new Exception("Hooking failed!");
}
private void HookStop() {
if (UnhookWindowsHookEx(_hookHandle) == IntPtr.Zero) throw new Exception("Unhooking failed!");
}
private IntPtr MouseClickHandler(int nCode, IntPtr wParam, IntPtr lParam) {
if (nCode >= 0 && wParam == (IntPtr) WM_LBUTTONDOWN) {
// user clicked
}
return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, uint threadId);
[DllImport("User32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("User32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
public static extern int UnhookWindowsEx(IntPtr idHook);
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();