昨天被问到一个问题:怎么把WinForms里的DataGrid的绑定了数据库bit字段的列默认显示的CheckBox换成“男”和“女”,也就是说怎么样像ASP.NET的模板列那样可以自定义。(此处不考虑在SQL在用Case把数据结果转换了)
由于,基本没有搞过WinForms,所以这个问题弄了很久才搞掂,可能对于WinForms高手来说,这是一个很简单的问题。(我搜了一下网页,有不少讲TableStyle和ColumnStyle的资料,但没有找到直接的解决方案,问了一些搞过WinForms的朋友,也没有直接的解决方案,所以把我的解决方案放在博客园首页,DUDU如觉不适合,请移除。)
昨天被问到一个问题:怎么把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来实现自定义呢?
结果还真是可行的:
//用Label显示"男"和"女",并且点击一次变成相反的
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项到数据之间做一个处理
//可以灵活适应各种情况的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我是新手,请多指教。。。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构