文章内容来自:http://social.microsoft.com/Forums/zh-CN/2212/thread/bffbfe12-ecd3-4854-956c-f02a0ec696c3
这些材料没有覆盖所有常见问题,它只是尽量涉及到在windows Forms数据控件和数据绑定论坛上经常问到的功能。
4. 可以动态的改变DataGridViewCell的类型吗?
5. 如何让DataGridViewComboBoxColumn在没有编辑的状态下显示为TextBox列?
8. 如何在DataGridView中实现归类的数据的显示?
9. 如何在DataGridView中使按下Enter键达到与按下Tab键一样的效果?
11.如何使DataGridViewCell中的‘/t’控制符起作用?
12.如何让DataGridViewTextBoxCell在编辑模式下接受Enter键和Tab键的输入?
13.如何在DataGridView的全部区域内显示网格线?
17.如何处理DataGridViewComboBoxCell的SelectedIndexChanged事件?
23.如何让最后一列足够宽以覆盖余下的DataGridView的工作区?
26.如何让DataGridViewComboBoxCell可以编辑?
27.如何实现根据DataGridViewComboBoxColumn列中单元格所选的值,在同一行的另一个DataGridViewComboBoxColumn列中单元格显示该值所对应的子集?
30.如何在同一个DataGridView中显示两张表上的数据?
34.如何在点击toolstrip button时将数据提交到数据源?
DataGridView 默认的定位模式没有禁止用户将焦点设定到特定的单元格上的功能。我们可以通过重写恰当的键盘、导航和鼠标的方法,比如DataGridView.OnKeyDown, DataGridView.ProcessDataGridViewKey, DataGridView.SetCurrentCellAddressCore, DataGridView.SetSelectedCellCore, DataGridView.OnMouseDown 来实现所需的定位逻辑。
例如,假设我们需要禁止用户将焦点设在第二列,我们可以从DataGridView 类派生一个类,并重写该类的SetCurrentCellAddressCore 和SetSelectedCellCore 方法来实现我们所需要的定位逻辑。详见以下示例:
代码:
Public class myDataGridView : DataGridView
{
private int columnToSkip = -1;
public int ColumnToSkip
{
get { return columnToSkip; }
set { columnToSkip = value ; }
}
protected override bool SetCurrentCellAddressCore(int columnIndex, int rowIndex,
bool setAnchorCellAddress, bool validateCurrentCell, bool throughMouseClick)
{
if (columnIndex == this .columnToSkip && this .columnToSkip != -1)
{
if (this .columnToSkip == this .ColumnCount - 1)
{
return base .SetCurrentCellAddressCore(0, rowIndex + 1,
setAnchorCellAddress, validateCurrentCell, throughMouseClick);
}
else
{
if (this .ColumnCount != 0)
{
return base .SetCurrentCellAddressCore(columnIndex + 1, rowIndex,
setAnchorCellAddress, validateCurrentCell, throughMouseClick);
}
}
}
return base .SetCurrentCellAddressCore(columnIndex, rowIndex,
setAnchorCellAddress, validateCurrentCell, throughMouseClick);
}
protected override void SetSelectedCellCore(int columnIndex, int rowIndex, bool selected)
{
if (columnIndex == this .columnToSkip)
{
if (this .columnToSkip == this .ColumnCount - 1)
{
base .SetSelectedCellCore(0, rowIndex + 1, selected);
}
else
{
if (this .ColumnCount != 0)
{
base .SetSelectedCellCore(columnIndex + 1, rowIndex, selected);
}
}
}
else
{
base .SetSelectedCellCore(columnIndex, rowIndex, selected);
}
}
}
相关链接:
我们可以在CellPainting事件中使用StringFormatFlag.DirectionVertical标记来绘制竖排文字。详见以下示例:
代码:
private void Form1_Load(object sender, EventArgs e)
{
DataTable dt = new DataTable();
dt.Columns.Add("c1");
dt.Columns.Add("c2");
for (int j = 0; j < 10; j++)
{
dt.Rows.Add("aaaaaaaaa", "bbbb");
}
this.dataGridView1.DataSource = dt;
for (int j = 0; j < 10; j++)
{
int height = TextRenderer.MeasureText(
this.dataGridView1[0, j].Value.ToString(),
this.dataGridView1.DefaultCellStyle.Font).Width;
this.dataGridView1.Rows[j].Height = height;
}
this.dataGridView1.CellPainting += new
DataGridViewCellPaintingEventHandler(dataGridView1_CellPainting); ;
}
void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.ColumnIndex == 0 && e.RowIndex > -1 && e.Value != null)
{
e.Paint(e.CellBounds, DataGridViewPaintParts.All
& ~DataGridViewPaintParts.ContentForeground);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
sf.FormatFlags = StringFormatFlags.DirectionVertical;
e.Graphics.DrawString(e.Value.ToString(), e.CellStyle.Font,
new SolidBrush(e.CellStyle.ForeColor), e.CellBounds, sf);
e.Handled = true;
}
}
相关链接:
DataGridView默认的列排序模式为DataGridViewColumnSortMode.Automatic。在某些情况下,开发者希望改变这种默认的排序,但又不希望通过在整个列中循环来实现,例如这个帖子http://social.msdn.microsoft.com/forums/en-US/winformsdatacontrols/thread/9c45f3ed-695f-4254-b2a2-be58832de8d3/。
我们可以从DataGridView类派生一个类并重写该类的OnColumnAdded方法来改变这种默认的模式。详见以下示例:
代码:
public class mydgv : DataGridView
{
protected override void OnColumnAdded(DataGridViewColumnEventArgs e)
{
base.OnColumnAdded(e);
e.Column.SortMode = DataGridViewColumnSortMode.NotSortable;
}
}
相关链接:
4.可以动态的改变DataGridViewCell的类型吗? [回到顶端]
开发者希望在保持ComboBox所选择的值的基础上动态的将DataGridViewComboBoxCell改变为DataGridViewTextBoxCell。单元格(cell)的类型实际上是可以被改变的,只需要重新创建一个所需要类型的单元格(cell),然后将新建的单元格(cell)赋给我们所需要改变的单元格(cell)。详见以下示例:
代码:
private void Form1_Load(object sender, EventArgs e)
{
DataTable dt = new DataTable("b");
dt.Columns.Add("col");
dt.Rows.Add("bb1");
dt.Rows.Add("bb2");
dt.Rows.Add("bb3");
this.dataGridView1.DataSource = dt;
DataGridViewComboBoxColumn cmb = new DataGridViewComboBoxColumn();
cmb.Items.Add("111");
cmb.Items.Add("222");
cmb.Items.Add("333");
this.dataGridView1.Columns.Add(cmb);
}
private void button1_Click(object sender, EventArgs e)
{
DataGridViewTextBoxCell cell = new DataGridViewTextBoxCell();
cell.Value = "bb1";
this.dataGridView1[1, 1] = cell;
}
相关链接:
http://social.msdn.microsoft.com/forums/en-US/winforms/thread/deabb9fb-16cb-4f73-be5c-ff67696bf792/
5.如何让DataGridViewComboBoxColumn在没有编辑的状态下显示为TextBox列? [回到顶端]
我们可以通过将“DataGridViewComboBoxColumn.DisplayStyle”属性设置为“DataGridViewComboBoxDisplayStyle.Nothing”来实现。
像在这个帖子中问到的:http://social.msdn.microsoft.com/forums/en-US/winformsdatacontrols/thread/531577e8-0be3-406d-a81b-48f8ed02e8df/,开发者希望将DataGridView的列标题显示多层。如下图所示:
-------------------------------------------------------------
| January | February | March |
| Win | Loss | Win | Loss | Win | Loss |
-------------------------------------------------------------
Team1 | | | | | | |
Team2 | | | | | | |
TeamN | | | | | | |
-------------------------------------------------------------
要实现这种功能,我们需要通过处理DataGridView.Painting和DataGridView.CellPainting事件来绘制我们所希望的标题样式。详见以下示例:
代码:
private void Form1_Load(object sender, EventArgs e)
{
this.dataGridView1.Columns.Add("JanWin", "Win");
this.dataGridView1.Columns.Add("JanLoss", "Loss");
this.dataGridView1.Columns.Add("FebWin", "Win");
this.dataGridView1.Columns.Add("FebLoss", "Loss");
this.dataGridView1.Columns.Add("MarWin", "Win");
this.dataGridView1.Columns.Add("MarLoss", "Loss");
for (int j = 0; j < this.dataGridView1.ColumnCount; j++)
{
this.dataGridView1.Columns[j].Width = 45;
}
this.dataGridView1.ColumnHeadersHeightSizeMode =
DataGridViewColumnHeadersHeightSizeMode.EnableResizing;
this.dataGridView1.ColumnHeadersHeight =
this.dataGridView1.ColumnHeadersHeight * 2;
this.dataGridView1.ColumnHeadersDefaultCellStyle.Alignment =
DataGridViewContentAlignment.BottomCenter;
this.dataGridView1.CellPainting += new
DataGridViewCellPaintingEventHandler(dataGridView1_CellPainting);
this.dataGridView1.Paint += new PaintEventHandler(dataGridView1_Paint);
}
void dataGridView1_Paint(object sender, PaintEventArgs e)
{
string[] monthes = { "January", "February", "March" };
for (int j = 0; j < 6; )
{
//获取列标题单元格
Rectangle r1 = this.dataGridView1.GetCellDisplayRectangle(j, -1, true);
r1.X += 1;
r1.Y += 1;
r1.Width = r1.Width * 2 - 2;
r1.Height = r1.Height / 2 - 2;
e.Graphics.FillRectangle(new
SolidBrush(this.dataGridView1.ColumnHeadersDefaultCellStyle.BackColor), r1);
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
e.Graphics.DrawString(monthes[j / 2],
this.dataGridView1.ColumnHeadersDefaultCellStyle.Font,
new SolidBrush(this.dataGridView1.ColumnHeadersDefaultCellStyle.ForeColor),
r1,
format);
j += 2;
}
}
void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (e.RowIndex == -1 && e.ColumnIndex > -1)
{
e.PaintBackground(e.CellBounds, false);
Rectangle r2 = e.CellBounds;
r2.Y += e.CellBounds.Height / 2;
r2.Height = e.CellBounds.Height / 2;
e.PaintContent(r2);
e.Handled = true;
}
}
相关链接:
在这个帖子中http://social.msdn.microsoft.com/forums/en-US/winformsdesigner/thread/c28399bb-9d50-4a1e-b671-3dbaebb5cc69/,开发者希望将Button控件和TextBox控件显示在一个单元格内。这种功能的实现可以通过创建一个承载TextBox和Button的UserControl,将UserControl添加到DataGridView的控件集合中,开始让它隐藏,然后通过处理CellBeginEdit事件在当前单元格内显示UserControl。我们也需要考虑通过处理Scroll事件来调整UserControl的位置让其正确显示,和处理CellEndEdit事件将单元格的值设置为从UserControl中获取的值。请点击帖子的链接来查看整个实例。
另一个相似的帖子,开发者希望在编辑单元格内容时显示一个下拉文本编辑器。
8.如何在DataGridView中实现归类的数据的显示? [回到顶端]
如下图所示:
要实现这种功能的关键就是处理CellPainting事件来绘制出我们所需要样式的单元格。详见以下示例:
代码:
public class GroupByGrid : DataGridView
{
protected override void OnCellFormatting(
DataGridViewCellFormattingEventArgs args)
{
// 回调父类
base.OnCellFormatting(args);
// 始终显示第一行
if (args.RowIndex == 0)
return;
if (IsRepeatedCellValue(args.RowIndex, args.ColumnIndex))
{
args.Value = string.Empty;
args.FormattingApplied = true;
}
}
private bool IsRepeatedCellValue(int rowIndex, int colIndex)
{
DataGridViewCell currCell =
Rows[rowIndex].Cells[colIndex];
DataGridViewCell prevCell =
Rows[rowIndex - 1].Cells[colIndex];
if ((currCell.Value == prevCell.Value) ||
(currCell.Value != null && prevCell.Value != null &&
currCell.Value.ToString() == prevCell.Value.ToString()))
{
return true;
}
else
{
return false;
}
}
protected override void OnCellPainting(
DataGridViewCellPaintingEventArgs args)
{
base.OnCellPainting(args);
args.AdvancedBorderStyle.Bottom =
DataGridViewAdvancedCellBorderStyle.None;
// 忽略列标头和行标头以及第一行
if (args.RowIndex < 1 || args.ColumnIndex < 0)
return;
if (IsRepeatedCellValue(args.RowIndex, args.ColumnIndex))
{
args.AdvancedBorderStyle.Top =
DataGridViewAdvancedCellBorderStyle.None;
}
else
{
args.AdvancedBorderStyle.Top = AdvancedCellBorderStyle.Top;
}
}
}
9.如何在DataGridView中使按下Enter键达到与按下Tab键一样的效果? [回到顶端]
要使按下Enter键达到与按下Tab键一样的效果,我们需要从DataGridView中派生出一个类,写一个自定义的DataGridView控件。这里有两个方面需要考虑。一方面,当DataGridView不处于编辑状态:在这种情况下,我们需要重写OnKeyDown事件来实现我们所需要的定位逻辑。另一方面,当DataGridView处于编辑的状态下:在这种情况下,Enter键是在ProcessDialogKey事件中被处理,因此我们需要重写该事件。详见以下示例:
代码:
class myDataGridView : DataGridView
{
protected override bool ProcessDialogKey(Keys keyData)
{
if (keyData == Keys.Enter)
{
int col = this.CurrentCell.ColumnIndex;
int row = this.CurrentCell.RowIndex;
if (row != this.NewRowIndex)
{
if (col == (this.Columns.Count - 1))
{
col = -1;
row++;
}
this.CurrentCell = this[col + 1, row];
}
return true;
}
return base.ProcessDialogKey(keyData);
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.KeyData == Keys.Enter)
{
int col = this.CurrentCell.ColumnIndex;
int row = this.CurrentCell.RowIndex;
if (row != this.NewRowIndex)
{
if (col == (this.Columns.Count - 1))
{
col = -1;
row++;
}
this.CurrentCell = this[col + 1, row];
}
e.Handled = true;
}
base.OnKeyDown(e);
}
}
相关链接:
当textbox单元格进入编辑状态时,EditingControl被显示在单元格中。我们可以在TextBox.KeyPress事件中,通过char.IsDigit()方法判断键盘输入是否为数字来决定是否需要过滤。详见以下示例:
代码:
public partial class DgvNumberOnlyColumn : Form
{
public DgvNumberOnlyColumn()
{
InitializeComponent();
}
private void DgvNumberOnlyColumn_Load(object sender, EventArgs e)
{
this.dataGridView1.Columns.Add("col1", "col1");
this.dataGridView1.Columns.Add("col2", "col2");
this.dataGridView1.Rows.Add();
this.dataGridView1.EditingControlShowing += new
DataGridViewEditingControlShowingEventHandler(
dataGridView1_EditingControlShowing);
}
void dataGridView1_EditingControlShowing(object sender,
DataGridViewEditingControlShowingEventArgs e)
{
if (this.dataGridView1.CurrentCell.ColumnIndex == 0)
{
if (e.Control is TextBox)
{
TextBox tb = e.Control as TextBox;
tb.KeyPress -= new KeyPressEventHandler(tb_KeyPress);
tb.KeyPress += new KeyPressEventHandler(tb_KeyPress);
}
}
}
void tb_KeyPress(object sender, KeyPressEventArgs e)
{
if (!(char.IsDigit(e.KeyChar)))
{
Keys key = (Keys)e.KeyChar;
if (!(key == Keys.Back || key == Keys.Delete))
{
e.Handled = true;
}
}
}
}
相关链接:
11.如何使DataGridViewCell中的‘/t’控制符起作用? [回到顶端]
当DataGridView在绘制单元格的值的时候,‘/t’控制符默认会被忽略。要让‘/t’控制符起作用,我们需要处理CellPainting事件来重新绘制单元格的值。
代码:
void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
e.Paint(e.CellBounds,
DataGridViewPaintParts.All & ~DataGridViewPaintParts.ContentForeground);
if (e.Value != null)
{
e.Graphics.DrawString(e.Value.ToString(),
e.CellStyle.Font,
new SolidBrush(e.CellStyle.ForeColor),
e.CellBounds.X, e.CellBounds.Y);
}
e.Handled = true;
}
相关链接:
12.如何让DataGridViewTextBoxCell在编辑模式下接受Enter键和Tab键的输入? [回到顶端]
让DataGridViewTextBoxCell在编辑模式下接受Enter键和Tab键输入,我们需要从DataGridView类中派生一个类,重写该类的ProcessDataGridViewKey事件来处理所接收的Enter键和Tab键。
代码:
class zxyDataGridView : DataGridView
{
protected override bool ProcessDataGridViewKey(KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Tab:
return false;
case Keys.Enter:
{
if (this.EditingControl != null)
{
if (this.EditingControl is TextBox)
{
TextBox tx = this.EditingControl as TextBox;
int tmp = tx.SelectionStart;
tx.Text = tx.Text.Insert(tx.SelectionStart,
Environment.NewLine);
tx.SelectionStart = tmp + Environment.NewLine.Length;
return true;
}
}
}
return false;
}
return base.ProcessDataGridViewKey(e);
}
}
我们也需要将TextBox的AcceptsTab属性设置为true使得DataGridViewTextBoxCell在编辑模式下接受Tab键输入。
代码:
void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
if (e.Control is TextBox)
{
TextBox tb = e.Control as TextBox;
tb.AcceptsTab = true;
}
}
相关链接:
13.如何在DataGridView的全部区域内显示网格线? [回到顶端]
默认的情况下,当DataGridView的区域大于所需要显示数据的区域时,DataGridView将多余的部分显示灰色背景。要在DataGridView的所有区域内都显示网格线,我们可以通过继承DataGridView类,重写该类的OnPaint事件,在没有数据的部分也同样绘制网格线。详见以下示例:
代码:
public class GridLineDataGridView : DataGridView
{
public GridLineDataGridView()
{
this.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
int rowHeight = this.RowTemplate.Height;
int h = this.ColumnHeadersHeight + rowHeight * this.RowCount;
int imgWidth = this.Width - 2;
Rectangle rFrame = new Rectangle(0, 0, imgWidth, rowHeight);
Rectangle rFill = new Rectangle(1, 1, imgWidth - 2, rowHeight);
Rectangle rowHeader = new Rectangle(2, 2, this.RowHeadersWidth - 3, rowHeight);
Pen pen = new Pen(this.GridColor, 1);
Bitmap rowImg = new Bitmap(imgWidth, rowHeight);
Graphics g = Graphics.FromImage(rowImg);
g.DrawRectangle(pen, rFrame);
g.FillRectangle(new SolidBrush(this.DefaultCellStyle.BackColor), rFill);
g.FillRectangle(new SolidBrush
(this.RowHeadersDefaultCellStyle.BackColor), rowHeader);
int w = this.RowHeadersWidth - 1;
for (int j = 0; j < this.ColumnCount; j++)
{
g.DrawLine(pen, new Point(w, 0), new Point(w, rowHeight));
w += this.Columns[j].Width;
}
int loop = (this.Height - h) / rowHeight;
for (int j = 0; j < loop + 1; j++)
{
e.Graphics.DrawImage(rowImg, 1, h + j * rowHeight);
}
}
}
相关链接:
http://social.msdn.microsoft.com/forums/en-US/winforms/thread/141aef69-b7c4-412f-a067-bc4bce011167/
http://social.msdn.microsoft.com/forums/en-US/winforms/thread/d39e565e-cf33-45b9-993c-99d39813fd15/
ReadOnly属性指示在单元格中的数据能否被修改。我们可以将单个单元格设置成只读,也可以通过设置DataGridViewRow.ReadOnly或者DataGridViewColumn.Readonly属性将整个列或者整行的单元格设置为只读。默认情况下,如果单元格所处在的行或者列被设置为只读,这些单元格也将会是只读的。
我们也可以动态的设置只读的单元格,例如将当前单元格设置为只读,那么表中所有单元格的所有内容都将不能被用户编辑。注意ReadOnly属性不能限制用户通过编程来修改单元格中的内容,也不会影响用户删除行。
只读可以阻止单元格被编辑,但是DataGridView没有内置功能禁用一个单元格。通常“禁用”表示用户不能定位到它和视觉上的禁用。禁止动态的定位到禁用的单元格是比较难做到的,但是可以做到视觉上的禁用。由于内置的单元格没有禁用的属性,接下来的示例扩展了DataGridViewButtonCell,让它可以通过设置disable属性来实现视觉上的“禁用”。
代码:
public class DataGridViewDisableButtonColumn : DataGridViewButtonColumn
{
public DataGridViewDisableButtonColumn()
{
this.CellTemplate = new DataGridViewDisableButtonCell();
}
}
public class DataGridViewDisableButtonCell : DataGridViewButtonCell
{
private bool enabledValue;
public bool Enabled
{
get
{
return enabledValue;
}
set
{
enabledValue = value;
}
}
// 重写Clone方法使得被修改的属性可以被复制
public override object Clone()
{
DataGridViewDisableButtonCell cell =
(DataGridViewDisableButtonCell)base.Clone();
cell.Enabled = this.Enabled;
return cell;
}
// 默认的情况下,button cell 为可用的
public DataGridViewDisableButtonCell()
{
this.enabledValue = true;
}
protected override void Paint(Graphics graphics,
Rectangle clipBounds, Rectangle cellBounds, int rowIndex,
DataGridViewElementStates elementState, object value,
object formattedValue, string errorText,
DataGridViewCellStyle cellStyle,
DataGridViewAdvancedBorderStyle advancedBorderStyle,
DataGridViewPaintParts paintParts)
{
// 如果是禁用的,绘制边框、背景和单元格中禁用的button
if (!this.enabledValue)
{
// 绘制被指定的单元格的背景
if ((paintParts & DataGridViewPaintParts.Background) ==
DataGridViewPaintParts.Background)
{
SolidBrush cellBackground =
new SolidBrush(cellStyle.BackColor);
graphics.FillRectangle(cellBackground, cellBounds);
cellBackground.Dispose();
}
// 绘出指定的单元格的边框
if ((paintParts & DataGridViewPaintParts.Border) ==
DataGridViewPaintParts.Border)
{
PaintBorder(graphics, clipBounds, cellBounds, cellStyle,
advancedBorderStyle);
}
// 计算绘制button的区域
Rectangle buttonArea = cellBounds;
Rectangle buttonAdjustment =
this.BorderWidths(advancedBorderStyle);
buttonArea.X += buttonAdjustment.X;
buttonArea.Y += buttonAdjustment.Y;
buttonArea.Height -= buttonAdjustment.Height;
buttonArea.Width -= buttonAdjustment.Width;
// 绘制禁用button
ButtonRenderer.DrawButton(graphics, buttonArea,
PushButtonState.Disabled);
// 绘制禁用button的文本
if (this.FormattedValue is String)
{
TextRenderer.DrawText(graphics,
(string)this.FormattedValue,
this.DataGridView.Font,
buttonArea, SystemColors.GrayText);
}
}
else
{
// 如果button cell为可用的,执行基类的Paint方法
base.Paint(graphics, clipBounds, cellBounds, rowIndex,
elementState, value, formattedValue, errorText,
cellStyle, advancedBorderStyle, paintParts);
}
}
}
16.如何使所有的单元格无论是否处于编辑状态都显示控件? [回到顶端]
DataGridView控件只支持在单元格处于编辑状态时显示一个真实的控件。DataGridView不支持在一行中显示多个控件。当单元格不处于编辑状态时,DataGridView只绘制控件的外观。
我们也可以通过DataGridView.Controls.Add()方法将控件添加到DataGridView,设置他们的位置和大小让他们寄放在单元格中,但是在非编辑状态下所有的单元格中都显示控件是毫无意义的。
17.如何处理DataGridViewComboBoxCell的SelectedIndexChanged事件? [回到顶端]
有时,获知用户在ComboBox编辑控件中已经选中一项是很有必要的。我们可以在DataGridView.EditingControlShowing事件中对DataGridViewComboBox进行一些处理来实现。接下来的示例演示了如何实现这种功能。注意这个示例同时也演示了如何防止引发多重SelectedIndexChanged事件。
代码:
private void dataGridView1_EditingControlShowing(object sender,
DataGridViewEditingControlShowingEventArgs e)
{
ComboBox cb = e.Control as ComboBox;
if (cb != null)
{
// 首先移除事件处理程序以防止多重触发附加事件
cb.SelectedIndexChanged -= new
EventHandler(cb_SelectedIndexChanged);
// 附加事件处理程序
cb.SelectedIndexChanged += new
EventHandler(cb_SelectedIndexChanged);
}
}
void cb_SelectedIndexChanged(object sender, EventArgs e)
{
MessageBox.Show("Selected index changed");
}
DataGridView控件没有内置的功能实现在一个单元格中显示图标和文字。我们可以通过处理相关的绘制来得到我们想要的效果。
接下来的示例演示了扩展DataGridViewTextColumn和cell来同时绘制文字和图像。该示例通过设置DataGridViewCellStyle.Padding属性来调整文本的位置,重写Paint事件来绘制图像。这个示例也可以简化为在CellPainting事件中进行处理。
代码:
public class TextAndImageColumn : DataGridViewTextBoxColumn
{
private Image imageValue;
private Size imageSize;
public TextAndImageColumn()
{
this.CellTemplate = new TextAndImageCell();
}
public override object Clone()
{
TextAndImageColumn c = base.Clone() as TextAndImageColumn;
c.imageValue = this.imageValue;
c.imageSize = this.imageSize;
return c;
}
public Image Image
{
get { return this.imageValue; }
set
{
if (this.Image != value)
{
this.imageValue = value;
this.imageSize = value.Size;
if (this.InheritedStyle != null)
{
Padding inheritedPadding = this.InheritedStyle.Padding;
this.DefaultCellStyle.Padding = new Padding(imageSize.Width,
inheritedPadding.Top, inheritedPadding.Right,
inheritedPadding.Bottom);
}
}
}
}
private TextAndImageCell TextAndImageCellTemplate
{
get { return this.CellTemplate as TextAndImageCell; }
}
internal Size ImageSize
{
get { return imageSize; }
}
}
public class TextAndImageCell : DataGridViewTextBoxCell
{
private Image imageValue;
private Size imageSize;
public override object Clone()
{
TextAndImageCell c = base.Clone() as TextAndImageCell;
c.imageValue = this.imageValue;
c.imageSize = this.imageSize;
return c;
}
public Image Image
{
get
{
if (this.OwningColumn == null ||
this.OwningTextAndImageColumn == null)
{
return imageValue;
}
else if (this.imageValue != null)
{
return this.imageValue;
}
else
{
return this.OwningTextAndImageColumn.Image;
}
}
set
{
if (this.imageValue != value)
{
this.imageValue = value;
this.imageSize = value.Size;
Padding inheritedPadding = this.InheritedStyle.Padding;
this.Style.Padding = new Padding(imageSize.Width,
inheritedPadding.Top, inheritedPadding.Right,
inheritedPadding.Bottom);
}
}
}
protected override void Paint(Graphics graphics, Rectangle clipBounds,
Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState,
object value, object formattedValue, string errorText,
DataGridViewCellStyle cellStyle,
DataGridViewAdvancedBorderStyle advancedBorderStyle,
DataGridViewPaintParts paintParts)
{
// 绘制基本内容
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState,
value, formattedValue, errorText, cellStyle,
advancedBorderStyle, paintParts);
if (this.Image != null)
{
// 绘制单元格上的图片
System.Drawing.Drawing2D.GraphicsContainer container =
graphics.BeginContainer();
graphics.SetClip(cellBounds);
graphics.DrawImageUnscaled(this.Image, cellBounds.Location);
graphics.EndContainer(container);
}
}
private TextAndImageColumn OwningTextAndImageColumn
{
get { return this.OwningColumn as TextAndImageColumn; }
}
}
在一些情况下开发者不希望显示DataGridView中的一些列。例如,开发者希望对特定用户显示员工薪水,而对其他的用户不显示。我们可以通过编程或者是设计器来实现。
通过编程实现隐藏一列:
在DataGridView控件中,设置列的Visible属性为false来隐藏列。
通过设计器实现隐藏一列:
1)在点击DataGridView的智能标记,选择编辑列。
2)在选择列的列表中,选择一列。
3)在列属性的表单中,设置Visible属性为false。
在DataGridView控件中,文本列默认是自动排序,其他类型的列不支持自动排序。在一些情况下开发者希望修改这些默认设置。
在DataGridView控件中,一个列的SortMode属性决定这列的排序方式,因此我们可以通过修改列的SortMode为DataGridViewColumnSortMode.NotSortable来阻止用户对这列排序。
默认情况下,DataGridView控件没有在多个列上的排序的功能。我们可以通过编程来实现多列排序,这里根据DataGridView是否为数据绑定而有所不同。
21.1 数据绑定的DataGridView
如果DataGridView是绑定到数据源的,是可以多行排序的,但是只有排序的第一列显示排序的标志符号。另外,SortedColumn属性只会返回第一个排序的列的信息。
有些数据源本身就支持多列排序。如果数据源实现了IBindingListView接口并且提供了对Sort属性的支持,那么通过设置Sort属性就能实现多列排序。我们可以手动的设置列的SortGlyhDirection来指示排序的列。
接下来的示例使用一个张数据表,设置列的Sort属性使第二列和第三列排序。这个示例也同样演示了如何设置列的SortGlyhDirection。这个示例是假设你窗体上添加了DataGridView和BindingSource组件:
代码:
DataTable dt = new DataTable();
dt.Columns.Add("C1", typeof(int));
dt.Columns.Add("C2", typeof(string));
dt.Columns.Add("C3", typeof(string));
dt.Rows.Add(1, "1", "Test1");
dt.Rows.Add(2, "2", "Test2");
dt.Rows.Add(2, "2", "Test1");
dt.Rows.Add(3, "3", "Test3");
dt.Rows.Add(4, "4", "Test4");
dt.Rows.Add(4, "4", "Test3");
DataView view = dt.DefaultView;
view.Sort = "C2 ASC, C3 ASC";
bindingSource.DataSource = view;
DataGridViewTextBoxColumn col0 = new DataGridViewTextBoxColumn();
col0.DataPropertyName = "C1";
dataGridView1.Columns.Add(col0);
col0.SortMode = DataGridViewColumnSortMode.Programmatic;
col0.HeaderCell.SortGlyphDirection = SortOrder.None;
DataGridViewTextBoxColumn col1 = new DataGridViewTextBoxColumn();
col1.DataPropertyName = "C2";
dataGridView1.Columns.Add(col1);
col1.SortMode = DataGridViewColumnSortMode.Programmatic;
col1.HeaderCell.SortGlyphDirection = SortOrder.Ascending;
DataGridViewTextBoxColumn col2 = new DataGridViewTextBoxColumn();
col2.DataPropertyName = "C3";
dataGridView1.Columns.Add(col2);
col2.SortMode = DataGridViewColumnSortMode.Programmatic;
col2.HeaderCell.SortGlyphDirection = SortOrder.Ascending;
.2未绑定的DataGridView
在未绑定的DataGridView的情况下实现多行排序,我们可以处理SortCompare事件或者通过重载Sort方法(IComparer)来实现更加复杂、灵活的排序。
21.2.1使用SortCompare事件实现自定义的排序
接下来的示例演示了使用SortCompare处理程序来实现自定义的排序。被选择的DataGridViewColumn将会被排序,如果在这一列中有相同的值,ID列将决定最终的排序顺序。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
class Form1 : Form
{
private DataGridView dataGridView1 = new DataGridView();
[STAThreadAttribute()]
static void Main()
{
System.Net.Mime.MediaTypeNames.Application.EnableVisualStyles();
Application.Run(new Form1());
}
public Form1()
{
// 窗体初始化
dataGridView1.AllowUserToAddRows = false;
dataGridView1.Dock = DockStyle.Fill;
dataGridView1.SortCompare += new DataGridViewSortCompareEventHandler(
this.dataGridView1_SortCompare);
Controls.Add(this.dataGridView1);
this.Text = "DataGridView.SortCompare demo";
PopulateDataGridView();
}
// 可以替换DataGridView部署的代码
public void PopulateDataGridView()
{
// DataGridView中添加列
dataGridView1.ColumnCount = 3;
// 设置DataGridView的属性
dataGridView1.Columns[0].Name = "ID";
dataGridView1.Columns[1].Name = "Name";
dataGridView1.Columns[2].Name = "City";
dataGridView1.Columns["ID"].HeaderText = "ID";
dataGridView1.Columns["Name"].HeaderText = "Name";
dataGridView1.Columns["City"].HeaderText = "City";
// DataGridView中添加行
dataGridView1.Rows.Add(new string[] { "1", "Parker", "Seattle" });
dataGridView1.Rows.Add(new string[] { "2", "Parker", "New York" });
dataGridView1.Rows.Add(new string[] { "3", "Watson", "Seattle" });
dataGridView1.Rows.Add(new string[] { "4", "Jameson", "New Jersey" });
dataGridView1.Rows.Add(new string[] { "5", "Brock", "New York" });
dataGridView1.Rows.Add(new string[] { "6", "Conner", "Portland" });
// 自动调整列的大小
dataGridView1.AutoResizeColumns();
}
private void dataGridView1_SortCompare(object sender,
DataGridViewSortCompareEventArgs e)
{
// 在当前列的排序
e.SortResult = System.String.Compare(
e.CellValue1.ToString(), e.CellValue2.ToString());
// 如果单元格值相等,根据ID列排序
if (e.SortResult == 0 && e.Column.Name != "ID")
{
e.SortResult = System.String.Compare(
dataGridView1.Rows[e.RowIndex1].Cells["ID"].Value.ToString(),
dataGridView1.Rows[e.RowIndex2].Cells["ID"].Value.ToString());
}
e.Handled = true;
}
}
21.2.2使用IComparer接口实现自定义的排序
接下来的示例演示了如何通过实现IComparer接口重写Sort方法来实现多重排序。
using System;
using System.Drawing;
using System.Windows.Forms;
class Form1 : Form
{
private DataGridView DataGridView1 = new DataGridView();
private FlowLayoutPanel FlowLayoutPanel1 = new FlowLayoutPanel();
private Button Button1 = new Button();
private RadioButton RadioButton1 = new RadioButton();
private RadioButton RadioButton2 = new RadioButton();
[STAThreadAttribute()]
public static void Main()
{
Application.Run(new Form1());
}
public Form1()
{
// 窗体初始化
AutoSize = true;
Text = "DataGridView IComparer sort demo";
FlowLayoutPanel1.FlowDirection = FlowDirection.TopDown;
FlowLayoutPanel1.Location = new System.Drawing.Point(304, 0);
FlowLayoutPanel1.AutoSize = true;
FlowLayoutPanel1.Controls.Add(RadioButton1);
FlowLayoutPanel1.Controls.Add(RadioButton2);
FlowLayoutPanel1.Controls.Add(Button1);
Button1.Text = "Sort";
RadioButton1.Text = "Ascending";
RadioButton2.Text = "Descending";
RadioButton1.Checked = true;
Controls.Add(FlowLayoutPanel1);
Controls.Add(DataGridView1);
}
protected override void OnLoad(EventArgs e)
{
PopulateDataGridView();
Button1.Click += new EventHandler(Button1_Click);
base.OnLoad(e);
}
// 可以替换DataGridView部署的代码
private void PopulateDataGridView()
{
DataGridView1.Size = new Size(300, 300);
// DataGridView中添加列
DataGridView1.ColumnCount = 2;
//设置DataGridView列的属性
DataGridView1.Columns[0].Name = "First";
DataGridView1.Columns[1].Name = "Last";
DataGridView1.Columns["First"].HeaderText = "First Name";
DataGridView1.Columns["Last"].HeaderText = "Last Name";
DataGridView1.Columns["First"].SortMode =
DataGridViewColumnSortMode.Programmatic;
DataGridView1.Columns["Last"].SortMode =
DataGridViewColumnSortMode.Programmatic;
// DataGridView中添加行
DataGridView1.Rows.Add(new string[] { "Peter", "Parker" });
DataGridView1.Rows.Add(new string[] { "James", "Jameson" });
DataGridView1.Rows.Add(new string[] { "May", "Parker" });
DataGridView1.Rows.Add(new string[] { "Mary", "Watson" });
DataGridView1.Rows.Add(new string[] { "Eddie", "Brock" });
}
private void Button1_Click(object sender, EventArgs e)
{
if (RadioButton1.Checked == true)
{
DataGridView1.Sort(new RowComparer(SortOrder.Ascending));
}
else if (RadioButton2.Checked == true)
{
DataGridView1.Sort(new RowComparer(SortOrder.Descending));
}
}
private class RowComparer : System.Collections.IComparer
{
private static int sortOrderModifier = 1;
public RowComparer(SortOrder sortOrder)
{
if (sortOrder == SortOrder.Descending)
{
sortOrderModifier = -1;
}
else if (sortOrder == SortOrder.Ascending)
{
sortOrderModifier = 1;
}
}
public int Compare(object x, object y)
{
DataGridViewRow DataGridViewRow1 = (DataGridViewRow)x;
DataGridViewRow DataGridViewRow2 = (DataGridViewRow)y;
// 根据Last Name列排序
int CompareResult = System.String.Compare(
DataGridViewRow1.Cells[1].Value.ToString(),
DataGridViewRow2.Cells[1].Value.ToString());
// 如果Last Name值相等,则根据First Name的值排序
if (CompareResult == 0)
{
CompareResult = System.String.Compare(
DataGridViewRow1.Cells[0].Value.ToString(),
DataGridViewRow2.Cells[0].Value.ToString());
}
return CompareResult * sortOrderModifier;
}
}
}
DataGridView没有内置的功能实现拖拽和拖放来重新排序行。但是使用以下的拖拽和拖放的代码可以很容易让DataGridView实现这种功能。请在窗体上添加name为dataGridView1的DataGridView控件,DataGridView的AllowDrop属性为true,并确保所需的事件与对应的事件处理程序相关联。
代码:
private Rectangle dragBoxFromMouseDown;
private int rowIndexFromMouseDown;
private int rowIndexOfItemUnderMouseToDrop;
private void dataGridView1_MouseMove(object sender, MouseEventArgs e)
{
if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
{
// 如果鼠标移动到矩形框外面,开始拖拽.
if (dragBoxFromMouseDown != Rectangle.Empty &&
!dragBoxFromMouseDown.Contains(e.X, e.Y))
{
// 执行拖拽,传入数据
DragDropEffects dropEffect = dataGridView1.DoDragDrop(
dataGridView1.Rows[rowIndexFromMouseDown],
DragDropEffects.Move);
}
}
}
private void dataGridView1_MouseDown(object sender, MouseEventArgs e)
{
// 获取鼠标按下时行的索引值.
rowIndexFromMouseDown = dataGridView1.HitTest(e.X, e.Y).RowIndex;
if (rowIndexFromMouseDown != -1)
{
// 记录按下鼠标的点
// DragSize指示在开始拖动操作前鼠标可以移动的范围
Size dragSize = SystemInformation.DragSize;
// 用DragSize创建一个以鼠标为中心的矩形
dragBoxFromMouseDown = new Rectangle(new Point(e.X - (dragSize.Width / 2),
e.Y - (dragSize.Height / 2)),
dragSize);
}
else
// 如果鼠标不在ListBox上,重置矩形
dragBoxFromMouseDown = Rectangle.Empty;
}
private void dataGridView1_DragOver(object sender, DragEventArgs e)
{
e.Effect = DragDropEffects.Move;
}
private void dataGridView1_DragDrop(object sender, DragEventArgs e)
{
// 由于鼠标的位置是与屏幕相关的,它必须转换为与客户端相匹配的点
Point clientPoint = dataGridView1.PointToClient(new Point(e.X, e.Y));
// 获取鼠标按下时的行的索引值
rowIndexOfItemUnderMouseToDrop =
dataGridView1.HitTest(clientPoint.X, clientPoint.Y).RowIndex;
// 如果拖放的行为为从将某一行移动然后移除、插入到某一行
if (e.Effect == DragDropEffects.Move)
{
DataGridViewRow rowToMove = e.Data.GetData(
typeof(DataGridViewRow)) as DataGridViewRow;
dataGridView1.Rows.RemoveAt(rowIndexFromMouseDown);
dataGridView1.Rows.Insert(rowIndexOfItemUnderMouseToDrop, rowToMove);
}
}
23.如何让最后一列足够宽以覆盖余下的DataGridView的工作区? [回到顶端]
将最后一列的AutoSizeMode设置为Fill,最后一列将会调整自己的大小填充到DataGridView余下的工作区域。另外,我们也可以通过设置最后一列的MinimumWidth以避免列的宽度太窄。
默认情况下,DataGridViewTextBoxCell中的文字不转行。这个可以通过设置单元格的WrapMode属性来控制。例如,将DataGridViewCellStyle.WrapMode 属性设置为 DataGridViewTriState.True 便可实现单元格中文本的自动换行效果。
代码:
this.dataGridView1.DefaultCellStyle.WrapMode = DataGridViewTriState.True;
默认的情况下,image列或单元格将null值显示为红色的“X”图标。我们可以通过设置列的NullValue属性为null让其不显示任何图片。
代码:
this.dataGridViewImageColumn1.DefaultCellStyle.NullValue = null;
26.如何让DataGridViewComboBoxCell可以编辑? [回到顶端]
默认情况下,DataGridViewComboBoxCell是不支持在单元格中输入的。然而在实际中常常有这种需求。要实现这种功能,有两个方面需要考虑。一方面,为了使用户可以在组合框中输入需要将ComboBox editing control的DropDownStyle设置为DropDown。另一方面,我们需要确认用户输入的值是否存在于ComboBox的项集合中。这是因为组合框单元格的值必须存在于项集合中,否则就会抛出异常。最恰当的做法是,在CellValidating时间处理函数中将值添加到项集合中去。详见以下代码:
代码:
private void dataGridView1_CellValidating(object sender,
DataGridViewCellValidatingEventArgs e)
{
if (e.ColumnIndex == comboBoxColumn.DisplayIndex)
{
if (!this.comboBoxColumn.Items.Contains(e.FormattedValue))
{
this.comboBoxColumn.Items.Add(e.FormattedValue);
}
}
}
private void dataGridView1_EditingControlShowing(object sender,
DataGridViewEditingControlShowingEventArgs e)
{
if (this.dataGridView1.CurrentCellAddress.X == comboBoxColumn.DisplayIndex)
{
ComboBox cb = e.Control as ComboBox;
if (cb != null)
{
cb.DropDownStyle = ComboBoxStyle.DropDown;
}
}
}
27.如何实现根据DataGridViewComboBoxColumn列中单元格所选的值,在同一行的另一个DataGridViewComboBoxColumn列中单元格显示该值所对应的子集? [回到顶端]
有时我们需要在DataGridView中显示两张有联系的表,比如一个类别和子类别。当用户选择类别时同时筛选子类别的数据。这个功能可以通过两个DataGridViewComboBoxColumn来实现。要实现这种功能,需要创建两个不同的筛选列表。一张列表没有经过筛选,另一张表在用户编辑子类别时被筛选。这是因为DataGridViewComboBoxCell的值必须存在于项集合中,否则会抛出异常。即然这样,由于ComboBoxCell都使用同样的数据源,如果我们对某个ComboBoxCell筛选数据源,另一个ComboBoxCell可能会在经过筛选数据源中搜索不到它所显示的数据而引发DataError事件。
接下来的示例使用Northwind数据库中两张有关联的数据表Territory表和Region表(一个Region属于一个特定Territory)。使用类别和子类别的概念,Region是类别,Territory是子类别。
代码:
private void Form1_Load(object sender, EventArgs e)
{
this.territoriesTableAdapter.Fill(this.northwindDataSet.Territories);
this.regionTableAdapter.Fill(this.northwindDataSet.Region);
// 为筛选的查看创建BindingSource
filteredTerritoriesBS = new BindingSource();
DataView dv = new DataView(northwindDataSet.Tables["Territories"]);
filteredTerritoriesBS.DataSource = dv;
}
private void dataGridView1_CellBeginEdit(object sender,
DataGridViewCellCancelEventArgs e)
{
if (e.ColumnIndex == territoryComboBoxColumn.Index)
{
// 将ComboBox单元格的数据源设定为筛选后的数据源
DataGridViewComboBoxCell dgcb = (DataGridViewComboBoxCell)dataGridView1
[e.ColumnIndex, e.RowIndex];
dgcb.DataSource = filteredTerritoriesBS;
// 根据region的选择筛选数据源
this.filteredTerritoriesBS.Filter = "RegionID = " +
this.dataGridView1[e.ColumnIndex - 1, e.RowIndex].Value.ToString();
}
}
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if (e.ColumnIndex == this.territoryComboBoxColumn.Index)
{
// 重新设置combobox单元格的数据源为未经筛选的数据源
DataGridViewComboBoxCell dgcb = (DataGridViewComboBoxCell)dataGridView1
[e.ColumnIndex, e.RowIndex];
dgcb.DataSource = territoriesBindingSource; //未筛选的数据源
this.filteredTerritoriesBS.RemoveFilter();
}
}
当用户在单元格中输入不符合规范的数据时,您或许会希望显示一些错误信息或图标来提示用户更正输入。然而即使设置了ErrorText属性,在编辑状态下,如果用户输入不符合规范的数据,错误提示图标也不会显示。
接下来的示例演示了通过在CellValidating事件中设置单元格的边距来给错误图标提供显示区域。由于边距默认情况下会影响错误图标(error icon)的定位,示例中通过CellPainting事件来处理绘制图标的位置。最后当鼠标移动到单元格时,用tooltip显示出错信息。示例同样也可以作为重写GetErrorIconBounds事件来实现与边距无关的错误图标定位的自定义的单元格。
代码:
private ToolTip errorTooltip;
private Point cellInError = new Point(-2, -2);
public Form1()
{
InitializeComponent();
dataGridView1.ColumnCount = 3;
dataGridView1.RowCount = 10;
}
private void dataGridView1_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
if (dataGridView1.IsCurrentCellDirty)
{
if (e.FormattedValue.ToString() == "BAD")
{
DataGridViewCell cell = dataGridView1[e.ColumnIndex, e.RowIndex];
cell.ErrorText = "Invalid data entered in cell";
// 增加边距以便显示图标。这将在编辑控件时发生。
if (cell.Tag == null)
{
cell.Tag = cell.Style.Padding;
cell.Style.Padding = new Padding(0, 0, 18, 0);
cellInError = new Point(e.ColumnIndex, e.RowIndex);
}
if (errorTooltip == null)
{
errorTooltip = new ToolTip();
errorTooltip.InitialDelay = 0;
errorTooltip.ReshowDelay = 0;
errorTooltip.Active = false;
}
e.Cancel = true;
}
}
}
private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
if (dataGridView1.IsCurrentCellDirty && !String.IsNullOrEmpty(e.ErrorText))
{
// 绘制出错误图标外的所有图像
e.Paint(e.ClipBounds, DataGridViewPaintParts.All &
~(DataGridViewPaintParts.ErrorIcon));
// 移动错误图标来填充空白边距
GraphicsContainer container = e.Graphics.BeginContainer();
e.Graphics.TranslateTransform(18, 0);
e.Paint(this.ClientRectangle, DataGridViewPaintParts.ErrorIcon);
e.Graphics.EndContainer(container);
e.Handled = true;
}
}
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if (dataGridView1[e.ColumnIndex, e.RowIndex].ErrorText != String.Empty)
{
DataGridViewCell cell = dataGridView1[e.ColumnIndex, e.RowIndex];
cell.ErrorText = String.Empty;
cellInError = new Point(-2, -2);
// 还原单元格的边距。这将在编辑控件时发生。
cell.Style.Padding = (Padding)cell.Tag;
// 隐藏和释放tooltip
if (errorTooltip != null)
{
errorTooltip.Hide(dataGridView1);
errorTooltip.Dispose();
errorTooltip = null;
}
}
}
// 显示或隐藏说明错误的tootip
private void dataGridView1_CellMouseMove(object sender,
DataGridViewCellMouseEventArgs e)
{
if (cellInError.X == e.ColumnIndex &&
cellInError.Y == e.RowIndex)
{
DataGridViewCell cell = dataGridView1[e.ColumnIndex, e.RowIndex];
if (cell.ErrorText != String.Empty)
{
if (!errorTooltip.Active)
{
errorTooltip.Show(cell.ErrorText, dataGridView1, 1000);
}
errorTooltip.Active = true;
}
}
}
private void dataGridView1_CellMouseLeave(object sender, DataGridViewCellEventArgs e)
{
if (cellInError.X == e.ColumnIndex &&
cellInError.Y == e.RowIndex)
{
if (errorTooltip.Active)
{
errorTooltip.Hide(dataGridView1);
errorTooltip.Active = false;
}
}
}
在DataGridView中显示的数据通常来自于数据源或其他数据类型,您或许会希望显示一列不是从数据源中获得的数据。这样的列被称为非绑定列。非绑定列有多种形式。我们可以使用虚拟模式(virtual mode)来同时显示绑定和非绑定数据。
接下来的示例演示了怎样让一个非绑定的DataGridViewCheckBoxCloumn记录用户选择数据情况。DataGridView处于虚拟模式(virtual mode)而且处理一些相关的事件。勾选的记录通过ID保存在Dictionary中,这样用户对内容排序就不会丢失CheckBox的选择状态。
代码:
private System.Collections.Generic.Dictionary<int, bool> checkState;
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = customerOrdersBindingSource;
// 虚拟化DataGridViewCheckBoxColumn
dataGridView1.VirtualMode = true;
dataGridView1.Columns.Insert(0, new DataGridViewCheckBoxColumn());
// 初始化dictionary,存储布尔值表示选择状态
checkState = new Dictionary<int, bool>();
}
private void dataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
// 当单元格中数据改变时,更新保存的选择状态
if (e.ColumnIndex == 0 && e.RowIndex != -1)
{
// 从OrderID列中获取orderID值
int orderID = (int)dataGridView1.Rows[e.RowIndex].Cells["OrderID"].Value;
checkState[orderID] = (bool)dataGridView1.Rows[e.RowIndex].Cells[0].Value;
}
}
private void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
// 处理需要显示的虚拟列中单元格的数据的通知。
// 如果key在字典表中存在,获取数据
if (e.ColumnIndex == 0)
{
int orderID = (int)dataGridView1.Rows[e.RowIndex].Cells["OrderID"].Value;
if (checkState.ContainsKey(orderID))
{
e.Value = checkState[orderID];
}
else
e.Value = false;
}
}
private void dataGridView1_CellValuePushed(object sender, DataGridViewCellValueEventArgs e)
{
// 处理虚拟列中单元格的数据需要保存到字典表中的通知
if (e.ColumnIndex == 0)
{
// 从OrderID列中获取orderID值
int orderID = (int)dataGridView1.Rows[e.RowIndex].Cells["OrderID"].Value;
// 如果key(orderID)存在更新选择状态到字典中
// 如果key(orderID)不存在添加选择状态到字典中
if (!checkState.ContainsKey(orderID))
{
checkState.Add(orderID, (bool)e.Value);
}
else
checkState[orderID] = (bool)e.Value;
}
}
30. 如何在同一个DataGridView 中显示两张表上的数据? [回到顶端]
我们可以用虚拟模式(Virtual Mode) 来实现DataGridView 的多表显示功能,除此之外,我们还可以使用JoinView 类,通过这个类我们可以将两张表联系在一起,然后绑定到DataGridView 。
JoinView:
http://support.microsoft.com/kb/325682
使用DataGridView常见的方案就是显示主/从信息,例如对具有父类/子类关系的两张数据表的显示。在主表中选择一行会触发子表中显示相应的数据。
我们可以通过DataGridView控件之间的交互和BindingSource组件来实现主从信息显示。接下来的示例演示了如何显示SQL Server的Northwind数据库中的有关联的两张表:Customers和Orders。通过在主DataGridView中选择一个顾客,顾客相对应的表单将会显示在从DataGridView中。
代码:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
public class Form1 : System.Windows.Forms.Form
{
private DataGridView masterDataGridView = new DataGridView();
private BindingSource masterBindingSource = new BindingSource();
private DataGridView detailsDataGridView = new DataGridView();
private BindingSource detailsBindingSource = new BindingSource();
[STAThreadAttribute()]
public static void Main()
{
Application.Run(new Form1());
}
// 初始化窗体
public Form1()
{
masterDataGridView.Dock = DockStyle.Fill;
detailsDataGridView.Dock = DockStyle.Fill;
SplitContainer splitContainer1 = new SplitContainer();
splitContainer1.Dock = DockStyle.Fill;
splitContainer1.Orientation = Orientation.Horizontal;
splitContainer1.Panel1.Controls.Add(masterDataGridView);
splitContainer1.Panel2.Controls.Add(detailsDataGridView);
this.Controls.Add(splitContainer1);
this.Load += new System.EventHandler(Form1_Load);
this.Text = "DataGridView master/detail demo";
}
private void Form1_Load(object sender, System.EventArgs e)
{
// 将DataGridView绑定到对应的BindingSource
// 从数据库中加载数据
masterDataGridView.DataSource = masterBindingSource;
detailsDataGridView.DataSource = detailsBindingSource;
GetData();
// 调整主DataGridView列的大小,以适应新加载的数据
masterDataGridView.AutoResizeColumns();
// 配置从表可以根据数据的变化自动调整列宽
detailsDataGridView.AutoSizeColumnsMode =
DataGridViewAutoSizeColumnsMode.AllCells;
}
private void GetData()
{
try
{
// 指定一个链接字符串
// 用可以连接到本机SQL Server 样板数据库
// Northwind的链接字符串替换下面的链接字符串
String connectionString =
"Integrated Security=SSPI;Persist Security Info=False;" +
"Initial Catalog=Northwind;Data Source=localhost";
SqlConnection connection = new SqlConnection(connectionString);
// 创建一个数据集(DataSet)
DataSet data = new DataSet();
data.Locale = System.Globalization.CultureInfo.InvariantCulture;
// 将Customers表中的数据填充到数据集(DataSet)
SqlDataAdapter masterDataAdapter = new
SqlDataAdapter("select * from Customers", connection);
masterDataAdapter.Fill(data, "Customers");
// 将Orders表中的数据填充到数据集(DataSet)
SqlDataAdapter detailsDataAdapter = new
SqlDataAdapter("select * from Orders", connection);
detailsDataAdapter.Fill(data, "Orders");
// 建立两张表之间的DataRelation
DataRelation relation = new DataRelation("CustomersOrders",
data.Tables["Customers"].Columns["CustomerID"],
data.Tables["Orders"].Columns["CustomerID"]);
data.Relations.Add(relation);
// 将主表数据连接器绑定到Customers表
masterBindingSource.DataSource = data;
masterBindingSource.DataMember = "Customers";
// 将子表连接器绑定到主表数据连接器
// 使用DataRelation名来实现根据主表的当前行来筛选子表中的数据
detailsBindingSource.DataSource = masterBindingSource;
detailsBindingSource.DataMember = "CustomersOrders";
}
catch (SqlException)
{
MessageBox.Show("To run this example, replace the value of the " +
"connectionString variable with a connection string that is " +
"valid for your system.");
}
}
}
32.如何在同一个DataGridView中显示主从信息? [回到顶端]
DataGridView不支持在同一个DataGridView中显示主从信息。您可以使用Windows Forms的先前版本DataGrid控件来实现这种功能。
我们可以通过设置某一列DataGridViewColumn.SortMode属性来禁止用户排序特定的列。在Visual studion 2005中通过右击DataGridView选择编辑列,选择我们希望禁止用户排序的列,设置其SortMode属性为NotSortable即可。
34.如何在点击toolstrip button时将数据提交到数据源? [回到顶端]
默认情况下,toolbar和menu不会触发数据验证。在一个数据绑定的控件中,验证是数据确认和更新的必要环节。一旦窗体和所有绑定的控件验证后,将会提交所有的当前修改。最后,TableAdapter将所有的修改更新到数据库。将下面的三行代码放添加到click事件处理程序中可以实现这种功能:
代码:
this.Validate();
this.customersBindingSource.EndEdit();
this.customersTableAdapter.Update(this.northwindDataSet.Customers);
35.如何当用户试图删除一行时显示再次确认的对话框? [回到顶端]
当用户在DataGridView中选择一行,点击Del键,就会触发UserDeletingRow事件。我们可以提示用户是否继续删除该行。注意新行不能被删除。将以下代码添加到UserDeletingRow事件处理程序中可以实现这种功能:
代码:
if (!e.Row.IsNewRow)
{
DialogResult response = MessageBox.Show("Are you sure?", "Delete row?",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2);
if (response == DialogResult.No)
e.Cancel = true;
}