怎样让WinForms下DataGrid可以像ASP.NET下的DataGrid一样使用自定义的模板列

昨天被问到一个问题:怎么把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(
02);
            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 - 1as 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(
02);
            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 - 1as 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我是新手,请多指教。。。

posted @ 2006-09-05 15:39  Think  阅读(17194)  评论(11编辑  收藏  举报