在 WPF 窗口中挂钩 Windows 消息循环会在内部添加白色边框

发布于 2024-11-15 10:11:31 字数 2366 浏览 1 评论 0原文

我正在尝试创建一个无法调整大小的 WindowStyle="None" (用于自定义按钮且没有标题)的 WPF 窗口。将 ResizeMode 设置为 NoResize 会删除我想保留的航空边框。

我可以设置最小/最大尺寸属性并完成它,除了:

  1. 调整大小光标仍然可见,并且
  2. 窗口显示以响应用户操作并适合其内容。它显示图像,因此大小会发生变化。

所以,我有一个简单的计划,可以让我完成 99% 的任务:

public class BorderedWindowNoResize : Window
{
    [DllImport( "DwmApi.dll" )]
    public static extern int DwmExtendFrameIntoClientArea(
        IntPtr hwnd,
        ref MARGINS pMarInset );

    [DllImport( "user32.dll", CharSet = CharSet.Auto )]
    public static extern IntPtr DefWindowProc(
        IntPtr hWnd,
        int msg,
        IntPtr wParam,
        IntPtr lParam );

    public BorderedWindowNoResize()
    {           
        Loaded += BorderedWindowNoResize_Loaded;
    }

    private void BorderedWindowNoResize_Loaded( object sender, RoutedEventArgs e )
    {           
        IntPtr mainWindowPtr = new WindowInteropHelper( this ).Handle;
        HwndSource mainWindowSrc = HwndSource.FromHwnd( mainWindowPtr );            
        mainWindowSrc.AddHook( WndProc );
    }

    private IntPtr WndProc( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled )
    {           
        var htLocation = DefWindowProc( hwnd, msg, wParam, lParam ).ToInt32();

        if( msg == (uint)WM.NCHITTEST )
        {
            handled = true;
            switch( htLocation )
            {
                case (int)HitTestResult.HTBOTTOM:
                case (int)HitTestResult.HTBOTTOMLEFT:
                case (int)HitTestResult.HTBOTTOMRIGHT:
                case (int)HitTestResult.HTLEFT:
                case (int)HitTestResult.HTRIGHT:
                case (int)HitTestResult.HTTOP:
                case (int)HitTestResult.HTTOPLEFT:
                case (int)HitTestResult.HTTOPRIGHT:
                    htLocation = (int)HitTestResult.HTBORDER;
                    break;
            }               
        }

        return new IntPtr( htLocation );
    }
}

基本上;

  1. 覆盖窗口过程。
  2. 调用默认的窗口过程。
  3. 如果消息是 WM_NCHITTEST,请检查边界结果。
  4. 如果是边框,则返回常规的HTBORDER

这可以让我保留航空窗口边框并隐藏调整大小光标,但它会在窗口内部添加约 5 像素的白色边框。

事实上,即使我在 WndPrc 顶部返回默认的 Windows 过程结果并且不执行任何其他操作,边框仍然在那里。我的窗口需要不同的背景颜色,所以这对我不起作用。

有什么想法吗?一如既往地提前致谢。

I am trying to create a WPF window with WindowStyle="None" (for custom buttons and no title) that cannot be resized. Setting ResizeMode to NoResize removes the aero border, which I want to keep.

I could set the min/max size properties and be done with it, except that:

  1. The resize cursors are still visible, and
  2. The window is displayed in response to a user action and fits to its contents. It displays an image, so the size changes.

So, I have a simple scheme that gets me 99% of the way there:

public class BorderedWindowNoResize : Window
{
    [DllImport( "DwmApi.dll" )]
    public static extern int DwmExtendFrameIntoClientArea(
        IntPtr hwnd,
        ref MARGINS pMarInset );

    [DllImport( "user32.dll", CharSet = CharSet.Auto )]
    public static extern IntPtr DefWindowProc(
        IntPtr hWnd,
        int msg,
        IntPtr wParam,
        IntPtr lParam );

    public BorderedWindowNoResize()
    {           
        Loaded += BorderedWindowNoResize_Loaded;
    }

    private void BorderedWindowNoResize_Loaded( object sender, RoutedEventArgs e )
    {           
        IntPtr mainWindowPtr = new WindowInteropHelper( this ).Handle;
        HwndSource mainWindowSrc = HwndSource.FromHwnd( mainWindowPtr );            
        mainWindowSrc.AddHook( WndProc );
    }

    private IntPtr WndProc( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled )
    {           
        var htLocation = DefWindowProc( hwnd, msg, wParam, lParam ).ToInt32();

        if( msg == (uint)WM.NCHITTEST )
        {
            handled = true;
            switch( htLocation )
            {
                case (int)HitTestResult.HTBOTTOM:
                case (int)HitTestResult.HTBOTTOMLEFT:
                case (int)HitTestResult.HTBOTTOMRIGHT:
                case (int)HitTestResult.HTLEFT:
                case (int)HitTestResult.HTRIGHT:
                case (int)HitTestResult.HTTOP:
                case (int)HitTestResult.HTTOPLEFT:
                case (int)HitTestResult.HTTOPRIGHT:
                    htLocation = (int)HitTestResult.HTBORDER;
                    break;
            }               
        }

        return new IntPtr( htLocation );
    }
}

Basically;

  1. Override the window procedure.
  2. Call the default window procedure.
  3. If the message it is WM_NCHITTEST, check for the border results.
  4. If it is a border, return the regular HTBORDER.

This works as far as allowing me to keep the aero window border and hiding the resize cursor(s), but it adds a ~5 pixel white border to the inside of my window.

In fact, even if I return the default windows procedure result at the top of WndPrc and do nothing else the border is still there. I need a different background color on my window, so this won't work for me.

Any ideas? Thanks in advance as always.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

等往事风中吹 2024-11-22 10:11:31

添加钩子时,您应该只处理您需要的消息,而忽略其他消息。我相信您正在处理某些消息两次,因为您调用了 DefWindowProc,但从未将已处理参数设置为 true。

因此,在您的情况下,您可以使用:

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {

    if (msg == (uint)WM.NCHITTEST) {
        handled = true;
        var htLocation = DefWindowProc(hwnd, msg, wParam, lParam).ToInt32();
        switch (htLocation) {
            case (int)HitTestResult.HTBOTTOM:
            case (int)HitTestResult.HTBOTTOMLEFT:
            case (int)HitTestResult.HTBOTTOMRIGHT:
            case (int)HitTestResult.HTLEFT:
            case (int)HitTestResult.HTRIGHT:
            case (int)HitTestResult.HTTOP:
            case (int)HitTestResult.HTTOPLEFT:
            case (int)HitTestResult.HTTOPRIGHT:
                htLocation = (int)HitTestResult.HTBORDER;
                break;
        }
        return new IntPtr(htLocation);
    }

    return IntPtr.Zero;
}

另外,我可能会在 OnSourceInitialized 覆盖,如下所示:

protected override void OnSourceInitialized(EventArgs e) {
    base.OnSourceInitialized(e);

    IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle;
    HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr);
    mainWindowSrc.AddHook(WndProc);
}

When you add your hook, you should only handle the messages you need to, and ignore the others. I believe you are handling certain messages twice, since you call DefWindowProc, but never set the handled parameter to true.

So in your case, you'd use:

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {

    if (msg == (uint)WM.NCHITTEST) {
        handled = true;
        var htLocation = DefWindowProc(hwnd, msg, wParam, lParam).ToInt32();
        switch (htLocation) {
            case (int)HitTestResult.HTBOTTOM:
            case (int)HitTestResult.HTBOTTOMLEFT:
            case (int)HitTestResult.HTBOTTOMRIGHT:
            case (int)HitTestResult.HTLEFT:
            case (int)HitTestResult.HTRIGHT:
            case (int)HitTestResult.HTTOP:
            case (int)HitTestResult.HTTOPLEFT:
            case (int)HitTestResult.HTTOPRIGHT:
                htLocation = (int)HitTestResult.HTBORDER;
                break;
        }
        return new IntPtr(htLocation);
    }

    return IntPtr.Zero;
}

Also, I'd probably add the hook in an OnSourceInitialized override, like so:

protected override void OnSourceInitialized(EventArgs e) {
    base.OnSourceInitialized(e);

    IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle;
    HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr);
    mainWindowSrc.AddHook(WndProc);
}
柠檬色的秋千 2024-11-22 10:11:31

您可以在 WPF 应用程序中的任何位置进行尝试

ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);

,并且:

// ******************************************************************
private static void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled)
{
    if (!handled)
    {
        if (msg.message == WmHotKey)
        {
            HotKey hotKey;

            if (_dictHotKeyToCalBackProc.TryGetValue((int)msg.wParam, out hotKey))
            {
                if (hotKey.Action != null)
                {
                    hotKey.Action.Invoke(hotKey);
                }
                handled = true;
            }
        }
    }
}

You can try from anywhere in a WPF App

ComponentDispatcher.ThreadFilterMessage += new ThreadMessageEventHandler(ComponentDispatcherThreadFilterMessage);

and:

// ******************************************************************
private static void ComponentDispatcherThreadFilterMessage(ref MSG msg, ref bool handled)
{
    if (!handled)
    {
        if (msg.message == WmHotKey)
        {
            HotKey hotKey;

            if (_dictHotKeyToCalBackProc.TryGetValue((int)msg.wParam, out hotKey))
            {
                if (hotKey.Action != null)
                {
                    hotKey.Action.Invoke(hotKey);
                }
                handled = true;
            }
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文