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;
}
}
}
}
