C# 为什么可以';我是否检测到发送到我的CommonDialog的Windows消息?拦截它们的正确方法是什么?

C# 为什么可以';我是否检测到发送到我的CommonDialog的Windows消息?拦截它们的正确方法是什么?,c#,winforms,windows-messages,common-dialog,C#,Winforms,Windows Messages,Common Dialog,我试图检测用户何时单击表单和公共对话框 形式相当简单。我创建了一个拦截消息的MessageFilter类: class MessageFilter : IMessageFilter { private const int WM_LBUTTONDOWN = 0x0201; public bool PreFilterMessage(ref Message message) { if (message.Msg == WM_LBUTTONDOWN)

我试图检测用户何时单击
表单
公共对话框

形式相当简单。我创建了一个拦截消息的
MessageFilter
类:

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)
当我运行控制台应用程序并单击
表单
时,我会看到“活动”记录到控制台

当我将
表单
替换为
公共对话框
时:

SaveFileDialog dialog = new SaveFileDialog();
dialog.ShowDialog();
即使可以看到Windows消息被发送到CommonDialog(FWIW,我检测不到任何消息),我也无法再检测到鼠标单击:

那为什么我不能截获这些信息呢


我想到的是,由于
Application.AddMessageFilter
是特定于线程的,可能如果CommonDialog是在调用
dialog.ShowDialog()
的线程之外的线程上创建的,我就不会收到这些消息

但是,我做了一个快速测试,尝试将
WM_CLOSE
消息发送到调用
dialog.ShowDialog()
的线程上的所有CommonDialogs,结果成功了:

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

为什么我不能截获公共对话消息?我能做些什么?

设置本地鼠标挂钩怎么样

对我的项目很有效

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

. 和。@Jimi的备注部分我不明白为什么在我谈论CommonDialogs时,你会链接到一个关于消息框的部分。下面关于情态动词的部分并没有特别的帮助——或者我不知道你想让我从中得到什么。关于
HookProc
,我以前考虑过,但我希望它能应用于其他通用对话框,而不仅仅是FIleDialog。其次,看起来我必须在我的另一个扩展FileDialog的类中重写HookProc,如果可能的话,我不希望这样做。@Jimi此外,我应该从IMessageFilter的备注部分学到什么?它分派给控件和窗体,公共对话框是。。。也不我不确定我不能让它工作。当我将
0
作为threadId参数传递给
SetWindowsHookEx
时,它返回一个空句柄,我崩溃了。当我使用当前线程ID(这更符合我的要求)时,它会返回一个有效句柄,并且基本上每次勾选都会触发
YOUR_EVENT_FUNC
,但我从不输入
if
块。根据文档
l
应该包含有关消息的信息,因此
w==
应该是
l==
吗?(即使我使用
l
而不是
w
,由于某种原因,
if
块也不会被输入)(另外,您的一些外部方法类型是不正确的)ㄴ 您的_EVENT _FUNC应该在每次勾号时触发,因为它在您移动鼠标时被调用(如果使用鼠标,则会获取每个事件)。此外,w包含挂接消息的类型,l是指向的指针。所以,不要改变它。将WM_LBUTTONDOWN替换为WM_LBUTTONUP。而且,我们正在生成低级钩子,因此SetwindowsHookEx上的threadid应该为0,但您的程序出现错误,为0。。。如果这个片段不能解决你的问题,我将删除这篇文章。你的代码片段让我更接近了,但是我不得不使用当前的线程ID而不是0,并且我选择了WH_鼠标而不是LL版本。如果你不介意的话,我可以编辑你的代码片段来展示我做了什么。