ASP.NET开发组件之DataGridView列标题可编辑组件(2)
绑定好DataGridView控件的事件处理方法之后,就是对RichTextBox控件的操作了。编辑框需要处理键盘操作以实现移动和完成编辑的操作,对应方法是rtbTitle_KeyDown。编辑框失去焦点时也要作为编辑完成的动作,对应方法是rtbTtile_Leave方法。ShowHeaderEdit方法是显示编辑效果的,主要是确定编辑框的位置和大小,把对应列的标题显示到编辑框中。这里不允许输入空的标题,如果需要,可以根据实际情况修改代码。另外其中加入了一些事件,用来更加灵活控制编辑操作。关于事件,稍后再详细介绍。


#region 文本框相关方法
/// <summary>
/// 文本框的键盘处理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void rtbTitle_KeyDown(object sender, KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Enter://回车结束编辑
this.m_TargetControl.Focus();//让编辑框失去焦点而结束编辑并隐藏,下同
e.Handled = true;//必须设置为true,否则会有烦人的系统提示音,下同
break;
case Keys.Right://向右
//判断光标是否移动到当前编辑字符串的末尾,光标移到末尾才移动编辑框
if (this.rtbTitle.SelectionStart >= this.rtbTitle.Text.Length)
{
//判断当前编辑列是否是最后一列
if (this.m_SelectedColumnIndex < this.m_TargetControl.Columns.Count - 1)
{
e.Handled = true;
this.m_TargetControl.Focus();
//获取下一个可见列的序号并设置为当前选中列序号
this.m_SelectedColumnIndex = this.GetNextVisibleColumnIndex(this.m_SelectedColumnIndex);
this.ShowHeaderEdit();//根据选中列显示编辑框
}
}
break;
case Keys.Left://向左
//判断光标是否到达当前编辑字符串的最前,光标移动到最前才移动编辑框
if (this.rtbTitle.SelectionStart == 0)
{
//判断当前编辑列是否是第0列
if (this.m_SelectedColumnIndex > 0)
{
e.Handled = true;
this.m_TargetControl.Focus();
this.m_SelectedColumnIndex = this.GetPreVisibleColumnIndex(this.m_SelectedColumnIndex);
this.ShowHeaderEdit();
}
}
break;
default:
break;
}
}
/// <summary>
/// 文本框失去焦点
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void rtbTitle_Leave(object sender, EventArgs e)
{
DataGridViewColumn myColumn = this.m_SortedColumnList[this.m_SelectedColumnIndex];
//定义事件参数
ColumnHeaderEditEventArgs myArgs = new ColumnHeaderEditEventArgs(myColumn, this.rtbTitle.Text.Trim());
if (this.EndingEdit != null)
{
this.EndingEdit(this, myArgs);//引发事件
if (myArgs.Cancel)//如果取消标志为true
{
this.rtbTitle.Focus();//保持编辑状态
return;
}
}
this.rtbTitle.Visible = false;
if (myArgs.NewHeaderText.Length > 0)//不允许用空字符串作为标题
{
if (myColumn.HeaderText != myArgs.NewHeaderText)
{
//用事件参数里面的新标题,因为在事件处理程序里面可能修改新标题
myColumn.HeaderText = myArgs.NewHeaderText;
}
}
if (this.EndEdit != null)
this.EndEdit(this, myArgs);//引发事件
}
/// <summary>
/// 显示标题编辑效果
/// </summary>
private void ShowHeaderEdit()
{
if (this.BeginEdit != null)
{
ColumnHeaderEditEventArgs myArgs = new ColumnHeaderEditEventArgs(this.m_SortedColumnList[this.m_SelectedColumnIndex], "");
BeginEdit(this, myArgs);
if (myArgs.Cancel)
return;
}
int intColumnRelativeLeft = 0;
//第一列左边距,需要判断是否显示行标题
int intFirstColumnLeft = (this.m_TargetControl.RowHeadersVisible ? this.m_TargetControl.RowHeadersWidth + 1 : 1);
int intTargetX = this.m_TargetControl.Location.X, intTargetY = this.m_TargetControl.Location.Y, intTargetWidth = this.m_TargetControl.Width;
intColumnRelativeLeft = GetColumnRelativeLeft(this.m_SelectedColumnIndex);
if (intColumnRelativeLeft < this.m_ScrollValue)
{
this.rtbTitle.Location = new Point(intTargetX + intFirstColumnLeft, intTargetY + 1);
if (intColumnRelativeLeft + this.m_SortedColumnList[this.m_SelectedColumnIndex].Width > this.m_ScrollValue)
this.rtbTitle.Width = intColumnRelativeLeft + this.m_SortedColumnList[this.m_SelectedColumnIndex].Width - this.m_ScrollValue;
else
this.rtbTitle.Width = 0;
}
else
{
this.rtbTitle.Location = new Point(intColumnRelativeLeft + intTargetX - this.m_ScrollValue + intFirstColumnLeft, intTargetY + 1);
if (this.rtbTitle.Location.X + this.rtbTitle.Width > intTargetX + intTargetWidth)
{
int intWidth = intTargetX + intTargetWidth - this.rtbTitle.Location.X;
this.rtbTitle.Width = (intWidth >= 0 ? intWidth : 0);
}
else
this.rtbTitle.Width = this.m_SortedColumnList[this.m_SelectedColumnIndex].Width;
}
this.rtbTitle.Height = this.m_TargetControl.ColumnHeadersHeight - 1;
this.rtbTitle.Text = this.m_SortedColumnList[this.m_SelectedColumnIndex].HeaderText;
this.rtbTitle.SelectAll();
this.rtbTitle.Visible = true;
this.rtbTitle.Focus();
}
#endregion 文本框相关方法
在上面对编辑框操作的相关方法中,又涉及到了对列对象的一些操作,比如获取相对坐标,左右移动时获取邻近显示的列。下面就是这些方法的代码。


#region DataGridView列相关方法
/// <summary>
/// 重新加载列对象的列表
/// </summary>
private void ReloadSortedColumnList()
{
this.m_SortedColumnList.Clear();
foreach (DataGridViewColumn column in this.m_TargetControl.Columns)
{
this.m_SortedColumnList.Add(column.DisplayIndex, column);
}
}
/// <summary>
/// 获取列的相对左边距
/// </summary>
/// <param name="ColumnIndex">列序号</param>
/// <returns>列的左边距</returns>
private int GetColumnRelativeLeft(int ColumnIndex)
{
int intLeft = 0;
DataGridViewColumn Column = null;
for (int intIndex = 0; intIndex < ColumnIndex; intIndex++)
{
if (this.m_SortedColumnList.ContainsKey(intIndex))
{
Column = this.m_SortedColumnList[intIndex];
if (Column.Visible)
intLeft += Column.Width + Column.DividerWidth;
}
}
return intLeft;
}
/// <summary>
/// 获取上一个可见列的序号
/// </summary>
/// <param name="CurrentIndex">当前列序号</param>
/// <returns></returns>
private int GetPreVisibleColumnIndex(int CurrentIndex)
{
int intPreIndex = 0;
for (int intIndex = CurrentIndex - 1; intIndex >= 0; intIndex--)
{
if (this.m_SortedColumnList.ContainsKey(intIndex) && this.m_SortedColumnList[intIndex].Visible)
{
intPreIndex = intIndex;
break;
}
}
return intPreIndex;
}
/// <summary>
/// 获取下一个可见列的序号
/// </summary>
/// <param name="CurrentIndex">当前列序号</param>
/// <returns></returns>
private int GetNextVisibleColumnIndex(int CurrentIndex)
{
int intNextIndex = CurrentIndex;
for (int intIndex = CurrentIndex + 1; intIndex <= this.m_SortedColumnList.Keys[this.m_SortedColumnList.Count - 1]; intIndex++)
{
if (this.m_SortedColumnList.ContainsKey(intIndex) && this.m_SortedColumnList[intIndex].Visible)
{
intNextIndex = intIndex;
break;
}
}
return intNextIndex;
}
#endregion DataGridView列相关方法
以上方法都比较简单,不再详细解释。下面就介绍事件。在类中声明了三个事件,代码如下:


#region 事件声明
/// <summary>
/// 开始编辑,可取消编辑
/// </summary>
[Description("在开始编辑列标题时发生的事件,可取消编辑。")]
public event ColumnHeaderEditEventHandler BeginEdit;
/// <summary>
/// 准备结束编辑,可取消
/// </summary>
[Description("在即将结束编辑时发生的事件,可取消。")]
public event ColumnHeaderEditEventHandler EndingEdit;
/// <summary>
/// 结束编辑
/// </summary>
[Description("在编辑结束后发生的事件。")]
public event ColumnHeaderEditEventHandler EndEdit;
#endregion 事件声明
BeginEdit事件是在编辑开始的时候发生的,如果有一些列不允许编辑,则可以在该事件处理方法中捕获并取消。
EndingEdition事件是在编辑即将结束的时候发生的,如果用户输入的列标题不合理,可以取消结束编辑,强制用户继续编辑。
EndEdit事件是在编辑结束后发生的,通知外部被编辑的列的相关信息。
这些事件的类型都是ColumnHeaderEditEventHandler,如下是该事件委托的定义以及事件参数的定义。如果对事件和委托不是很了解,请先查阅相关资料,这里不作详细阐述。
小技巧――事件委托和事件参数相关
通常事件委托的名称定义为事件相关名称+EventHandler,比如MouseEventHandler,PaintEventHandler,CancelEventHandler,FormClosedEventHandler。事件委托一般包含两个参数格式,定义格式如public delegate void MyEventHandler(object sender, MyEventArgs e)。而事件参数一般定义为事件相关名称+EventArgs,比如DragEventArgs,ListChangedEventArgs,NavigateEventArgs,MouseEventArgs。事件参数中的属性一般是不可修改的,即没有set段,是通过构造函数指定的。如果需要通过参数影响事件的行为,则会存在set段。


/// <summary>
/// 列标题编辑事件委托
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public delegate void ColumnHeaderEditEventHandler(object sender, ColumnHeaderEditEventArgs e);
/// <summary>
/// 列标题编辑事件参数
/// </summary>
public class ColumnHeaderEditEventArgs : EventArgs
{
private bool m_Cancel = false;
/// <summary>
///