需求:要求datagridview同一列,每一行显示不同的下拉表内容来提供选择列表项。另外没有下拉内容的行需要支持可输入可编辑。
然而,datagridview 自带的DatagridviewcomboboxColumn 默认的下拉样式为dropdownlist,也就是说是不支持输入的,只可选择。
下面就是对datagridview的扩展,使之支持自带combobox 随着下拉内容修改其下拉样式。
扩展的datagridview代码如下:
/// <summary>
/// 扩展的 DataGridView
/// </summary>
public class DataGridViewEx : DataGridView
{
bool _showRowHeaderNumbers;
/// <summary>
/// 是否显示行号
/// </summary>
[Description("是否显示行号"), DefaultValue(true)]
public bool ShowRowHeaderNumbers
{
get { return _showRowHeaderNumbers; }
set
{
if (_showRowHeaderNumbers != value)
Invalidate();
_showRowHeaderNumbers = value;
}
}
public DataGridViewEx()
{
_showRowHeaderNumbers = true;
}
protected override void OnEditingControlShowing(DataGridViewEditingControlShowingEventArgs e)
{
if (CurrentCell != null && CurrentCell.OwningColumn is DataGridViewComboBoxColumnEx)
{
//修改组合框的样式
var combo = e.Control as ComboBox;
if (combo != null)
{
combo.DropDownHeight =100;
if (combo.DataSource == null)
{
combo.DropDownStyle = ComboBoxStyle.DropDown;
combo.DropDownHeight = 1;
combo.Leave += new EventHandler(combo_Leave);
}
}
}
base.OnEditingControlShowing(e);
}
/// <summary>
/// 当焦点离开时,需要将新输入的值加入到组合框的 Items 列表中
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void combo_Leave(object sender, EventArgs e)
{
var combo = sender as ComboBox;
if (combo == null) return;
combo.Leave -= new EventHandler(combo_Leave);
if (CurrentCell == null || !(CurrentCell.OwningColumn is DataGridViewComboBoxColumnEx)) return;
//一定要将新输入的值加入到组合框的值列表中
//否则下一步给单元格赋值的时候会报错(因为值不在组合框的值列表中)
var cell = CurrentCell as DataGridViewComboBoxCell;
if (cell == null || cell.DataSource != null) return;
cell.Items.Add(combo.Text);
cell.Value = cell.Items[cell.Items.Count-1];
}
}
其中根据combobox的datasource绑定是否为空来判断样式是否为dropdownlist,若为dropdownlist只可选择不可编辑,若为dropdown两者皆可。为使得dropdown只允许输入,不允许选择,通过属性dropdownHeight 为1来使之看起来没有下拉框。
还需要对DataGridViewComboBoxColumn进行简单的扩展,添加一个属性字段DropDownStyle,来控制combobox的样式。
代码如下:
/// <summary>
/// 可修改 DropDownStyle 的 DataGridViewComboBoxColumn
/// </summary>
public class DataGridViewComboBoxColumnEx : DataGridViewComboBoxColumn
{
/// <summary>
/// 控制组合框的外观和功能
/// </summary>
[Description("控制组合框的外观和功能"), DefaultValue(ComboBoxStyle.DropDownList)]
public ComboBoxStyle DropDownStyle { get; set; }
public DataGridViewComboBoxColumnEx()
{
DropDownStyle = ComboBoxStyle.DropDown;
}
}
1.把扩展的datagridviewEx控件拖进来。如果已经使用了datagridview,只需要在designer中,修改InitializeComponent方法中,替换datagridview对应名称的类型为扩展类DataGridViewEx即可。
2.修改列的类型为DataGridViewComboBoxColumnEx
然后来看一下具体使用吧
在为grid赋值的时候,改变combobox的datasource或者再点击combobox列的cell的时候加载指定的datasource
这里采用的是在赋值的时候就一次加载DataGridViewComboBoxColumnEx ,每个单元格对应的datasource。
首先是一个绑定datasource的公共方法
private static void SetComboboxDataSource(DataGridView grid, int rowIndex, int columnIndex, BindingSource source)
{
var currentValue = grid.Rows[rowIndex].Cells[columnIndex].Value.ToString();
var currentCell = grid.Rows[rowIndex].Cells[columnIndex] as DataGridViewComboBoxCell;
if (currentCell != null && source != null)
{
currentCell.DataSource = source;
currentCell.DisplayMember = "Key";
currentCell.ValueMember = "Value";
}
else if (currentCell != null && !string.IsNullOrEmpty(currentValue))
{
if (currentValue == "---")
{
currentCell.ReadOnly = true;
currentCell.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
}
currentCell.Items.Add(currentValue);
currentCell.Value = currentValue;
}
}
如果datasource不为空,就为当前combobox的cell绑定datasource
如果datasource为空,当前cell有值,就将该值添加到items中,这样就不会报错,否则会说cell为无效值。
如果都为空则不操作
接下来 为某一个grid重绘,绑定来源
/// <summary>
/// 设置propertyType单元格样式和初始化的值
/// </summary>
/// <param name="grid"></param>
private void SetComboboxValue(DataGridView grid)
{
for (var i = 0; i < grid.Rows.Count; i++)
{
var rowIndex = i;
var order = Convert.ToInt32(grid.Rows[rowIndex].Cells[1].Value);
//第三列赋值
var enumData = _presenter.GetEnumData(order);
SetComboboxDataSource(grid, rowIndex, 3, enumData);
//第四列赋值
var itemValueEnumData = _presenter.GetItemValueEnumData(order);
SetComboboxDataSource(grid, rowIndex, 4, itemValueEnumData);
}
}
以上是为grid当中的两列赋值,说明我们在grid中可以引用多次DataGridViewComboBoxColumnEx ,并且赋值
最后看一下效果吧
下拉内容,只允许选择,不允许输入
无下拉内容,不出现下拉框,可输入可编辑
combobox中输入编辑内容。
最后附,初始做法
其实还有另外一种方式可以实现,就是动态的在datagridview的某一列某一行添加combobox,未添加单元格使用默认的textbox列,也就是说同列不同行具有不同的控件。
这样做的话,列表选择 和内容输入的样式区别大,外观上比较好
先看一下动态加载的代码
//var propertyTypeCell = grid.Rows[rowIndex].Cells[3];
//var combo = _presenter.GetEnumData(order);
//if (combo == null) continue;
//grid.Controls.Add(combo);
//var rec = grid.GetCellDisplayRectangle(3, rowIndex, false);
//if (propertyTypeCell.Value != null)
//{
// combo.SelectedValue = propertyTypeCell.Value.ToString();
//}
//combo.Name = rowIndex.ToString(CultureInfo.InvariantCulture);
//combo.Size = rec.Size;
//combo.Left = rec.Left;
//combo.Top = rec.Top;
//combo.Visible = true;
//combo.SelectedValueChanged += this.dgrdViewBiblio_CellEndEdit;
//combo.DropDown += this.dgrdViewBiblio_CellBeginEdit;
在penseter层生成一个带有数据源绑定的combobox
public ComboBox GetEnumData(int order)
{
var xsb = new XmlSchemaBuilder();
Dictionary<string, string> dic;
switch (order)
{
case 0:
dic = xsb.TitleEnumData();
break;
case 2:
dic = xsb.ContributorEnumData();
break;
case 3:
dic = xsb.DateEnumData();
break;
case 23:
dic = xsb.DescriptionEnumData();
break;
default:
return null;
}
var datasource = new BindingSource {DataSource = dic};
return new ComboBox() { DataSource = datasource ,DisplayMember = "Key",ValueMember = "Value"};
}
但是最后还是弃用,因为自己动态添加的combobox 和datagridview是分离的,看似在一起。实则互不干涉。
但是datagridview的很多操作需要通知combobox变化,比如说滚动条滚动 比如说删除行。
combobox的变化,比如说编辑,选择等也都需要通知datagridview。比较麻烦
以上方法,在没有滚动的情况下还是可以使用的,但是如果有滚动建议不使用。太麻烦。