The CurrentCellDirtyStateChanged event code will limit the number of checkboxes to (2). The rows with checked boxes contain editable data that is used to calculate values in the remaining unchecked readonly rows. I would like the datagrid to indicate editable rows by changing the color of the checked rows and have the remaining rows be readonly. The CurrentCellDirtyStateChanged event works for managing the checkboxes, but conflicts with the CellClick event for changing the row color. The boxes do not check and change color consistently. Any help would be greatly appreciated.
using System.Linq;
private void dgvParameters_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dgvParameters.CurrentCell is DataGridViewCheckBoxCell && dgvParameters.IsCurrentCellDirty && !(bool)dgvParameters.CurrentCell.FormattedValue)
{
var count = dgvParameters.Rows.Cast<DataGridViewRow>()
.SelectMany(r => r.Cells.OfType<DataGridViewCheckBoxCell>()
.Where(c => (bool)c.FormattedValue))
.Count();
if (count == 2) dgvParameters.CancelEdit();
}
}
private void dgvParameters_CellClick(object sender, DataGridViewCellEventArgs e)
{
int index = dgvParameters.CurrentRow.Index;
DataGridViewCellStyle CheckedStyle;
DataGridViewCellStyle UnCheckedStyle;
CheckedStyle = new DataGridViewCellStyle();
UnCheckedStyle = new DataGridViewCellStyle();
CheckedStyle.BackColor = Color.White;
CheckedStyle.ForeColor = Color.Black;
UnCheckedStyle.BackColor = Color.LightGray;
UnCheckedStyle.ForeColor = Color.Black;
bool isSelect = dgvParameters[5, index].Value as bool? ?? false;
if (Convert.ToBoolean(row.Cells[5].Value))
{
dgvParameters.Rows[index].Cells[1].Style = CheckedStyle;
dgvParameters.Rows[index].Cells[3].Style = CheckedStyle;
dgvParameters.Rows[index].Cells[1].ReadOnly = false;
dgvParameters.Rows[index].Cells[3].ReadOnly = false;
}
else
{
dgvParameters.Rows[index].Cells[1].Style = UnCheckedStyle;
dgvParameters.Rows[index].Cells[3].Style = UnCheckedStyle;
dgvParameters.Rows[index].Cells[1].ReadOnly = true;
dgvParameters.Rows[index].Cells[3].ReadOnly = true;
}
}
CodePudding user response:
Your question says that you would like a row that is not checked should to be ReadOnly. One obvious conflict is that if a row is truly read-only in its entireity, then there would be no way to ever check it to make it editable. My first suggestion would be to make a refreshStyles
method that takes into account which cells are DataGridViewCheckBoxCell
so that these can always be toggled regardless of the state of the row.
private void refreshStyles()
{
for (int i = 0; i < DataSource.Count; i )
{
var item = DataSource[i];
var row = dataGridView.Rows[i];
// Use System.Linq to get all of the row cells that aren't checkboxes.
var cells =
row.Cells.Cast<DataGridViewCell>()
.Where(_ => !(_ is DataGridViewCheckBoxCell));
if(item.IsChecked)
{
row.DefaultCellStyle.BackColor = Color.Azure;
foreach (var cell in cells) cell.ReadOnly = false;
}
else
{
dataGridView.Rows[i].DefaultCellStyle.BackColor =
Color.FromArgb(0xf0, 0xf0, 0xf0);
foreach (var cell in cells) cell.ReadOnly = true;
}
}
}
This is a step towards having the DGV present with a mix of read-only and editable rows.
This is all based on having the DataSource
property of the DGV set to a binding list of a class we'll call DgvItem
that is minimally implemented as shown. The reason for making the IsChecked
a binding property is in order to have the DataSource send a notification when its value changes (otherwise it only notifies when items are added or removed).
class DgvItem : INotifyPropertyChanged
{
bool _IsChecked = false;
public bool IsChecked
{
get => _IsChecked;
set
{
if (!Equals(_IsChecked, value))
{
_IsChecked = value;
OnPropertyChanged();
}
}
}
public string Description { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(
[CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Initialize
The only thing left is to glue it all together in the override
of the Load
event in MainForm
.
protected override void onl oad(EventArgs e)
{
base.OnLoad(e);
dataGridView.AllowUserToAddRows = false;
// Set the DataSource of DGV to a binding list of 'DgvItem' instances.
dataGridView.DataSource = DataSource;
for (int i = 1; i <= 5; i )
{
DataSource.Add(new DgvItem { Description = $"Item {i}" });
}
// Do a little column formatting
dataGridView.Columns[nameof(DgvItem.IsChecked)].Width = 50;
dataGridView.Columns[nameof(DgvItem.IsChecked)].HeaderText = string.Empty;
dataGridView.Columns[nameof(DgvItem.Description)].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
// Ensure the row isn't stuck in edit mode when a checkbox changes
dataGridView.CurrentCellDirtyStateChanged = (sender, e) =>
{
if(dataGridView.CurrentCell is DataGridViewCheckBoxCell)
{
dataGridView.EndEdit();
}
};
// Get notified when a checkbox changes
DataSource.ListChanged = (sender, e) =>
{
switch (e.ListChangedType)
{
case ListChangedType.ItemChanged:
// Make changes to row styles when that happens.
refreshStyles();
break;
}
};
// Init the styles
refreshStyles();
}
I hope this gives you a few ideas towards achieving the outcome you want.
CodePudding user response:
You can continue with the CurrentCellDirtyStateChanged
approach and handle the CellValueChanged
event to set the properties of the same row's cells in question according to the new value of the DataGridViewCheckBoxCell
.
First of all, make sure that you set the default values of the target cells either by the designer or code. I presume the initial value of the check box cells is false
.
public YourForm()
{
InitializeComponent();
foreach (int i in new[] { 1, 3 })
{
dgvParameters.Columns[i].DefaultCellStyle.BackColor = Color.LightGray;
dgvParameters.Columns[i].DefaultCellStyle.ForeColor = Color.Gray;
dgvParameters.Columns[i].ReadOnly = true;
}
}
Secondly, modify the CurrentCellDirtyStateChanged
event as follows...
private void dgvParameters_CurrentCellDirtyStateChanged(object sender, EventArgs e)
{
if (dgvParameters.CurrentCell is DataGridViewCheckBoxCell &&
dgvParameters.IsCurrentCellDirty)
{
if (!(bool)dgvParameters.CurrentCell.FormattedValue)
{
var count = dgvParameters.Rows.Cast<DataGridViewRow>()
.SelectMany(r => r.Cells.OfType<DataGridViewCheckBoxCell>()
.Where(c => (bool)c.FormattedValue))
.Count();
if (count == 2) dgvParameters.CancelEdit();
}
dgvParameters.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
}
Note the dgvParameters.CommitEdit(DataGridViewDataErrorContexts.Commit);
line is there to apply and see the result as you toggle the value. Comment it if you need to keep the dirty state of the current row and commit the change on cell leave.
Lastly, handle the CellValueChanged
event to update the target cells if the e.ColumnIndex
is the index of the check box cell...
private void dgvParameters_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
if (e.RowIndex >= 0 && e.ColumnIndex == 5)
{
var cell5 = dgvParameters.Rows[e.RowIndex].Cells[e.ColumnIndex];
bool isReadOnly = !(bool)cell5.FormattedValue;
Color backColor, foreColor;
if (isReadOnly)
{
backColor = Color.LightGray;
foreColor = Color.Gray;
}
else
{
backColor = Color.White;
foreColor = Color.Black;
}
foreach (int i in new[] { 1, 3 })
{
dgvParameters.Rows[e.RowIndex].Cells[i].Style.BackColor = backColor;
dgvParameters.Rows[e.RowIndex].Cells[i].Style.ForeColor = foreColor;
dgvParameters.Rows[e.RowIndex].Cells[i].ReadOnly = isReadOnly;
}
}
}
This way, you are just handling one row and you don't need to think about the rest.