为什么我无法检测发送到CommonDialog的Windows消息?拦截它们的正确方法是什么?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了为什么我无法检测发送到CommonDialog的Windows消息?拦截它们的正确方法是什么?相关的知识,希望对你有一定的参考价值。

我试图检测用户点击FormCommonDialog的时间。

表格相当简单。我创建了一个拦截消息的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)

当我运行控制台应用程序并单击Form时,我看到“活动”已记录到控制台。

当我用Form替换CommonDialog时:

SaveFileDialog dialog = new SaveFileDialog();
dialog.ShowDialog();

我无法再检测到鼠标点击,即使我可以看到Windows消息被分派到CommonDialog(FWIW,我无法检测到任何消息):

enter image description here

那我怎么不能拦截那些消息呢?


我想到的是,因为Application.AddMessageFilter是特定于线程的,所以如果CommonDialog是在与调用dialog.ShowDialog()的线程不同的线程上创建的,我不会得到任何这些消息。

但是,我做了一个快速测试,我尝试向调用WM_CLOSE的线程上的所有CommonDialogs发送dialog.ShowDialog()消息,并且它有效:

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

以上是关于为什么我无法检测发送到CommonDialog的Windows消息?拦截它们的正确方法是什么?的主要内容,如果未能解决你的问题,请参考以下文章

无法检测从android发送的php中的Post参数

如何在后台将检测到的 Beacon 信息的详细信息发送到服务器?

C TCP无法检测到断开的连接[重复]

想从图像中检测模糊,但无法正确处理

VB控件commondialog 的具体作用与用法

VB选择文件保存到指定文件夹