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

如何通过WndProc捕获自定义控件的边框拖动调整大小操作?

解决自定义控件边框拖动动作的捕获问题

问题描述

我尝试通过WndProc捕获用户拖动自定义控件边框调整大小的时刻,希望仅在拖动左侧边框(鼠标左键按下)等场景下调用特定方法,但目前仅能捕获鼠标位置,无法识别正确的拖动动作。

当前代码

const int WM_NCHITTEST = 0x84;
const int WM_SETCURSOR = 0x20;
const int WM_NCLBUTTONDBLCLK = 0xA3;

protected override void WndProc(ref Message m)
{
    int borderWidth = (int)_drawingControlPanel.ImageScale;
    if (m.Msg == WM_SETCURSOR)  /*设置光标为SizeAll*/
    {
        if ((m.LParam.ToInt32() & 0xffff) == 0x2 /*移动*/)
        {
            Cursor.Current = Cursors.SizeAll;
            m.Result = (IntPtr)1;
            return;
        }
    }

    if ((m.Msg == WM_NCLBUTTONDBLCLK)) /*双击时禁用最大化*/
    {
        m.Result = (IntPtr)1;
        return;
    }

    base.WndProc(ref m);

    if (m.Msg == WM_NCHITTEST)
    {
        var pos = PointToClient(new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16));

        if (pos.X <= ClientRectangle.Left + borderWidth && pos.Y <= ClientRectangle.Top + borderWidth)
            m.Result = new IntPtr(13); //左上角
        else if (pos.X >= ClientRectangle.Right - borderWidth && pos.Y <= ClientRectangle.Top + borderWidth)
            m.Result = new IntPtr(14); //右上角
        else if (pos.X <= ClientRectangle.Left + borderWidth && pos.Y >= ClientRectangle.Bottom - borderWidth)
            m.Result = new IntPtr(16); //左下角
        else if (pos.X >= ClientRectangle.Right - borderWidth && pos.Y >= ClientRectangle.Bottom - borderWidth)
            m.Result = new IntPtr(17); //右下角
        else if (pos.X <= ClientRectangle.Left + borderWidth)
            m.Result = new IntPtr(10); //左侧
        else if (pos.Y <= ClientRectangle.Top + borderWidth)
            m.Result = new IntPtr(12); //顶部
        else if (pos.X >= ClientRectangle.Right - borderWidth)
            m.Result = new IntPtr(11); //右侧
        else if (pos.Y >= ClientRectangle.Bottom - borderWidth)
            m.Result = new IntPtr(15); //底部
        else
            m.Result = new IntPtr(2); //移动
    }
}

解决方案

要精准捕获边框拖动的完整动作,需要额外监听WM_NCLBUTTONDOWN、WM_NCLBUTTONUP和WM_SIZING消息,配合状态变量跟踪拖动过程:

1. 添加必要的消息常量

在控件类中新增以下常量:

const int WM_NCLBUTTONDOWN = 0xA1; // 非客户区左键按下
const int WM_NCLBUTTONUP = 0xA2;   // 非客户区左键释放
const int WM_SIZING = 0x214;       // 窗口大小调整中

2. 增加拖动状态跟踪字段

添加私有变量记录当前是否处于拖动状态,以及拖动的边框方向:

private bool _isResizing = false;
private int _resizeEdge = 0; // 存储WM_NCHITTEST返回的边缘值,例如10代表左侧边框

3. 处理拖动开始事件(WM_NCLBUTTONDOWN)

当鼠标左键在边框区域按下时,标记拖动状态并触发对应逻辑:

if (m.Msg == WM_NCLBUTTONDOWN)
{
    var pos = PointToClient(new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16));
    int borderWidth = (int)_drawingControlPanel.ImageScale;

    // 复用WM_NCHITTEST的判断逻辑,确定当前按下的边框
    if (pos.X <= ClientRectangle.Left + borderWidth)
        _resizeEdge = 10;
    else if (pos.X >= ClientRectangle.Right - borderWidth)
        _resizeEdge = 11;
    else if (pos.Y <= ClientRectangle.Top + borderWidth)
        _resizeEdge = 12;
    else if (pos.Y >= ClientRectangle.Bottom - borderWidth)
        _resizeEdge = 15;
    else if (pos.X <= ClientRectangle.Left + borderWidth && pos.Y <= ClientRectangle.Top + borderWidth)
        _resizeEdge = 13;
    else if (pos.X >= ClientRectangle.Right - borderWidth && pos.Y <= ClientRectangle.Top + borderWidth)
        _resizeEdge = 14;
    else if (pos.X <= ClientRectangle.Left + borderWidth && pos.Y >= ClientRectangle.Bottom - borderWidth)
        _resizeEdge = 16;
    else if (pos.X >= ClientRectangle.Right - borderWidth && pos.Y >= ClientRectangle.Bottom - borderWidth)
        _resizeEdge = 17;
    else
        _resizeEdge = 0;

    if (_resizeEdge != 0)
    {
        _isResizing = true;
        // 仅在拖动左侧边框时调用特定方法
        if (_resizeEdge == 10)
        {
            LeftBorderResizeStart();
        }
    }
}

4. 处理拖动过程事件(WM_SIZING)

在调整大小的实时过程中,持续执行对应逻辑:

if (m.Msg == WM_SIZING)
{
    if (_isResizing && _resizeEdge == 10)
    {
        LeftBorderResizing();
    }
}

5. 处理拖动结束事件(WM_NCLBUTTONUP)

鼠标释放时重置状态并执行收尾逻辑:

if (m.Msg == WM_NCLBUTTONUP)
{
    if (_isResizing && _resizeEdge == 10)
    {
        LeftBorderResizeEnd();
    }
    _isResizing = false;
    _resizeEdge = 0;
}

6. 完整修改后的代码

const int WM_NCHITTEST = 0x84;
const int WM_SETCURSOR = 0x20;
const int WM_NCLBUTTONDBLCLK = 0xA3;
const int WM_NCLBUTTONDOWN = 0xA1;
const int WM_NCLBUTTONUP = 0xA2;
const int WM_SIZING = 0x214;

private bool _isResizing = false;
private int _resizeEdge = 0;

protected override void WndProc(ref Message m)
{
    int borderWidth = (int)_drawingControlPanel.ImageScale;

    // 处理拖动开始
    if (m.Msg == WM_NCLBUTTONDOWN)
    {
        var pos = PointToClient(new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16));
        
        if (pos.X <= ClientRectangle.Left + borderWidth)
            _resizeEdge = 10;
        else if (pos.X >= ClientRectangle.Right - borderWidth)
            _resizeEdge = 11;
        else if (pos.Y <= ClientRectangle.Top + borderWidth)
            _resizeEdge = 12;
        else if (pos.Y >= ClientRectangle.Bottom - borderWidth)
            _resizeEdge = 15;
        else if (pos.X <= ClientRectangle.Left + borderWidth && pos.Y <= ClientRectangle.Top + borderWidth)
            _resizeEdge = 13;
        else if (pos.X >= ClientRectangle.Right - borderWidth && pos.Y <= ClientRectangle.Top + borderWidth)
            _resizeEdge = 14;
        else if (pos.X <= ClientRectangle.Left + borderWidth && pos.Y >= ClientRectangle.Bottom - borderWidth)
            _resizeEdge = 16;
        else if (pos.X >= ClientRectangle.Right - borderWidth && pos.Y >= ClientRectangle.Bottom - borderWidth)
            _resizeEdge = 17;
        else
            _resizeEdge = 0;

        if (_resizeEdge != 0)
        {
            _isResizing = true;
            if (_resizeEdge == 10)
            {
                LeftBorderResizeStart();
            }
        }
    }

    // 处理拖动过程
    if (m.Msg == WM_SIZING)
    {
        if (_isResizing && _resizeEdge == 10)
        {
            LeftBorderResizing();
        }
    }

    // 处理拖动结束
    if (m.Msg == WM_NCLBUTTONUP)
    {
        if (_isResizing && _resizeEdge == 10)
        {
            LeftBorderResizeEnd();
        }
        _isResizing = false;
        _resizeEdge = 0;
    }

    // 原有光标设置逻辑
    if (m.Msg == WM_SETCURSOR)
    {
        if ((m.LParam.ToInt32() & 0xffff) == 0x2)
        {
            Cursor.Current = Cursors.SizeAll;
            m.Result = (IntPtr)1;
            return;
        }
    }

    // 原有双击禁用最大化逻辑
    if (m.Msg == WM_NCLBUTTONDBLCLK)
    {
        m.Result = (IntPtr)1;
        return;
    }

    base.WndProc(ref m);

    // 原有WM_NCHITTEST逻辑
    if (m.Msg == WM_NCHITTEST)
    {
        var pos = PointToClient(new Point(m.LParam.ToInt32() & 0xffff, m.LParam.ToInt32() >> 16));

        if (pos.X <= ClientRectangle.Left + borderWidth && pos.Y <= ClientRectangle.Top + borderWidth)
            m.Result = new IntPtr(13); //左上角
        else if (pos.X >= ClientRectangle.Right - borderWidth && pos.Y <= ClientRectangle.Top + borderWidth)
            m.Result = new IntPtr(14); //右上角
        else if (pos.X <= ClientRectangle.Left + borderWidth && pos.Y >= ClientRectangle.Bottom - borderWidth)
            m.Result = new IntPtr(16); //左下角
        else if (pos.X >= ClientRectangle.Right - borderWidth && pos.Y >= ClientRectangle.Bottom - borderWidth)
            m.Result = new IntPtr(17); //右下角
        else if (pos.X <= ClientRectangle.Left + borderWidth)
            m.Result = new IntPtr(10); //左侧
        else if (pos.Y <= ClientRectangle.Top + borderWidth)
            m.Result = new IntPtr(12); //顶部
        else if (pos.X >= ClientRectangle.Right - borderWidth)
            m.Result = new IntPtr(11); //右侧
        else if (pos.Y >= ClientRectangle.Bottom - borderWidth)
            m.Result = new IntPtr(15); //底部
        else
            m.Result = new IntPtr(2); //移动
    }
}

// 替换为你的实际业务逻辑
private void LeftBorderResizeStart()
{
    // 左侧边框拖动开始时执行的操作
}

private void LeftBorderResizing()
{
    // 左侧边框拖动过程中执行的操作
}

private void LeftBorderResizeEnd()
{
    // 左侧边框拖动结束时执行的操作
}

关键说明

  • WM_NCLBUTTONDOWN:标记拖动动作的开始,通过坐标判断触发的边框方向。
  • WM_SIZING:实时响应大小调整,适合处理拖动中的动态逻辑。
  • WM_NCLBUTTONUP:结束拖动状态,执行收尾操作。
  • 确保边框方向的判断逻辑与WM_NCHITTEST完全一致,避免状态错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 04:06:24