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

WinForms自定义DataGridViewCell:实现横向3个可点击按钮

解决自定义DataGridViewCell实现多按钮的问题

核心问题说明

你当前代码的问题在于:DataGridViewCell不是WinForms容器控件,构造函数里创建的Panel和Button对象并没有被添加到DataGridView的控件树中,也不会参与单元格的绘制流程,所以完全不会显示。另外你用ButtonRenderer绘制的按钮区域计算错误,导致按钮重叠或超出单元格范围。

推荐解决方案:纯绘制+点击判断(轻量高效)

这种方式不需要真实控件,通过绘制按钮外观,再根据鼠标点击位置判断触发对应逻辑,是实现DataGridView多按钮单元格的标准方式。

修正后的完整代码

public class CustomButtonCell : DataGridViewButtonCell
{
    // 定义按钮点击事件,供外部订阅
    public event EventHandler<ButtonClickEventArgs> ButtonClicked;

    // 按钮文本(按需求保留3个)
    private readonly string[] _buttonTexts = { "Edit", "Delete", "Approve" };
    public bool Enabled { get; set; } = true;

    public override object Clone()
    {
        var cell = (CustomButtonCell)base.Clone();
        cell.Enabled = Enabled;
        return cell;
    }

    protected override void Paint(Graphics graphics,
        Rectangle clipBounds, Rectangle cellBounds, int rowIndex,
        DataGridViewElementStates elementState, object value,
        object formattedValue, string errorText,
        DataGridViewCellStyle cellStyle,
        DataGridViewAdvancedBorderStyle advancedBorderStyle,
        DataGridViewPaintParts paintParts)
    {
        // 先绘制单元格背景和边框,跳过默认文本绘制
        base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState,
            value, formattedValue, errorText, cellStyle,
            advancedBorderStyle, paintParts & ~DataGridViewPaintParts.ContentForeground);

        if (!Enabled)
            return;

        // 计算边框调整量,避免按钮覆盖单元格边框
        var borderAdjustment = BorderWidths(advancedBorderStyle);
        var usableArea = new Rectangle(
            cellBounds.X + borderAdjustment.X,
            cellBounds.Y + borderAdjustment.Y,
            cellBounds.Width - borderAdjustment.Width,
            cellBounds.Height - borderAdjustment.Height);

        // 将可用区域平均分成3份,每个按钮占一份
        int buttonWidth = usableArea.Width / _buttonTexts.Length;
        var buttonAreas = new Rectangle[_buttonTexts.Length];

        for (int i = 0; i < _buttonTexts.Length; i++)
        {
            buttonAreas[i] = new Rectangle(
                usableArea.X + i * buttonWidth,
                usableArea.Y,
                buttonWidth,
                usableArea.Height);

            // 判断按钮状态:鼠标悬停时显示热态
            var buttonState = PushButtonState.Default;
            if (DataGridView?.CurrentCell == this && DataGridView.IsCurrentCellInEditMode)
            {
                buttonState = PushButtonState.Hot;
            }

            ButtonRenderer.DrawButton(graphics, buttonAreas[i], buttonState);

            // 居中绘制按钮文本
            var textSize = TextRenderer.MeasureText(_buttonTexts[i], cellStyle.Font);
            var textLocation = new Point(
                buttonAreas[i].X + (buttonAreas[i].Width - textSize.Width) / 2,
                buttonAreas[i].Y + (buttonAreas[i].Height - textSize.Height) / 2);
            TextRenderer.DrawText(graphics, _buttonTexts[i], cellStyle.Font, textLocation, 
                Enabled ? SystemColors.ControlText : SystemColors.GrayText);
        }
    }

    protected override void OnMouseDown(DataGridViewCellMouseEventArgs e)
    {
        base.OnMouseDown(e);
        if (!Enabled || e.RowIndex < 0)
            return;

        var borderAdjustment = BorderWidths(DataGridView.AdvancedCellBorderStyle);
        var usableArea = new Rectangle(
            ContentBounds.X + borderAdjustment.X,
            ContentBounds.Y + borderAdjustment.Y,
            ContentBounds.Width - borderAdjustment.Width,
            ContentBounds.Height - borderAdjustment.Height);

        int buttonWidth = usableArea.Width / _buttonTexts.Length;
        int clickedButtonIndex = (e.X - usableArea.X) / buttonWidth;

        if (clickedButtonIndex >= 0 && clickedButtonIndex < _buttonTexts.Length)
        {
            ButtonClicked?.Invoke(this, new ButtonClickEventArgs(clickedButtonIndex, _buttonTexts[clickedButtonIndex], e.RowIndex));
        }
    }

    // 处理鼠标悬停,触发单元格重绘以更新按钮状态
    protected override void OnMouseMove(DataGridViewCellMouseEventArgs e)
    {
        base.OnMouseMove(e);
        DataGridView.InvalidateCell(this);
    }

    protected override void OnMouseLeave(int rowIndex)
    {
        base.OnMouseLeave(rowIndex);
        DataGridView.InvalidateCell(this);
    }
}

// 自定义事件参数,传递按钮信息
public class ButtonClickEventArgs : EventArgs
{
    public int ButtonIndex { get; }
    public string ButtonText { get; }
    public int RowIndex { get; }

    public ButtonClickEventArgs(int buttonIndex, string buttonText, int rowIndex)
    {
        ButtonIndex = buttonIndex;
        ButtonText = buttonText;
        RowIndex = rowIndex;
    }
}

public class CustomButtonColumn : DataGridViewColumn
{
    public CustomButtonColumn()
    {
        CellTemplate = new CustomButtonCell();
        // 设置足够容纳3个按钮的列宽
        Width = 240;
    }
}

使用说明

  1. 将代码添加到项目中
  2. 在Form中添加自定义列到DataGridView:
    var customCol = new CustomButtonColumn();
    dataGridView1.Columns.Add(customCol);
    
  3. 订阅按钮点击事件:
    ((CustomButtonCell)dataGridView1.Columns[0].CellTemplate).ButtonClicked += (sender, e) =>
    {
        MessageBox.Show($"点击了{e.ButtonText}按钮,行索引:{e.RowIndex}");
    };
    

备选方案:嵌入真实WinForms控件(复杂)

如果一定要用真实的Button控件,需要手动管理控件的创建、销毁、位置更新:

  • 重写CustomButtonCell的InitializeEditingControl方法,在编辑模式创建Panel和Buttons并添加到DataGridView
  • 处理DataGridView的Scroll、RowHeightChanged、CellEndEdit等事件,更新控件位置或销毁控件
  • 这种方式逻辑复杂,易出现控件重叠、内存泄漏问题,仅推荐进阶开发者尝试

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 19:40:34