只占一列的DataGridView的进度条
Progress bar for DataGridView that only accounts for one column
我在为我的进度条编写代码时遇到问题,基本上我有一个必须由用户填写的数据网格视图,我希望我的进度条仅在选中第 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 循环,它对同一个选中的复选框进行了多次计数。任何建议将不胜感激,因为我的新手想法显然失败了
菲利普,
我不太确定你的代码,但通常我会通过以下方式处理你的情况:
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);
我相信有很多方法可以做到这一点。下面是一种管理进度条的可能方法,网格列中的“选中”复选框指示进度条的当前值。
如果没有选中复选框,则进度条将为零。如果所有的复选框都被选中,那么进度条将是满的。如果选中了一半的复选框,则进度条将显示一半。
不清楚是否允许用户“添加”行。我将假设在加载网格时行是固定的,并且用户无法向网格“添加”行。否则,代码将不得不在添加行时“更新”进度条的最大值。
当前代码的小痕迹...
当前发布的代码有点奇怪,做了很多不必要的工作。对于初学者来说,for
循环看起来很奇怪,因为此代码是在网格 CurrentCellDirtyStateChanged
事件中执行的……因此,每次单个单元格值变脏或更改时都会触发此事件。所以从技术上讲,只有一 (1) 个单元格发生了变化。当只有一个复选框值可能已更改时,遍历所有复选框值似乎很奇怪。
我认为这是因为代码没有跟踪进度条的当前值。因此,每次单个复选框值更改时都需要计算此值。我相信,即使不跟踪其当前值,也有更简单的方法来更改进度条值。我想在选中复选框时将进度条加 1,未选中时减 1。
此外,当前代码“检查”值的方式很奇怪。 for
循环之前有一行…
string A = this.norms_TableDataGridView.Rows[i].Cells[2].Value.ToString();
然后,在 for
循环中有一个“检查”以查看 A
是否为“True”?
if (A == "True")
这很奇怪,因为 A
在循环中永远不会改变。如果它是 true
,那么它在循环中将始终是 true
。 false
也一样。因此,当 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--;
}
}
}
}
我希望这是有道理的。
每当您使用 windows 表单,并且决定需要直接读取或写入 DataGridView 的单元格时,您应该坐下来,深呼吸,再想想是否明智直接访问单元格而不是使用数据绑定。
人们对使用 DataBinding 犹豫不决,因为他们认为它很难,或者他们真的不知道如何使用它。
但是,如果您使用数据绑定来显示您的数据,则您将数据与其显示方式分开了。这使您能够在不更改数据的情况下更改显示。您可以将数据重复用于其他目的,并且无需 DataGridView 即可对数据进行单元测试。
显然,您有一个显示类似项目行的 DataGridView。假设 Reports
。您需要一份 class 报告。我对这份报告了解不多,只是显然它有一些可以显示为 checked/not 检查的东西:布尔值 属性.
我刚刚为示例发明的其他属性。
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();
最后的 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 的报表显示在哪一行。
我在为我的进度条编写代码时遇到问题,基本上我有一个必须由用户填写的数据网格视图,我希望我的进度条仅在选中第 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 循环,它对同一个选中的复选框进行了多次计数。任何建议将不胜感激,因为我的新手想法显然失败了
菲利普,
我不太确定你的代码,但通常我会通过以下方式处理你的情况:
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);
我相信有很多方法可以做到这一点。下面是一种管理进度条的可能方法,网格列中的“选中”复选框指示进度条的当前值。
如果没有选中复选框,则进度条将为零。如果所有的复选框都被选中,那么进度条将是满的。如果选中了一半的复选框,则进度条将显示一半。
不清楚是否允许用户“添加”行。我将假设在加载网格时行是固定的,并且用户无法向网格“添加”行。否则,代码将不得不在添加行时“更新”进度条的最大值。
当前代码的小痕迹...
当前发布的代码有点奇怪,做了很多不必要的工作。对于初学者来说,for
循环看起来很奇怪,因为此代码是在网格 CurrentCellDirtyStateChanged
事件中执行的……因此,每次单个单元格值变脏或更改时都会触发此事件。所以从技术上讲,只有一 (1) 个单元格发生了变化。当只有一个复选框值可能已更改时,遍历所有复选框值似乎很奇怪。
我认为这是因为代码没有跟踪进度条的当前值。因此,每次单个复选框值更改时都需要计算此值。我相信,即使不跟踪其当前值,也有更简单的方法来更改进度条值。我想在选中复选框时将进度条加 1,未选中时减 1。
此外,当前代码“检查”值的方式很奇怪。 for
循环之前有一行…
string A = this.norms_TableDataGridView.Rows[i].Cells[2].Value.ToString();
然后,在 for
循环中有一个“检查”以查看 A
是否为“True”?
if (A == "True")
这很奇怪,因为 A
在循环中永远不会改变。如果它是 true
,那么它在循环中将始终是 true
。 false
也一样。因此,当 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--;
}
}
}
}
我希望这是有道理的。
每当您使用 windows 表单,并且决定需要直接读取或写入 DataGridView 的单元格时,您应该坐下来,深呼吸,再想想是否明智直接访问单元格而不是使用数据绑定。
人们对使用 DataBinding 犹豫不决,因为他们认为它很难,或者他们真的不知道如何使用它。
但是,如果您使用数据绑定来显示您的数据,则您将数据与其显示方式分开了。这使您能够在不更改数据的情况下更改显示。您可以将数据重复用于其他目的,并且无需 DataGridView 即可对数据进行单元测试。
显然,您有一个显示类似项目行的 DataGridView。假设 Reports
。您需要一份 class 报告。我对这份报告了解不多,只是显然它有一些可以显示为 checked/not 检查的东西:布尔值 属性.
我刚刚为示例发明的其他属性。
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();
最后的 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 的报表显示在哪一行。