You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为C#透明穿透窗体设置边框点击识别例外?

实现点击穿透窗体的边框点击例外

我有一个透明且支持点击穿透的C#窗体,点击操作会直接穿透到窗体下层。但该窗体绘制了20像素宽的红色边框,我希望点击该边框时能被窗体识别而非穿透,请问是否可以实现这种点击例外?

现有实现代码

1. 设置窗体透明

FormBorderStyle = FormBorderStyle.None;
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);

this.TransparencyKey = this.BackColor = Color.Turquoise;

2. 设置点击穿透

int initialStyle = GetWindowLong(this.Handle, -20);
SetWindowLong(this.Handle, -20, initialStyle | 0x80000 | 0x20);
// Create and add the custom border control to the form

3. 绘制边框

protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);

    // Draw a red border around the form
    using (Pen pen = new Pen(Color.Red, 20))
    {
        e.Graphics.DrawRectangle(pen, 0, 0, Width - 1, Height - 1);
    }
}

我尝试的代码,思路是否正确?

protected override void WndProc(ref Message m)
{
    const int WM_NCHITTEST = 0x0084;
    const int HTTRANSPARENT = -1;
    const int HTCLIENT = 1;

    if (m.Msg == WM_NCHITTEST)
    {
        if (Control.MouseButtons == MouseButtons.Left && !isClickThrough)
        {
            // Set the window style to make it click-through
            int initialStyle = GetWindowLong(this.Handle, -20);
            SetWindowLong(this.Handle, -20, initialStyle | 0x80000 | 0x20);
            isClickThrough = true;
        }
        else if (Control.MouseButtons == MouseButtons.None && isClickThrough)
        {
            // Reset the window style to make it non-click-through
            int initialStyle = GetWindowLong(this.Handle, -20);
            SetWindowLong(this.Handle, -20, initialStyle & ~0x80000 & ~0x20);
            isClickThrough = false;
        }

        m.Result = (IntPtr)(isClickThrough ? HTTRANSPARENT : HTCLIENT);
        return;
    }

    base.WndProc(ref m);
}

解答

你的思路存在问题,当前代码通过鼠标按键状态全局切换窗体的点击穿透属性,无法精准识别点击位置是否在边框上,达不到仅边框可点击的效果。

正确的做法是在WM_NCHITTEST消息中直接判断鼠标位置是否落在边框区域内:

  • 先将屏幕坐标转换为窗体客户端坐标
  • 判断坐标是否处于距离窗体边缘20像素的范围内
  • 若在边框区域,返回HTCLIENT让窗体接收点击;否则返回HTTRANSPARENT允许点击穿透

修改后的WndProc代码如下:

private const int WM_NCHITTEST = 0x0084;
private const int HTTRANSPARENT = -1;
private const int HTCLIENT = 1;
private const int BORDER_WIDTH = 20; // 与绘制的边框宽度保持一致

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_NCHITTEST)
    {
        // 将消息中的屏幕坐标转换为窗体客户端坐标
        Point screenPos = new Point(m.LParam.ToInt32() & 0xFFFF, m.LParam.ToInt32() >> 16);
        Point clientPos = this.PointToClient(screenPos);

        // 判断鼠标是否点击在边框区域
        bool isOnBorder = clientPos.X <= BORDER_WIDTH || 
                          clientPos.X >= this.ClientSize.Width - BORDER_WIDTH || 
                          clientPos.Y <= BORDER_WIDTH || 
                          clientPos.Y >= this.ClientSize.Height - BORDER_WIDTH;

        // 根据判断结果设置消息返回值
        m.Result = isOnBorder ? (IntPtr)HTCLIENT : (IntPtr)HTTRANSPARENT;
        return;
    }

    base.WndProc(ref m);
}

注意事项:

  • 无需动态修改窗体的扩展样式(0x80000和0x20),保持初始的点击穿透设置即可,通过WM_NCHITTEST的返回值控制单点击的穿透行为。
  • 确保BORDER_WIDTH常量值与你绘制边框时的宽度一致,避免判断范围出现偏差。

内容的提问来源于stack exchange,提问作者Chefke

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 08:30:05