using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using System.Diagnostics;
namespace upControls
{
/// <summary>
/// 可快速绑定到关系表或单表的树,树自动按照主表及其子表的PrimaryKey列值来绑定
/// 只遍历一次Rows中的所有行,所以加载速度非常快
/// 结点内容可以只显示值,也可以显示列名称以作说明
/// 关系型的数据源要求具有:子列必需具唯一约束
/// 附加列必需是关系表中的最底层表所拥有的列
/// 适用于ParentID,ID,Text式之外的所有表的树填充
/// </summary>
public class DataTreeView : TreeView
{
private System.ComponentModel.Container components = null;
private DataTable _mainDatatable;
private string[] _appendColumnNames=null;
private System.Windows.Forms.ContextMenu cntMenu;
private bool _columnNameOnText;
private TreeNode _parentNode;
public DataTreeView ()
{
InitializeComponent();
MenuItem mnu;
mnu=cntMenu.MenuItems.Add ("显示列名");
mnu.Click +=new EventHandler(mnu_Click);
mnu=cntMenu.MenuItems.Add ("-");
mnu=cntMenu.MenuItems.Add ("展开");
mnu.Click +=new EventHandler(mnu_Click);
mnu=cntMenu.MenuItems.Add ("折叠");
mnu.Click +=new EventHandler(mnu_Click);
mnu=cntMenu.MenuItems.Add ("-");
mnu=cntMenu.MenuItems.Add ("全部展开");
mnu.Click +=new EventHandler(mnu_Click);
mnu=cntMenu.MenuItems.Add ("全部折叠");
mnu.Click +=new EventHandler(mnu_Click);
}
/// <summary>
/// 清理所有正在使用的资源。
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region 组件设计器生成的代码
/// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器
/// 修改此方法的内容。
/// </summary>
private void InitializeComponent()
{
this.cntMenu = new System.Windows.Forms.ContextMenu();
this.cntMenu.Popup += new System.EventHandler(this.cntMenu_Popup);
this.CheckBoxes = true;
this.ContextMenu = this.cntMenu;
}
#endregion
///////////////////////////////////////////////////////////////////////////////
/// <summary>
/// 主表,主表的第一个primarykey值将添加在树的顶层
/// </summary>
public DataTable MainTable
{
get{return this._mainDatatable ; }
}
/// <summary>
/// 除primarykey列之外的列,可以附加在最后一个primarykey列的结点之下的列
/// </summary>
public string[] AppendColumnNames
{
get{return _appendColumnNames ; }
}
/// <summary>
/// 结点的文本要否包含列名
/// </summary>
public bool ColumnNameOnText
{
get{return _columnNameOnText ; }
}
/// <summary>
/// 填充一个表及其子表到树,结点显示的数据是每一个键列的内容
/// </summary>
/// <param name="dataTable">要填充到树的表</param>
/// <param name="parentNode">要填充到哪一个现有的结点之下</param>
/// <param name="appendColumnnames">附加列(非键列),格式是:表名.列名,或只有列名</param>
/// <param name="ColumnnameOnText">列名要不要显示在结点的文本之中</param>
/// <param name="clearNodes">要不要清除现存的结点再填充</param>
public void Fill ( DataTable dataTable,TreeNode parentNode,
string[] appendColumnnames,bool ColumnnameOnText,bool clearNodes)
{
_mainDatatable =dataTable;
_appendColumnNames=appendColumnnames;
_columnNameOnText=ColumnnameOnText;
_parentNode=parentNode;
TryBinding(clearNodes);
}
/// <summary>
/// 尝试填充树,如果各个必需属性都设置好了
/// </summary>
/// <param name="clearNodes"></param>
public void TryBinding(bool clearNodes)
{
if (clearNodes)
{
this.Nodes.Clear() ;
TryBinding(_mainDatatable,null,_appendColumnNames);
}
else
TryBinding(_mainDatatable,_parentNode,_appendColumnNames);
}
private void TryBinding(DataTable dataTable,TreeNode parentNode,
string[] appendColumnNames)
{
if (dataTable==null) return ;
//先加入主表名结点
if (parentNode!=null)
parentNode=parentNode.Nodes.Add (dataTable.TableName );
else
parentNode=this.Nodes.Add (dataTable.TableName );
//返回包含了一个表中所有键列的数组,但是如果表是子表的话,则作为关系的键列不包含在内
//因为父表中存在相同的列值,不需要加载重复内容的结点
DataColumn[] PrimaryKey=AddThesePrimaryKey(dataTable);
TreeNode[] priNodes=new TreeNode[PrimaryKey.Length ];
string sort=string.Empty;
//作排序准备
for (int i=0 ; i < PrimaryKey.Length ; i ++ )
sort=sort + "," + PrimaryKey[i].ColumnName ;
//在下边的数据行遍历中需要确保是按序排列的,快速加载全靠它
sort=sort.Trim (',');
//已删除的行当然不要加到树
DataRow[] allRows=dataTable.Select (string.Empty,sort,DataViewRowState.CurrentRows );
foreach (DataRow dr in allRows)
{
//下面的for设置priNodes数组,保证priNodes内有n个Node对应于当前行的每一个键列
for (int i=0 ; i < PrimaryKey.Length ; i ++ )
{
string colName=PrimaryKey [i].ColumnName ;
//内容为null,则这一行中这个键列之后的列内容都都不会加到树了
if (dr[colName]==null)
break;
TreeNode nod=new TreeNode ();
//格式化结点的文本
this.FormatNodeText (nod,dr,PrimaryKey [i]);
if (priNodes[i]!=null)
{
//是否已经存在,前面的键列一般是允许重复内容的
if ( priNodes[i].Text !=nod.Text )
priNodes[i]=nod;
}
else
priNodes[i]=nod;
}
int r =0 ;
TreeNode pnod=null;
//判断priNodes中的node是否要加到树以及要加到哪里(不能用foreach,顺序不同了)
for (int i=0 ; i < priNodes.Length ; i ++ )
{
if (priNodes[i] ==null)
break;
TreeNode nod=priNodes[i];
if (r==0 )
{
if (!parentNode.Nodes.Contains (nod))
parentNode.Nodes.Add (nod);
}
else if (!pnod.Nodes .Contains (nod))
pnod.Nodes .Add (nod);
pnod=nod;
++r;
}
//在上面的循环中没有被设置,表示每一个键列都是null,虽然不太可能
if (pnod==null) continue;
//附加列必需是关系表中的最底层表
if (dataTable.ChildRelations.Count ==0 && appendColumnNames!=null)
{
foreach (string fullColName in appendColumnNames)
{
//appendColumnNames中的列名可以是:表名.列名,或只有列名
//因为一个表可能有多个关系,而表名起导航作用
string[] fullName=fullColName.Split ('.');
string tabName=string.Empty,colName=string.Empty;
if (fullName.Length >1)
{
tabName=fullName[0];
colName=fullName[1];
}
else
{
tabName=dataTable.TableName ;
colName=fullName[0];
}
if (tabName==dataTable.TableName && dataTable.Columns .Contains (colName))
{
TreeNode nod=new TreeNode ();
this.FormatNodeText (nod,dr,dataTable.Columns [colName]);
pnod.Nodes.Add (nod);
}
}
}
///再填充子表的内容到树,每一个表都只历遍一次Rows
foreach (DataRelation drl in dataTable.ChildRelations )
TryBinding(drl.ChildTable ,pnod,appendColumnNames);
}
}
/// <summary>
/// 返回包含了一个表中所有键列的数组,但是如果表是子表的话,则作为关系的键列不包含在内(因为父表中存在相同的列值,不需要加载重复内容的结点)
/// </summary>
/// <param name="dt"></param>
/// <returns></returns>
private DataColumn[] AddThesePrimaryKey(DataTable dt)
{
DataColumn[] keys=null;
ArrayList list=new ArrayList ();
foreach (DataColumn dc in dt.PrimaryKey)
list.Add (dc);
if (dt!=this.MainTable )
foreach (DataRelation drl in dt.ParentRelations )
foreach (DataColumn dc in drl.ChildColumns )
list.Remove (dc);
if (list.Count >0 )
{
keys=new DataColumn [list.Count ];
list.CopyTo (keys,0);
}
return keys;
}
/// <summary>
/// 将结点文本格式化
/// </summary>
/// <param name="node"></param>
/// <param name="dataRow"></param>
/// <param name="dc"></param>
private void FormatNodeText(TreeNode node,DataRow dataRow,DataColumn dc)
{
string nodeText=string.Empty,nameCol=string.Empty,caption=string.Empty;
///Column_AutoID uca=null;
///Column_AutoID包含了一个ID列的编码规则的信息,
///if (dc.ExtendedProperties.ContainsKey ("Ext_AutoID"))
///uca=dc.ExtendedProperties["Ext_AutoID"] as Column_AutoID ;
if (dataRow.Table.Columns.Contains (dc.ColumnName ))
{
nodeText=dataRow[dc].ToString ().Trim ();
///Column_AutoID.IDNameColumn: 保存一个ID列的名称列名,如果有此项,结点文本就可用名称来说明ID了,例如用姓名说明人员编号
///if (uca!=null && uca.IDNameColumn !=null)
///{
///nameCol=uca.IDNameColumn ;
///if (dataRow.Table.Columns .Contains (nameCol))
///nameCol= dataRow[nameCol].ToString (); //名称列的值
///}
caption=dc.Caption ;
if (dc.DataType ==typeof( Boolean) )
{
nodeText=caption;
node.Checked = System.Convert.ToBoolean (dataRow[dc]);
}
else
{
nameCol=nameCol==string.Empty?nameCol:"\t[" + nameCol +"]";
if (this.ColumnNameOnText )
nodeText=caption + ":" + nodeText + nameCol;
else
nodeText= nodeText + nameCol;
}
node.Text =nodeText;
}
}
private void cntMenu_Popup(object sender, System.EventArgs e)
{
}
private void mnu_Click(object sender, EventArgs e)
{
MenuItem mnu=sender as MenuItem ;
switch (mnu.Index )
{
case 0:
mnu.Checked=!mnu.Checked;
_columnNameOnText=mnu.Checked ;
this.TryBinding (true);
break;
case 2:
if (this.SelectedNode !=null)
this.SelectedNode.ExpandAll ();
break;
case 3:
if (this.SelectedNode !=null)
this.SelectedNode.Collapse ();
break;
case 5:
this.ExpandAll ();
break;
case 6:
this.CollapseAll ();
break;
}
}
}
}
///作者: CSDN网名alias88,邮件:alias88@163.com,QQ:63343