从列的名称就可以猜测出来,选择这列表示可以多选,默认这列表示只能单选。还有一个更BT的要求,就是当第一行“所有网点”被选择以后,下面所有的行都要被选择,而当下面的所有行被选择时,第一行也要被自动选择。
对于多项选择,这是DataGridViewCheckBoxColumn的本能,不需要做过多的解析,但是单选就不是了。不知道MS为什么没有提供类似于DataGridViewRadioButtonColumn的功能,只有自己来实现。总的来说,要实现单选的功能也并不是特别复杂,可以从两个方面入手,从控件上入手或从数据源上入手。
从控件上入手比较直接,也很明了,为DataGridView的CellContentClick事件创建一个处理过程,在这个过程里判断是否是需要进行单选的列,如果是并且当前的值是已选择的状态(Value属性的值为true或CheckState.Checked),就遍历其它所有的行,并且设置相应的值为未选择。
如果要从数据源上入手,可能在实现上会复杂许多,不过条理应该更清楚,并且还可以满足我先前说的两个功能的要求。DataGridView是数据源在界面上的显示,当数据源的值更改以后,在界面上也会跟着变,那么,无论是单选还是全选的方式,都可以通过直接操纵数据源来实现界面上的效果。
我还是着重说一下数据源层面的实现方式,要让数据源的属性值变化以后界面上也跟着变,还是要我们自己做一点工作的。如果用BindingSource可以使问题简单些,假如我们的数据源实现了System.ComponentModel.INotifyPropertyChanged接口,在对象的属性值变化时引方这个接口上的PropertyChanged事件,就可以及时通知BindingSource去更新DataGridView相应的行。假如没有实现这个接口,就要使用BindingSource.ResetItem方法进行显式地更新。那么,具体怎么做呢,看一下代码:
bool _selected;
public bool Selected
{
get { return _selected; }
set
{
if (_selected != value)
{
_selected = value;
RaisePropertyChanged("Selected");
//
}
}
}
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public bool Selected
{
get { return _selected; }
set
{
if (_selected != value)
{
_selected = value;
RaisePropertyChanged("Selected");
//
}
}
}
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
在Selected属性的set方法里,如果检测到值发生改变了就引发PropertyChanged事件,然后再进行后续的处理。后续的处理可根据选择的方式进行,如果是单选,就遍历当前BindingSource里的其它数据项,并且把它们的Selected属性全部设置为false。如果是多选并且要求有“全部选择”的功能,就把其它数据项的Selected属性全部设置为true,这样就大功告成了。可能有的人看到这里会糊涂了,这个Selected属性是在哪里的,假如我的数据项里没有Selected属性怎么办?很简单哪,其实这里所用到的数据项一般情况下都不会是自己的业务逻辑对象或实体类,而是创建一个专用的类型,除了表示是否选择的Selected属性和是否默认的IsDefault属性,其它的可以根据实际需要另外添加,比如在图中的名称(Name)和地区(Area)这两个列,可以在数据项的构造函数里通过业务逻辑对象直接赋值。
以上这些实现以后,还有一个小问题。DataGridViewCheckBoxCell在点击了复选框以后,其状态不会立刻传递给相应的数据项,也就是说,Selected属性不会立即变化,这样就不会立即引发其它行上数据的变化。而只有在这个单元格退出编辑状态(焦点离开或按Enter键)时,才会引发变化。这种行为很别扭,如果给最终用户使用怕是会找不着北,我们必须让点击事件发生以后立即把新的状态提交到数据项当中。我仔细研究了DataGridViewCheckBoxCell的源代码,发现它在OnMouseUp和OnKeyUp这两个方法中更新复选框的状态,那我想只要继承这个类型,再重载这两个方法,并提交数据修改就可以了。提交数据修改可以用DataGridView.CommitEdit方法来完成。另外说一下,先前所说的在控件层面上实现单选的功能也可以在这里进行实现,下面是完整的源代码:
public class ExtendedCheckBoxColumn : DataGridViewCheckBoxColumn
{
public ExtendedCheckBoxColumn()
: base()
{
base.CellTemplate = new ExtendedCheckBoxCell();
}
bool _singleSelect;
[
DefaultValue(false),
Category("Behavior")
]
public bool SingleSelect
{
get { return _singleSelect; }
set
{
_singleSelect = value;
}
}
bool _commitOnClick = true;
[
DefaultValue(true),
Category("Behavior")
]
public bool CommitOnClick
{
get { return _commitOnClick; }
set { _commitOnClick = value; }
}
}
public class ExtendedCheckBoxCell : DataGridViewCheckBoxCell
{
public ExtendedCheckBoxCell()
{
}
protected override void OnKeyUp(KeyEventArgs e, int rowIndex)
{
base.OnKeyUp(e, rowIndex);
if (e.Handled)
SetupCheckState();
}
protected override void OnMouseUp(DataGridViewCellMouseEventArgs e)
{
base.OnMouseUp(e);
if (e.Button == MouseButtons.Left)
SetupCheckState();
}
// 这个方法是为了防止SetupCheckState重入而设置的。
bool _locked;
private void SetupCheckState()
{
DataGridView dgv = this.DataGridView;
DataGridViewRow row = this.OwningRow;
ExtendedCheckBoxColumn column = this.OwningColumn as ExtendedCheckBoxColumn;
if (dgv != null && column != null && row.Index >= 0 && !_locked)
{
_locked = true;
if (column.CommitOnClick)
{
dgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
if (column.SingleSelect)
{
object value = this.Value;
DataGridViewCheckBoxCell template = (DataGridViewCheckBoxCell)column.CellTemplate;
if (template.ThreeState && ((CheckState)value) == CheckState.Checked ||
!template.ThreeState && (bool)value)
{
if (template.ThreeState) value = CheckState.Unchecked;
else value = false;
foreach (DataGridViewRow dgvr in dgv.Rows)
{
if (dgvr.Index != row.Index && !dgvr.IsNewRow)
{
dgvr.Cells[column.Index].Value = value;
}
}
}
}
}
_locked = false;
}
}
}
{
public ExtendedCheckBoxColumn()
: base()
{
base.CellTemplate = new ExtendedCheckBoxCell();
}
bool _singleSelect;
[
DefaultValue(false),
Category("Behavior")
]
public bool SingleSelect
{
get { return _singleSelect; }
set
{
_singleSelect = value;
}
}
bool _commitOnClick = true;
[
DefaultValue(true),
Category("Behavior")
]
public bool CommitOnClick
{
get { return _commitOnClick; }
set { _commitOnClick = value; }
}
}
public class ExtendedCheckBoxCell : DataGridViewCheckBoxCell
{
public ExtendedCheckBoxCell()
{
}
protected override void OnKeyUp(KeyEventArgs e, int rowIndex)
{
base.OnKeyUp(e, rowIndex);
if (e.Handled)
SetupCheckState();
}
protected override void OnMouseUp(DataGridViewCellMouseEventArgs e)
{
base.OnMouseUp(e);
if (e.Button == MouseButtons.Left)
SetupCheckState();
}
// 这个方法是为了防止SetupCheckState重入而设置的。
bool _locked;
private void SetupCheckState()
{
DataGridView dgv = this.DataGridView;
DataGridViewRow row = this.OwningRow;
ExtendedCheckBoxColumn column = this.OwningColumn as ExtendedCheckBoxColumn;
if (dgv != null && column != null && row.Index >= 0 && !_locked)
{
_locked = true;
if (column.CommitOnClick)
{
dgv.CommitEdit(DataGridViewDataErrorContexts.Commit);
if (column.SingleSelect)
{
object value = this.Value;
DataGridViewCheckBoxCell template = (DataGridViewCheckBoxCell)column.CellTemplate;
if (template.ThreeState && ((CheckState)value) == CheckState.Checked ||
!template.ThreeState && (bool)value)
{
if (template.ThreeState) value = CheckState.Unchecked;
else value = false;
foreach (DataGridViewRow dgvr in dgv.Rows)
{
if (dgvr.Index != row.Index && !dgvr.IsNewRow)
{
dgvr.Cells[column.Index].Value = value;
}
}
}
}
}
_locked = false;
}
}
}