【问题标题】:Progress bar for DataGridView that only accounts for one column仅占一列的 DataGridView 的进度条
【发布时间】:2021-01-31 00:55:46
【问题描述】:

我在为我的进度条编写代码时遇到问题,基本上我有一个必须由用户填写的 datagridview,我希望我的进度条仅在选中第 2 列的复选框时增加(或减少时它们未选中),到目前为止,我在void norms_TableDataGridView_CurrentCellDirtyStateChanged中尝试了以下代码

        int cnt = this.norms_TableDataGridView.Rows.Count;
        int i = 0;
        string A = this.norms_TableDataGridView.Rows[i].Cells[2].Value.ToString(); //"Approved" Cells 

        for (i = 0; i < cnt; i++)
        {
            if (A == "True")
            {
                s_Checks++;
            }
       }
       progressBar.Value = s_Checks * (progressBar.Maximum / TOTAL_CHECKBOXES);
       

这使进度条以比应有的速度增加,因为对 for 循环执行操作,e 它对同一个选中复选框进行了多次计数。任何建议都将不胜感激,因为我的新手想法显然失败了

【问题讨论】:

标签: c# winforms datagridview progress-bar


【解决方案1】:

我相信有很多方法可以做到这一点。下面是管理进度条的一种可能方法,这样网格列中的“选中”复选框指示进度条的当前值。

如果未选中任何复选框,则进度条将为零。如果选中所有复选框,则进度条将已满。如果选中了一半的复选框,则进度条将显示一半填充。

尚不清楚是否允许用户“添加”行。我将假设在加载网格时行是固定的,并且用户无法将行“添加”到网格中。否则,代码必须在添加行时“更新”进度条的最大值。

当前代码的一小部分......

当前发布的代码有点奇怪,并且做了很多不必要的工作。对于初学者来说,for 循环在某种意义上看起来很奇怪,因为这段代码在网格中执行 CurrentCellDirtyStateChanged 事件……因此,每次单个单元格值变脏或更改时都会触发此事件。所以从技术上讲,只有一 (1) 个单元格发生了变化。当只有一个复选框值可能发生变化时,循环遍历所有复选框值似乎很奇怪。

我认为这是因为代码没有跟踪进度条的当前值。因此,每次单个复选框值更改时都需要计算此值。我有信心,即使不跟踪其当前值,也有一种更简单的方法可以更改进度条值。我正在考虑在选中复选框时向进度条添加 1,并在未选中时减去 1。

此外,当前代码“检查”值的方式很奇怪。 for 循环前有一行……

string A = this.norms_TableDataGridView.Rows[i].Cells[2].Value.ToString();

然后,在for 循环中有一个“检查”来查看A 是否为“真”?

if (A == "True")

这很奇怪,因为A 在循环中从不改变。如果是true,那么在循环中它总是truefalse 也一样。因此,当for 循环退出时,s_Checks++ 将始终为零或等于cnt

要解决此问题,您需要在每次循环迭代时将A“更改”为下一个复选框值。但是,即使使用此修复程序,代码仍在检查自上次检查以来未更改的值。 同样,可能有更简单的方法。

不同的方法....

我假设加载网格时,所有复选框都未选中,进度条将从零开始。此外,当网格加载数据时,进度条Maximum 的值将设置为网格中(固定)的行数。

使用这种方法,当复选框从“未选中”更改为“选中”时,代码会将进度条值“添加”1。并且,如果“选中”框“未选中”,代码将从进度条值中“减去”1。

为避免错误并保持简单,代码仅检查以确保将 1 加到进度条值不会超过其 Maximum 值。此外,检查以确保减 1 不会低于 Minimum 值。

因此,这种方法将如下所示。一旦加载了未选中所有复选框的网格数据,我们将设置进度条、最小值和最大值。像……

private void Form2_Load(object sender, EventArgs e) {
  dataGridView1.DataSource = YourDataSource;
  progressBar1.Maximum = dataGridView1.Rows.Count;
  progressBar1.Minimum = 0;
}

我没有使用网格CurrentCellDirtyStateChanged 事件,而是使用了网格CellContentClick 事件。

使用CellContentClick 事件的一个问题是,当它触发时,单元格的值还没有改变。我们可以轻松地反转检查逻辑,但是,为了使代码保持可读状态,我将调用网格 CommitEdit 来更新网格中的更改。

在这种情况下,代码会检查复选框是否被选中……在此示例中,复选框位于第 1 列中。

(bool)dataGridView1.Rows[e.RowIndex].Cells[1].Value == true

如果复选框从“未选中”变为“已选中”,则代码会将进度条值加 1,如果复选框从“选中”变为“未选中”,则代码会将其值减 1。

private void dataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) {
  if (e.ColumnIndex == 1) {
    dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit);
    if ((bool)dataGridView1.Rows[e.RowIndex].Cells[1].Value == true) {
      if (progressBar1.Value < progressBar1.Maximum) {
        progressBar1.Value++;
      }
    }
    else {
      if (progressBar1.Value > 0) {
        progressBar1.Value--;
      }
    }
  }
}

我希望这是有道理的。

【讨论】:

  • 感谢您提供的详细信息和课程,真的可以帮助像我这样不了解我编写的代码的很多含义/后果的新手。一个问题,如果有第二行可以有一个,例如,检查第 6 行或第 7 行,我将如何处理?
  • 我不明白你的问题...... “如果有第二行可能有一个,例如,第 6 行或第 7 行检查,我将如何处理? ” ...?对不起,但是,这对我来说没有意义。你能用一个例子重新表述或演示一下吗?
  • 对不起,我的意思是第二栏*
  • 我很抱歉这个愚蠢的问题,但是在if ((bool)dataGridView1.Rows[e.RowIndex].Cells[1].Value == true) 行中,我如何找出我想让进度条评估的列的索引?因为我不能让代码工作并且它没有给出任何特定的错误。我在想我只是错误地实现了你的代码
  • if (e.ColumnIndex == 1) { ` norms_TableDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);` ` if ((bool)norms_TableDataGridView.Rows[e.RowIndex].Cells[2].Value == true) ` ` {` ` if (progressBar1.Value 0)` ` {` ` progressBar1.Value--;` ` }` ` }` }
【解决方案2】:

菲利普,

我对您的代码有点不确定,但通常我会通过以下方式处理您的情况:

using System.Linq;

var checkedRows = norms_TableDataGridView.Rows.Cast<DataGridViewRow>()
                  .Count(r => Convert.ToBoolean(r.Cells[2].Value));
double allRows = norms_TableDataGridView.Rows.Count;
progressBar.Value = Math.Round((checkedRows / allRows) * 100,0); 

【讨论】:

  • 感谢您的回复!对我来说似乎很干净,一个问题,这会去哪里?到进度条功能?
  • @Filipe Almeida,这将与您的 CheckBox_Changed 事件处理程序等效,以便每次用户选中或取消选中 DataGridView 上的框时,进度条都会更新。正如我认为其他人提到的那样,利用该事件可能有点棘手,但如果它有帮助,我会更新我的答案以显示它的样子。
【解决方案3】:

每当您使用 Windows 窗体并决定需要直接读取或写入 DataGridView 的单元格时,您应该坐下来,深呼吸,然后再考虑直接访问单元格是否明智使用数据绑定。

人们对使用 DataBinding 犹豫不决,因为他们认为它很难,或者他们真的不知道如何使用它。

但是,如果您使用数据绑定来显示数据,则将数据与其显示方式分开。这使您可以更改显示,而无需更改数据。您可以将数据重复用于其他目的,并且无需 DataGridView 即可对数据进行单元测试。

显然,您有一个显示类似项目行的 DataGridView。假设Reports。你需要一份课堂报告。我对该报告了解不多,只是显然它有一些可以显示为选中/未选中的内容:布尔属性。

我刚刚为示例发明的其他属性。

public class Report
{
    public int Id {get; set;}
    public string Title {get; set;}
    public string Author {get; set;}
    public DateTime PublicationDate {get; set;}

    public bool IsRead {get; set;}   // will be a checkbox in the datagridview
}

使用 Visual Studio 设计器,您添加了一个 DataGridView 和一些列。可能是所有列,或者您可能对 PublicationDate 或作者不感兴趣。

DataGridView reportView =  new DataGridView();
DataGridViewColumn columnId = new DataGridViewColumn()
columnId.DataPropertyName = nameof(Report.Id);
DatagridViewColumn columnTitle = new DataGridViewColumn();
columnTitle.DataPropertyName = nameof(Report.Title);
DataGridViewCheckBoxColumn columnIsRead = new DataGridViewCheckBoxColumn();
columnIsRead.DataPropertyName = nameof(Report.IsRead);
// TODO: add the columns to reportView.Columns.

现在假设您有一个获取报告的方法:

private IEnumerable<report> FetchReports()
{
    ...
}

现在您所要做的就是将获取的报告分配给数据源:

private void DisplayReports()
{
    this.reportView.DataSource = this.FetchReports();
}

然后转眼!所有报表都在 DataGridView 中。

但是,这是一个方向。如果操作员更改任何内容,则它们不会反映在原始数据中。如果你想要两种方式的改变,你需要使用一个实现 IBindingList 的对象,比如BindingList

良好的做法是将以下几行添加到您的表单中:

public BindingList<Report> DisplayedReports
{
    get => (BindingList<Report>)this.reportView.DataSource;
    set => this.reportView.DataSource = value;
}

public Report CurrentReport => (Report)this.reportView.CurrentRow?.DataBoundItem;

public IEnumerable<Report> SelectedReports => this.reportView.SelectedCells
    .Cast<DataGridViewCell>()
    .Select(cell => (Report)cell.OwningRow.DataBoundItem)
    .Distinct();

最后的区别是如果您在一行中选择了两个单元格。

用法:

private void Form_Load(object sender, ...)
{
    // initialize the datagridview
    this.DisplayedReports = new BindingList<Report>(this.FetchReports().ToList());
}

当操作员添加/删除/更改显示的报告时,BindingList 会自动更新。

过了一会儿,操作员表示他完成了编辑:

private void ButtonOk_Clicked(object sender, ...)
{
    ICollection<Report> reports = this.DisplayedReports;
    // Todo: if desired: find out which reports are added / removed / changed
    this.ProcessEditedReports(reports);
}

注意:由于您将视图与数据分开,操作员可以重新排列列、对行进行排序等,而无需关心 ID 为 25 的报告显示在哪一行。

【讨论】:

  • 非常感谢您的意见!真的,我不知道实现您的解决方案所需的大部分知识,您给了我很多研究和学习的主题,再次感谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-01-12
  • 2013-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-06
  • 1970-01-01
相关资源
最近更新 更多