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

C# WinForm中向DataGridView添加PictureBox控件报错解决方法

问题原因

DataGridViewCell类型没有Controls属性,控件无法直接挂载到单元格对象上,原代码中dataGrid.Rows[i].Cells[3].Controls.Add()的写法本身不成立。除此之外原代码还有两个会导致运行异常的隐性问题:

  • 仅实例化了1个查看按钮、1个删除按钮,WinForm控件不能同时归属多个父容器,就算挂载路径正确,最终按钮也只会显示在最后一行
  • for循环边界写为i <= dataGrid.Rows.Count,会触发索引越界:行索引从0开始,最大有效索引为Rows.Count - 1;如果DataGridView开启了AllowUserToAddRows,末尾自动生成的空白新行也不需要添加操作按钮。
正确实现方案

不要往单元格上挂载控件,直接把控件添加到DataGridView本身的控件集合中,在数据绑定完成后根据单元格的实际显示位置,计算每个按钮的坐标和大小,逐行生成独立的按钮实例即可。同时需要处理滚动、控件大小变化场景下的按钮位置刷新,避免错位。
修正后的完整代码如下:

private void InitializeComponent()
{
    dataGrid = new DataGridView();
    dataGrid.DataSource = this._lstWorklet;
    // 绑定事件避免滚动、调整控件尺寸时按钮错位
    dataGrid.Scroll += (s, ev) => { RefreshActionButtonsPosition(); };
    dataGrid.SizeChanged += (s, ev) => { RefreshActionButtonsPosition(); };
    dataGrid.ColumnWidthChanged += (s, ev) => { RefreshActionButtonsPosition(); };
    dataGrid.DataBindingComplete += new System.Windows.Forms.DataGridViewBindingCompleteEventHandler(dataGrid_DataBindingComplete);
}

// 统一刷新操作按钮位置
private void RefreshActionButtonsPosition()
{
    // 先清理之前生成的操作按钮,避免重复生成
    foreach (Control ctl in dataGrid.Controls.OfType<PictureBox>().Where(p => p.Tag?.ToString() == "GridActionBtn").ToList())
    {
        dataGrid.Controls.Remove(ctl);
        ctl.Dispose();
    }

    // 逐行生成按钮,跳过未提交的空白新行
    for (int i = 0; i < dataGrid.Rows.Count; i++)
    {
        DataGridViewRow row = dataGrid.Rows[i];
        if (row.IsNewRow) continue;
        
        DataGridViewCell actionCell = row.Cells[3];
        Rectangle cellRect = dataGrid.GetCellDisplayRectangle(3, i, true);
        // 不在可视区域的单元格跳过,优化性能
        if (!actionCell.Displayed || cellRect.IsEmpty) continue;

        // 为当前行生成独立的查看按钮
        PictureBox btnView = new PictureBox();
        btnView.Tag = (row.DataBoundItem, "View");
        btnView.Size = new Size(24, 24);
        btnView.BackgroundImage = ProcessingDataModel._viewImg;
        btnView.BackgroundImageLayout = ImageLayout.Center;
        btnView.Cursor = Cursors.Hand;
        btnView.Click += btnViewRecording_click;
        // 计算位置:单元格内水平边距10px,垂直居中
        btnView.Location = new Point(cellRect.X + 10, cellRect.Y + (cellRect.Height - btnView.Height)/2);
        new ToolTip().SetToolTip(btnView, "查看录音");

        // 为当前行生成独立的删除按钮
        PictureBox btnDelete = new PictureBox();
        btnDelete.Tag = (row.DataBoundItem, "Delete");
        btnDelete.Size = new Size(24, 24);
        btnDelete.BackgroundImage = ProcessingDataModel._deleteImg;
        btnDelete.BackgroundImageLayout = ImageLayout.Center;
        btnDelete.Cursor = Cursors.Hand;
        btnDelete.Click += btnDeleteRecording_click;
        // 放在查看按钮右侧,留8px间距
        btnDelete.Location = new Point(btnView.Right + 8, cellRect.Y + (cellRect.Height - btnDelete.Height)/2);
        new ToolTip().SetToolTip(btnDelete, "删除录音");

        // 按钮添加到DataGridView的控件集合,而非单元格
        dataGrid.Controls.Add(btnView);
        dataGrid.Controls.Add(btnDelete);
    }
}

private void dataGrid_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
    // 配置操作列基础属性
    dataGrid.Columns[3].Name = "Action";
    dataGrid.Columns[3].HeaderText = "操作";
    dataGrid.Columns[3].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
    // 生成操作按钮
    RefreshActionButtonsPosition();
}
点击事件取值示例

按钮的Tag中已经存储了当前行绑定的业务对象,点击时不需要额外计算行索引,直接取数据即可:

private void btnViewRecording_click(object sender, EventArgs e)
{
    if (sender is PictureBox btn && btn.Tag is (WorkletModel item, string action) && action == "View")
    {
        // 此处编写查看录音逻辑,item为当前行绑定的业务实体
        MessageBox.Show($"查看对应录音:{item.RecordPath}");
    }
}

private void btnDeleteRecording_click(object sender, EventArgs e)
{
    if (sender is PictureBox btn && btn.Tag is (WorkletModel item, string action) && action == "Delete")
    {
        if(MessageBox.Show("确认删除该条录音?", "操作提示", MessageBoxButtons.YesNo) == DialogResult.Yes)
        {
            // 此处编写删除录音逻辑,操作完成后重新绑定数据源即可
        }
    }
}

注:如果项目使用4.7以下版本的.NET Framework,不支持值元组语法,可以将行索引存在按钮Tag中,点击时通过dataGrid.Rows[索引].DataBoundItem获取对应行数据即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 23:54:22