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; } }
使用说明
- 将代码添加到项目中
- 在Form中添加自定义列到DataGridView:
var customCol = new CustomButtonColumn(); dataGridView1.Columns.Add(customCol); - 订阅按钮点击事件:
((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
相关产品推荐
相关产品推荐

