news 2026/6/10 20:22:49

系统消息的接收与无标题栏/边框窗体的移动与尺寸调整

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
系统消息的接收与无标题栏/边框窗体的移动与尺寸调整

私信,有看过上一篇的朋友说想了解Winform相关内容,感谢建议,因为因此稍作回忆后就想稍作一些记录和对比。

最早的时候TDS文件搜索这款工具是用Winform实现的,最早版本是4.72的Winform,后来变成了net8。当时有想过改WPF,但一直感觉必要性不大。直到后来net8开始尝试了avalonia,才觉得有了些必要对界面及一些逻辑进行了重写。

个人看来,Winform开发速度快,UI资源占用少,界面响应也快,而且本身的开发框架与windows系统功能兼容性很高,不少效果都能通过黑科技般的操作实现。

为了追求高画质/自由度,可能不得不用Avalonia UI,这时很多习惯了的处理都需要重新找解决方案。尽管Avalonia类似WPF,但很多语法细节差异还较大,学习资料相对较少,如果没有ai辅助,刚入门甚至会有无从下手的感觉。而且Avalonia开发要从sdk开始安装,开发界面也没法直观拖拉拽调试,开发热更新需要适应。

AVALONIA WINFORM

尽管用了Avalonia,但对Winform还是很喜欢的,给了很多C#程序员一开始写代码的动力,也是快速测试想法的首要选择。因此呢,这里我把一些在tds这个软件中winform和avalonia相关实现的差异和走过的坑简单分享一下。Winform版本的代码也全部开源推送仓库了(关注公众号发送tds消息自取)。如果有仍在用Winform想实现特殊效果的,或者有想尝试下Avalonia的可以参考。我们将分为五个TDS开发过程中的实例,对Windows操作系统上的Winform和Avalonia两者实现相同的功能进行对比,分别是:

系统消息接收

无标题栏/边框窗体的窗体移动

无标题栏/边框下的窗体尺寸改变

二、系统消息接收

在windows操作系统下,响应系统级别快捷键响应最高效的实现自然是RegisterHotKey。当程序注册成功一个热键后,用户按下了热键,操作系统会向你的程序窗体局部发送一个标准的系统消息。注册热键直接调用系统api就可以了,但是如何拿到消息呢?

[System.Runtime.InteropServices.DllImport("user32.dll")]

public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint keyValue, Keys vk);

2.1 Winform的消息循环

Winform拿到消息很简单,在Form类下面重写WndProc函数即可,通过判定Message的值即可实现响应功能的触发。这个函数是不断触发的,他不光处理按键,还处理其他各种各样的消息,因此写功能的时候一定要考虑到代码的执行效率。

protected override void WndProc(ref Message m)

{

switch (m.Msg)

{

case 0x0312:

switch (m.WParam.ToString()) //处理热键消息id

{

case "8617":

autoshoworhide(); //窗口隐藏或显现

break;

}

base.WndProc(ref m);

break;

// ....其他消息的处理

default:

base.WndProc(ref m);

break;

}

}

2.2 Avalonia的消息循环

Avalonia中,一开始尝试过用非阻塞式系统索API函数 PeekMessage,尽管同样能拿到系统消息,但由于各种原因,总是会出现丢消息以及界面卡顿的情况。知道后来发现了可通过Avalonia.Controls下面的Win32Properties.AddWndProcHookCallback函数。Win32Properties.AddWndProcHookCallback(this, WndProc),需要将本窗体对象(Avalonia.controls.Window)也就是this,和回调函数WndProc传入,即可像Winform一样流畅处理系统消息了。

private IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam, ref bool handled)

{

// If not a hotkey message or the global hotkey for showing the window

if ((int)wParam == GlobalHotkey.HotKeyId)

{

AutoShowOrHide();

}

// ....其他消息的处理

return IntPtr.Zero;

}

三、无标题栏/边框窗体的窗体移动

无界面下,没有标题栏了,那么窗体的移动需要通过鼠标与控件的交互来实现。比如说点击控件后窗体跟着鼠标移动,被鼠标拖拽等。可能大家会觉得这个实现很难,需要计算各种鼠标与窗口位置,控制边界等...

Wait wait... 如果不亲自试一试的话永远也不会知道真正做起来有多简单。

3.1 Winform的欺骗

在Winform下的实现只需在某个控件的mouseDown事件中加入2行代码即可。他的原理其实是欺骗Windows,告诉操作系统你鼠标点击的是程序的标题栏,然后程序就可以像拖拽标题栏一样随鼠标移动了

public const int WM_SYSCOMMAND = 0x0112;

public const int SC_MOVE = 0xF010;

public const int HTCAPTION = 0x0002; //标题栏, 其他对应的功能取值可参考 https://learn.microsoft.com/zh-cn/windows/win32/inputdev/wm-nchittest

[DllImport("user32.dll")]

public static extern bool ReleaseCapture();

[DllImport("user32.dll")]

public static extern bool SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam);

private void Keywords_MouseDown(object sender, MouseEventArgs e) //某个控件的鼠标按下事件

{

//移动窗体

ReleaseCapture();

SendMessage(this.Handle, WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);

}

3.2 Avalonia实现

可能是为了适配触控操作,Avalonia无界面移动实现更简单,在控件的PointerPressedEvent事件下直接调用自带方法this.BeginMoveDrag, 将PointerPressedEventArgs传入即可完成与Winform一样的效果。

private void Keywords_MouseDown(object sender, PointerPressedEventArgs e)

{

this.BeginMoveDrag(e);

}

四、无标题栏/边框下的窗体尺寸改变

这个实现主要是考虑Winform窗体,设置FormBorderStyle为None,当标题栏消失时,往往边框也消失了,边框移动也没有了。

4.1 Winform的再次欺骗

仍然是在操作系统消息的处理循环中,通过改写 WM_NCHITTEST 消息 (0x0084),欺骗系统鼠标在窗体的哪个区域,并触发操作系统默认的机制。

// 下面的常量是为了模拟有标题栏窗体时,触发调整尺寸的上下左右四条边和四个角的状态标识

const int HTLEFT = 10;

const int HTRIGHT = 11;

const int HTTOP = 12;

const int HTTOPLEFT = 13;

const int HTTOPRIGHT = 14;

const int HTBOTTOM = 15;

const int HTBOTTOMLEFT = 0x10;

const int HTBOTTOMRIGHT = 17;

protected override void WndProc(ref Message m)

{

switch (m.Msg)

{

case 0x0084: // WM_NCHITTEST

base.WndProc(ref m); // 默认消息不干扰

Point vPoint = new Point((int)m.LParam & 0xFFFF,

(int)m.LParam >> 16 & 0xFFFF);

vPoint = PointToClient(vPoint);

if (vPoint.X <= 5) //计算容差然后触发,后面类似...

if (vPoint.Y <= 5)

m.Result = (IntPtr)HTTOPLEFT;

else if (vPoint.Y >= ClientSize.Height - 5)

m.Result = (IntPtr)HTBOTTOMLEFT;

else

m.Result = (IntPtr)HTLEFT;

else if (vPoint.X >= ClientSize.Width - 5)

if (vPoint.Y <= 5)

m.Result = (IntPtr)HTTOPRIGHT;

else if (vPoint.Y >= ClientSize.Height - 5)

m.Result = (IntPtr)HTBOTTOMRIGHT;

else

m.Result = (IntPtr)HTRIGHT;

else if (vPoint.Y <= 5)

m.Result = (IntPtr)HTTOP;

else if (vPoint.Y >= ClientSize.Height - 5)

m.Result = (IntPtr)HTBOTTOM;

break;

default:

base.WndProc(ref m);

break;

}

}

4.2 Avalonia实现

直接在xml中配置,将以下三个参数添加到已有的配置中即可。

<Window

ExtendClientAreaToDecorationsHint="True"

ExtendClientAreaChromeHints="NoChrome"

ExtendClientAreaTitleBarHeightHint="-1"

>

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 21:32:44

探索逆合成孔径雷达稀疏成像:短孔径与压缩感知的奇妙融合

逆合成孔径雷达稀疏成像&#xff0c;短孔径成像&#xff0c;压缩感知在雷达成像领域&#xff0c;逆合成孔径雷达&#xff08;ISAR&#xff09;成像技术一直是研究热点。特别是在一些特殊应用场景下&#xff0c;比如对快速目标成像或者在有限空间内进行成像时&#xff0c;传统的…

作者头像 李华
网站建设 2026/6/10 18:53:29

锁的进化史:从偏向锁到重量级锁的奇幻之旅

文章目录为什么需要这么多锁&#xff1f;锁的“状态机”&#xff1a;四种锁状态偏向锁&#xff1a;专一的锁为什么需要偏向锁&#xff1f;偏向锁的工作原理偏向锁的撤销轻量级锁&#xff1a;温和的竞争为什么需要轻量级锁&#xff1f;轻量级锁的工作原理自旋优化&#xff1a;耐…

作者头像 李华
网站建设 2026/6/10 2:21:03

探索三相PWM整流器双闭环仿真模型

三相PWM整流器双闭环仿真模型 模型中包含&#xff1a;主电路&#xff0c;坐标变换&#xff0c;电压电流双环PI控制器&#xff0c;SVPWM控制 1.功率因数1&#xff0c;THD仅1.2% 2.模型闭环输出电压200VDC 3.输出功率调节输出电阻阻值计算功率 4.三相六开关七段式SVPWM仿真&#…

作者头像 李华
网站建设 2026/6/10 15:38:28

【征文计划】从一个小模板开始,深入Rokid AR生态

当我第一次看到项目里那个简单的 main.xsml 文件时&#xff0c;我并没有意识到它背后隐藏着一个多么庞大的AR生态系统。今天&#xff0c;让我带你一起揭开Rokid AR世界的神秘面纱。 故事要从一副49克的眼镜说起 还记得科幻电影里那些炫酷的AR眼镜吗&#xff1f;现在&#xff…

作者头像 李华
网站建设 2026/6/10 15:22:50

孟加拉语视频Clickbait检测数据集分析报告

孟加拉语视频Clickbait检测数据集分析报告 引言与背景 随着数字媒体的快速发展&#xff0c;互联网上的内容呈现爆炸式增长&#xff0c;其中不乏大量为吸引注意力而设计的误导性内容&#xff0c;即Clickbait。Clickbait通常通过夸张、误导性或悬念性的标题吸引用户点击&#xff…

作者头像 李华
网站建设 2026/6/10 16:54:23

LabVIEW打造超实用数据回放软件:功能大揭秘

labview编写的数据回放软件&#xff0c;支持多曲线回放&#xff0c;支持曲线缩放&#xff0c;支持曲线打标签&#xff0c;支持曲线勾选可见不可见&#xff0c;支持点击曲线加粗显示&#xff0c;支持点击曲线显示当前曲线Y标尺&#xff0c;支持曲线配置&#xff0c;支持红蓝标尺…

作者头像 李华