当前位置: 首页 > news >正文

当无边框窗口被子窗口遮挡导致难以调节窗口大小时,可通过处理 NCHITTEST 消息重新支持调节窗口大小

做无边框窗口之后,我们有方法可以让窗口的标题栏区域和边缘调大小的区域继续正常工作,直到——这个窗口上面覆盖了其他的子窗口。这个子窗口会吃掉消息导致父窗口的边缘无法再继续处理这些消息。


@TOC

子窗口遮挡了父窗口

看一下下面的动画,这个窗口的下半部分放了一个子窗口。

被子窗口遮挡了边缘的父窗口

然后尝试在边缘调节窗口尺寸,会发现被子窗口覆盖的部分是无法完成窗口大小调节的。

子窗口区域无法调节窗口大小

究其原因,是子窗口处理掉了与调窗口大小相关的消息,导致父窗口完全不知道应该如何处理这个时候的操作。

在子窗口处理消息循环

在我的另一篇博客中,我有提到通过处理 WM_NCHITTEST 消息,返回 HT_RIGHT 等来实现支持 Windows 原生窗口功能的效果。然而那种方法是不适用于本文的场景的,如果你试试就会发现,那种方法会使得你只能调子窗口的大小,对父窗口无济于事。

正确的处理方法是当鼠标划过原本应该处在非客户区部分的时候,将消息交给父窗口处理。于是,我们需要在消息循环的处理中返回 HTTRANSPARENT 来告诉操作系统这个区域子窗口不处理消息,请交给父窗口。

这里,我以 WPF 的消息循环来写代码。因为只要是 Windows 平台的 UI 框架都有消息循环的处理,所以可以很容易迁移到其他框架甚至是其他语言。

public partial class ChildWindow : Window
{
    public ChildWindow()
    {
        InitializeComponent();
        SourceInitialized += ChildWindow_SourceInitialized;
    }

    private async void ChildWindow_SourceInitialized(object sender, EventArgs e)
    {
        var helper = new WindowInteropHelper(this);
        var source = HwndSource.FromHwnd(helper.Handle);
        source.AddHook(WndProc);
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        const int WM_NCHITTEST = 0x0084;
        const int HTTRANSPARENT = -1;
        switch (msg)
        {
            case WM_NCHITTEST:
                // 这里,我强行让所有区域返回 HTTRANSPARENT,于是整个子窗口都交给父窗口处理消息。
                // 正常,你应该在这里计算窗口边缘。
                handled = true;
                return new IntPtr(HTTRANSPARENT);
            default:
                break;
        }
        return IntPtr.Zero;
    }
}

上面的代码会比较简化,因为我让子窗口的所有区域都返回 HTTRANSPARENT,这会让整个子窗口区域的消息都不由子窗口处理。如果需要使用这段代码的话,你需要自己判断窗口的边缘。

子窗口区域可以调节窗口大小

如果需要得到当前坐标的话,可以把下面的方法加入到你的项目中:

public static (int lowOrder, int highOrder) GetOrderWord(IntPtr value)
{
    int low = unchecked((short) (long) value);
    int high = unchecked((short) ((long) value >> 16));
    return (low, high);
}

于是将消息循环中的 lParam 传入可以获得当前的坐标(屏幕坐标系):

// 获得当前基于屏幕坐标系的当前鼠标光标位置。
var (x, y) = GetOrderWord(lParam);

需要注意一些坑

当你准备使用返回 HTTRANSPARENT 时,一定要保证你坐标所在的父子窗口在同一个线程!

返回 HTTRANSPARENT 时,操作系统只会查找同线程的其他窗口,如果你的父窗口非同一个线程,那么操作系统处理消息循环时是找不到下一个处理消息的窗口的。

如果你一定要在父窗口非同一个线程时返回 HTTRANSPARENT 那么你的整个窗口(顶层窗口和子窗口)将无法再操作!你可以阅读 HTTRANSPARENT is evil - virtualdub.org 了解相关的坑。


参考资料

  • WM_NCHITTEST message (Winuser.h) - Win32 apps - Microsoft Docs
  • multithreading - WM_NCHITTEST and HTTRANSPARENT blocks input from message loop - Stack Overflow
  • WM_NCHITTEST and HTTRANSPARENT blocks input from message loop - multithreading
  • Click through window with image (WPF) issues (HTTRANSPARENT isn't working)
  • HTTRANSPARENT is evil - virtualdub.org
  • c++ - how to move parent window without border from child using WM_NCHITTEST - Stack Overflow
  • winapi - Win api in C#. Get Hi and low word from IntPtr - Stack Overflow

我的博客会首发于 https://blog.walterlv.com/,而 CSDN 会从其中精选发布,但是一旦发布了就很少更新。

如果在博客看到有任何不懂的内容,欢迎交流。我搭建了 dotnet 职业技术学院 欢迎大家加入。

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、重新发布,但务必保留文章署名吕毅(包含链接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我联系。

相关文章:

  • 如何给 GitHub Pages 配置多个域名?
  • 通过子类化窗口(SubClass)来为现有的某个窗口添加新的窗口处理程序(或者叫钩子,Hook)
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .NET Windows:删除文件夹后立即判断,有可能依然存在
  • .NET 将混合了多个不同平台(Windows Mac Linux)的文件 目录的路径格式化成同一个平台下的路径
  • git 乱改你的换行符?一句话设置让 git 不再碰你某个文件的换行符
  • Linux Shell 中需要转义的字符
  • Unity3D 入门:Unity Editor 编辑器常用快捷键
  • Unity3D 入门:Unity 项目版本管理建议使用的 .gitignore 忽略文件和 .gitattributes 文件(2020年4月更新)
  • Unity3D 入门:让 C# 脚本公开可在 Unity 编辑器中设置的属性
  • Unity3D 入门:如何管理 Unity 项目中的 NuGet 包?使用第三方 NuGet 包管理器——NuGetForUnity
  • Unity3D 入门:如何在脚本中找到游戏对象的父子级 祖孙级对象和它们的组件
  • Unity3D 入门:如何制作天空效果?天空盒的使用
  • Unity3D 入门:使用 Visual Studio 开发 Unity C# 脚本,说说根目录的那些 sln 和 csproj 文件
  • Unity3D 入门:最简单的控制视角,以及控制角色前进、转向的脚本
  • angular组件开发
  •  D - 粉碎叛乱F - 其他起义
  • fetch 从初识到应用
  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!
  • leetcode讲解--894. All Possible Full Binary Trees
  • mysql常用命令汇总
  • node.js
  • Spring Boot MyBatis配置多种数据库
  • WebSocket使用
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 快速构建spring-cloud+sleuth+rabbit+ zipkin+es+kibana+grafana日志跟踪平台
  • 前言-如何学习区块链
  • 吐槽Javascript系列二:数组中的splice和slice方法
  • 无服务器化是企业 IT 架构的未来吗?
  • 鱼骨图 - 如何绘制?
  • 云大使推广中的常见热门问题
  • CMake 入门1/5:基于阿里云 ECS搭建体验环境
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • 正则表达式-基础知识Review
  • ###STL(标准模板库)
  • #define MODIFY_REG(REG, CLEARMASK, SETMASK)
  • #FPGA(基础知识)
  • #图像处理
  • (145)光线追踪距离场柔和阴影
  • (3)选择元素——(17)练习(Exercises)
  • (附源码)spring boot网络空间安全实验教学示范中心网站 毕业设计 111454
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (四)c52学习之旅-流水LED灯
  • (一)WLAN定义和基本架构转
  • (转)关于pipe()的详细解析
  • .NET 分布式技术比较
  • .Net 路由处理厉害了
  • .NET 依赖注入和配置系统
  • .NET命名规范和开发约定
  • .NET微信公众号开发-2.0创建自定义菜单
  • @Bean有哪些属性
  • @column注解_MyBatis注解开发 -MyBatis(15)
  • @RequestParam,@RequestBody和@PathVariable 区别
  • @我的前任是个极品 微博分析