昨天被问到一个问题:怎么把WinForms里的DataGrid的绑定了数据库bit字段的列默认显示的CheckBox换成“男”和“女”,也就是说怎么样像ASP.NET的模板列那样可以自定义。(此处不考虑在SQL在用Case把数据结果转换了)
由于,基本没有搞过WinForms,所以这个问题弄了很久才搞掂,可能对于WinForms高手来说,这是一个很简单的问题。(我搜了一下网页,没有找到直接的解决方案,问了一些搞过WinForms的朋友,也没有直接的解决方案,所以把我的解决方案放在博客园首页,DUDU如觉不适合,请移除。)解决这个问题的副作用就是对WinForms的机制有了一点了解。
最终实现效果:
开始的思路还是ASP.NET的思路,企图用WinForms的DataGrid的事件来实现。试了ControlAdde,BindingComplated等事件,都没有用,有些能拿到绑定时的控件,却拿不到对应的数据。
后来有朋友启发用CurrencyManager来实现,试了半天,能拿到数据,又拿不到对应的绑定生成的控件了。
晕,后来还是控件开发的思想,既然可以用DataGridTextBoxColumnStyle和DataGridBoolColumnStyle分别生成TextBox和CheckBox,为什么不可以自定义一个DataGridColumnStyle来实现自定义呢?
结果还真是可行的:
class DataGridCustomBoolColumnStyle : DataGridColumnStyle
{
private Label _customLabel = new Label();
private bool _isEditing = false;
public DataGridCustomBoolColumnStyle()
{
_customLabel.Visible = false;
_customLabel.Click +=new EventHandler(_customLabel_Click);
}
protected override void Abort(int rowNum)
{
_isEditing = false;
Invalidate();
}
void _customLabel_Click(object sender, EventArgs e)
{
Label lbl = sender as Label;
if (lbl.Text == "男")
lbl.Text = "女";
else
lbl.Text = "男";
this._isEditing = true;
base.ColumnStartedEditing(_customLabel);
}
protected override object GetColumnValueAtRow(CurrencyManager source, int rowNum)
{
object o = base.GetColumnValueAtRow(source, rowNum);
bool value = true;
if (Convert.IsDBNull(o))
value = true;
else
{
value = (bool)o;
}
return value;
}
protected override void SetDataGridInColumn(DataGrid value)
{
base.SetDataGridInColumn(value);
if (_customLabel.Parent != null)
{
_customLabel.Parent.Controls.Remove(_customLabel);
}
if (value != null)
{
value.Controls.Add(_customLabel);
}
}
protected override bool Commit(CurrencyManager dataSource, int rowNum)
{
_customLabel.Bounds = Rectangle.Empty;
_customLabel.Visible = false;
if (!_isEditing)
return true;
_isEditing = false;
try
{
bool value = (_customLabel.Text == "男");
SetColumnValueAtRow(dataSource, rowNum, value);
}
catch (Exception)
{
Abort(rowNum);
return false;
}
Invalidate();
return true;
}
protected override void Edit(CurrencyManager source, int rowNum, System.Drawing.Rectangle bounds, bool readOnly, string displayText, bool cellIsVisible)
{
bool value = (bool)GetColumnValueAtRow(source, rowNum);
if (cellIsVisible)
{
_customLabel.Bounds = new Rectangle(bounds.X + 2, bounds.Y + 2, bounds.Width - 4, bounds.Height - 4);
_customLabel.Visible = true;
}
else
{
_customLabel.Visible = false;
}
_customLabel.Text = value ? "男" : "女";
if (_customLabel.Visible)
{
DataGridTableStyle.DataGrid.Invalidate(bounds);
}
_customLabel.BackColor = Color.Red;
}
protected override int GetMinimumHeight()
{
return _customLabel.PreferredHeight + 4;
}
protected override int GetPreferredHeight(System.Drawing.Graphics g, object value)
{
return _customLabel.PreferredHeight + 4;
}
protected override System.Drawing.Size GetPreferredSize(System.Drawing.Graphics g, object value)
{
return new Size(40, _customLabel.PreferredHeight + 4);
}
protected override void Paint(System.Drawing.Graphics g, System.Drawing.Rectangle bounds, CurrencyManager source, int rowNum, System.Drawing.Brush backBrush, System.Drawing.Brush foreBrush, bool alignToRight)
{
bool value = (bool)GetColumnValueAtRow(source, rowNum);
g.FillRectangle(backBrush, bounds);
bounds.Offset(0, 2);
bounds.Height -= 2;
g.DrawString(value ? "男" : "女", DataGridTableStyle.DataGrid.Font, foreBrush, bounds);
}
protected override void Paint(Graphics g,Rectangle bounds,CurrencyManager source,int rowNum)
{
Paint(g, bounds, source, rowNum, false);
}
protected override void Paint(Graphics g, Rectangle bounds, CurrencyManager source,int rowNum,bool alignToRight)
{
Brush coreBrush = _isEditing ? Brushes.Red : Brushes.White;
Brush backBrush = _isEditing ? Brushes.Blue : Brushes.Black;
Paint(
g, bounds,
source,
rowNum,
coreBrush,
backBrush,
alignToRight);
}
}
DataGridTableStyle dgts = new DataGridTableStyle();
dgts.MappingName = this.dataSet11.Employees.TableName;
this.dataGrid1.TableStyles.Add(dgts);
GridColumnStylesCollection gcsc = dataGrid1.TableStyles[0].GridColumnStyles;
DataGridBoolColumn dgbc = gcsc[gcsc.Count - 1] as DataGridBoolColumn;
DataGridCustomBoolColumnStyle dgcbc = new DataGridCustomBoolColumnStyle();
dgcbc.MappingName = dgbc.MappingName;
gcsc.Remove(dgbc);
gcsc.Add(dgcbc);
this.employeesTableAdapter.Fill(this.dataSet11.Employees);
this.dataGrid1.DataSource = this.dataSet11.Employees;
这个实现很简单,数据与显示之间的映射是固定的,既然简单的能实现,我们再来实现个复杂的,用ComboBox来表示一些固定值的选择,比如enum和bool,因为数据库中的数据并没有enum,所以,这个DataGridComboBoxColumnStyle提供两个委托,可以数据到ComboBox项和ComboBox项到数据之间做一个处理
public delegate string FormatValueToString(object value);
public delegate object ParseStringToValue(string value);
class DataGridComboBoxColumnStyle:DataGridColumnStyle
{
public FormatValueToString FormartDelegate;
public ParseStringToValue ParseDelegate;
private bool _isEditing = false;
private ComboBox _combo = new ComboBox();
public ComboBox InnerComboBox
{
get
{
return _combo;
}
}
public DataGridComboBoxColumnStyle()
{
_combo.SelectedIndexChanged += new EventHandler(_combo_SelectedIndexChanged);
_combo.Visible = false;
}
void _combo_SelectedIndexChanged(object sender, EventArgs e)
{
this._isEditing = true;
base.ColumnStartedEditing(_combo);
}
protected override void Abort(int rowNum)
{
_isEditing = false;
Invalidate();
}
protected override void SetDataGridInColumn(DataGrid value)
{
base.SetDataGridInColumn(value);
if (_combo.Parent != null)
{
_combo.Parent.Controls.Remove(_combo);
}
if (value != null)
{
value.Controls.Add(_combo);
}
}
protected override bool Commit(CurrencyManager dataSource, int rowNum)
{
_combo.Bounds = Rectangle.Empty;
_combo.Visible = false;
if (!_isEditing)
return true;
_isEditing = false;
try
{
string value = _combo.SelectedText;
SetColumnValueAtRow(dataSource, rowNum, value);
}
catch (Exception)
{
Abort(rowNum);
return false;
}
Invalidate();
return true;
}
protected override object GetColumnValueAtRow(CurrencyManager source, int rowNum)
{
object value = base.GetColumnValueAtRow(source, rowNum);
if (Convert.IsDBNull(value))
{
value = this.NullText;
}
if (FormartDelegate != null)
{
value = FormartDelegate(value);
}
return value;
}
protected override void SetColumnValueAtRow(CurrencyManager source, int rowNum, object value)
{
if(ParseDelegate != null)
{
value = ParseDelegate((string)value);
}
base.SetColumnValueAtRow(source, rowNum, value);
}
protected override void Edit(CurrencyManager source, int rowNum, System.Drawing.Rectangle bounds, bool readOnly, string displayText, bool cellIsVisible)
{
string value = (string)GetColumnValueAtRow(source, rowNum);
if (cellIsVisible)
{
_combo.Bounds = new Rectangle(bounds.X + 2, bounds.Y + 2, bounds.Width - 4, bounds.Height - 4);
_combo.Visible = true;
}
else
{
_combo.Visible = false;
}
for (int i = 0; i < _combo.Items.Count; i++)
{
if (value == (string)_combo.Items[i])
{
_combo.SelectedIndex = i;
break;
}
}
if (_combo.Visible)
{
DataGridTableStyle.DataGrid.Invalidate(bounds);
}
}
protected override int GetMinimumHeight()
{
return _combo.PreferredHeight + 4;
}
protected override int GetPreferredHeight(System.Drawing.Graphics g, object value)
{
return _combo.PreferredHeight + 4;
}
protected override System.Drawing.Size GetPreferredSize(System.Drawing.Graphics g, object value)
{
return new Size(100, _combo.PreferredHeight + 4);
}
protected override void Paint(System.Drawing.Graphics g, System.Drawing.Rectangle bounds, CurrencyManager source, int rowNum, System.Drawing.Brush backBrush, System.Drawing.Brush foreBrush, bool alignToRight)
{
string value = (string)GetColumnValueAtRow(source, rowNum);
g.FillRectangle(backBrush, bounds);
bounds.Offset(0, 2);
bounds.Height -= 2;
g.DrawString(value, DataGridTableStyle.DataGrid.Font, foreBrush, bounds);
}
protected override void Paint(Graphics g,Rectangle bounds,CurrencyManager source,int rowNum)
{
Paint(g, bounds, source, rowNum, false);
}
protected override void Paint(Graphics g, Rectangle bounds, CurrencyManager source,int rowNum,bool alignToRight)
{
Brush coreBrush = _isEditing ? Brushes.Red : Brushes.White;
Brush backBrush = _isEditing ? Brushes.Blue : Brushes.Black;
Paint(
g, bounds,
source,
rowNum,
coreBrush,
backBrush,
alignToRight);
}
}
DataGridTableStyle dgts = new DataGridTableStyle();
dgts.MappingName = this.dataSet11.Employees.TableName;
this.dataGrid1.TableStyles.Add(dgts);
GridColumnStylesCollection gcsc = dataGrid1.TableStyles[0].GridColumnStyles;
DataGridBoolColumn dgbc = gcsc[gcsc.Count - 1] as DataGridBoolColumn;
DataGridComboBoxColumnStyle dgcbc = new DataGridComboBoxColumnStyle();
dgcbc.MappingName = dgbc.MappingName;
//这里定义ComboBOx的样式和项,因为整个ComboBox都公开了,所以随你怎么设置都行
dgcbc.InnerComboBox.Items.Add("男");
dgcbc.InnerComboBox.Items.Add("女");
dgcbc.InnerComboBox.DropDownStyle = ComboBoxStyle.DropDownList;
//这里定义数据和ComboBOx项之间如何转换
dgcbc.ParseDelegate = new ParseStringToValue(ParseStringToBool);
dgcbc.FormartDelegate = new FormatValueToString(FormatBoolToString);
gcsc.Remove(dgbc);
gcsc.Add(dgcbc);
this.employeesTableAdapter.Fill(this.dataSet11.Employees);
this.dataGrid1.DataSource = this.dataSet11.Employees;
熟悉WinForms的设计思路之后,我们又可以像用ASP.NET的DataGird一样用DataGrid了。
WinForms我是新手,请多指教。。。