这两天正在建树。
目标有几个:
1. 信息在数据库中,程序自动装载。
2. 表要少,特别是数据信息只在一个表中为好。因为目前的程序分三种:
1) 演示程序,信息在xml 文件中为好。随时都能展示给其它人看。
2)小客户端程序,不需要连远端数据库,用本地的access数据库就可以工作。
3)客户端工作在连接远端数据库的情况下。
也就是说,基础信息,有可能从远端同步到本地。
所以,表要少。
而且,树的层级关系,目前来看,不同的被管对象是不对的,是动态变化的,我们不能说哪一层,具有什么特定的含义,所以,要建一个子从属于父的关联树。
然后,展示到界面上。
小团队开发,或是一个人开发,象我这样,最好先考虑界面。因为界面是工作量最大的,内存结构做得好,可不一定界面上就好显示。
一开始调研了一个小程序: HierarchyTree
http://www.codeproject.com/script/articles/download.aspx?file=/KB/aspnet/HierarchyTree/HierarchyTree.zip&rp=http://www.codeproject.com/Articles/140827/Dynamic-Binding-Of-Hierarchy-Data-Structure-To-Tre
这个例子,展示了一种通常的作法。
但从各个方面,我不喜欢。看来在C#里面处理树不是很方便。当然,也是作者没有写好。
foreach (HierarchyTrees.HTree hTree in hierarchyTrees)
{
HierarchyTrees.HTree parentNode = hierarchyTrees.Find(delegate(HierarchyTrees.HTree emp) { return emp.NodeID == hTree.UnderParent; });
if (parentNode != null)
{
foreach (TreeNode tn in tvHierarchyView.Nodes)
{
if (tn.Value == parentNode.NodeID.ToString())
{
tn.ChildNodes.Add(new TreeNode(hTree.NodeDescription.ToString(), hTree.NodeID.ToString()));
}
if (tn.ChildNodes.Count > 0)
{
foreach (TreeNode ctn in tn.ChildNodes)
{
//RecursiveChild(ctn, parentNode.NodeID.ToString(), hTree);
}
}
}
}
else
{
tvHierarchyView.Nodes.Add(new TreeNode(hTree.NodeDescription, hTree.NodeID.ToString()));
}
}
tvHierarchyView.ExpandAll();
}
这里我们看到,作者进行了两次迭代。比较糟的做法。
通常来讲,应当几步:
1) 在内存中建树。
2) 插入到界面。
插入界面的过程,有两个功能,需要注意:一个是在内存树中,如何遍历,如何快速定位。
一个是在界面树中,如何快速定位。
所以,在建内存数,和插入界面的时候,应当伴随建一个hash 表,或是快速map 之类的,两棵树都要有。
否则,代码就这成这位兄台这样:看起来很短,但究其实质,很糟。
为什么两步呢?以前我很喜欢一步到位,这样好处是,不用自己建树了,直接用界面控件的树就好了。不能说不好,因为这样有一个很重要的好处:不需要进行内存与界面的同步工作。对于动态树,这的确是要小心处理的,如果内存树还有界面树又分别建了索引,同步的工作量,还是有的。
但,编程,一味图快,图简单,问题也多。因为程序和人一样,是要腐烂的。总有一天,作者本人也不想闻一闻,究其原因,就是语义不清。
有了良好的内存结构,意味着复用性好:一方面,本程序内,其它模块好利用,另一方面这些代码,也便于复用到其它的项目中。
=============
本着这样的意图。我再次开始了脑子。
一般而言,大公司做的东西相对好一些。所以,我找到devExpress中的例子,花了大半天时间,建环境。
因为要跟代码。
看的例子是这个:用的是DX2011.2.5_src
XtraTreeList
刚接个电话,闲话少说吧,
这个例子,是从一个xml中读取信息,放到界面上。
可以猜测,一定有两个固有的字段:
ID
ParentID
这里,我再强调一下关键是你要有原码。看原码比一切理论都有实际意义。
另外,一定要建好调试环境。因为我手头的devExpress原码,也搞不清楚是从哪拿的,没有注释,可能这也是devexpress公司的策略,他们不会发布有注释的原码。
但也足够了。
至于,如何建这种环境,以后我来讲解。接了两个电话,已超时了。
<?xml version="1.0" standalone="yes"?>
<NewDataSet>
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true">
<xs:complexType>
<xs:choice maxOccurs="unbounded">
<xs:element name="Table">
<xs:complexType>
<xs:sequence>
<xs:element name="Id" type="xs:double" minOccurs="0" />
<xs:element name="ParentId" type="xs:double" minOccurs="0" />
<xs:element name="FirstName" type="xs:string" minOccurs="0" />
<xs:element name="LastName" type="xs:string" minOccurs="0" />
<xs:element name="JobTitle" type="xs:string" minOccurs="0" />
<xs:element name="Phone" type="xs:string" minOccurs="0" />
<xs:element name="EmailAddress" type="xs:string" minOccurs="0" />
<xs:element name="AddressLine1" type="xs:string" minOccurs="0" />
<xs:element name="City" type="xs:string" minOccurs="0" />
<xs:element name="PostalCode" type="xs:string" minOccurs="0" />
<xs:element name="CountryRegionName" type="xs:string" minOccurs="0" />
<xs:element name="StateProvinceName" type="xs:string" minOccurs="0" />
<xs:element name="GroupName" type="xs:string" minOccurs="0" />
<xs:element name="BirthDate" type="xs:dateTime" minOccurs="0" />
<xs:element name="HireDate" type="xs:dateTime" minOccurs="0" />
<xs:element name="Gender" type="xs:string" minOccurs="0" />
<xs:element name="MaritalStatus" type="xs:string" minOccurs="0" />
<xs:element name="Title" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
<Table>
<Id>109</Id>
<ParentId>0</ParentId>
<FirstName>Bruce</FirstName>
<LastName>Cambell</LastName>
<JobTitle>Chief Executive Officer</JobTitle>
<Phone>(417) 166-3268</Phone>
<EmailAddress>Bruce_Cambell@example.com</EmailAddress>
<AddressLine1>4228 S National Ave</AddressLine1>
<City>Oaks</City>
<PostalCode>65809</PostalCode>
<StateProvinceName>California</StateProvinceName>
<CountryRegionName>United States</CountryRegionName>
<GroupName>Executive General and Administration</GroupName>
<BirthDate>1957-09-06T00:00:00</BirthDate>
<HireDate>2000-07-02T00:00:00</HireDate>
<Gender>M</Gender>
<MaritalStatus>M</MaritalStatus>
<Title>Mr.</Title>
</Table>
<Table>
<Id>42</Id>
<ParentId>109</ParentId>
<FirstName>Cindy</FirstName>
<LastName>Haneline</LastName>
<JobTitle>Information Services Manager</JobTitle>
<Phone>(918) 161-3649</Phone>
<EmailAddress>Cindy_Haneline@example.com</EmailAddress>
<AddressLine1>2429 E. 15th Street</AddressLine1>
<City>Vista</City>
<PostalCode>74014</PostalCode>
<StateProvinceName>California</StateProvinceName>
<CountryRegionName>United States</CountryRegionName>
<GroupName>Executive General and Administration</GroupName>
<BirthDate>1973-12-23T00:00:00</BirthDate>
<HireDate>1996-11-06T00:00:00</HireDate>
<Gender>F</Gender>
<MaritalStatus>S</MaritalStatus>
</Table>
============
好了,开始。
DX2011.2.5_src\Sources\DevExpress.XtraTreeList\DevExpress.XtraTreeList\TreeList.cs(692):
protected const string defKeyFieldName = "ID",
defParentFieldName = "ParentID",
defImageIndexFieldName = "ImageIndex";
public TreeList() : this(null) {
}
这是最关键的几句话,我一开始跟了半天,才找到,原来是写死的。
初始化:
namespace DevExpress.XtraTreeList.Demos {
public partial class NodesFiltering : DevExpress.XtraTreeList.Demos.TutorialControl {
public NodesFiltering() {
InitializeComponent();
InitData();
InitEditors();
//InitFilter();
treeList1.Columns["pkgName"].AllNodesSummary = true;
treeList1.Columns["pkgName"].SummaryFooter = SummaryItemType.Count;
treeList1.Columns["pkgName"].OptionsFilter.FilterPopupMode = FilterPopupMode.CheckedList;
treeList1.OptionsView.ShowSummaryFooter = true;
}
这里,我们改一下,不从xml,改从sqlserver中读。
private void InitData() {
string DBFileName = DevExpress.Utils.FilesHelper.FindingFileName(Application.StartupPath, "Data\\Employees.xml");
if(DBFileName != "") {
DataSet dataSet = new DataSet();
//dataSet.ReadXml(DBFileName);
using (SqlConnection connection = new SqlConnection(@"Data Source=127.0.0.1;Initial Catalog=AutoPack;Persist Security Info=True;User ID=sa;Password=12345"))
{
connection.Open();//SSP_GET_HIERARCHY
using (SqlCommand command = new SqlCommand("select * from TBL_TREE_HIERARCHY", connection))
{
//command.CommandType = System.Data.CommandType.StoredProcedure;
//SqlDataReader reader = command.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
SqlDataAdapter da = new SqlDataAdapter(command);
//DataSet ds = new DataSet();
//fill the DataSet using default values for DataTable names, etc.
da.Fill(dataSet);
}
}
treeList1.DataSource = dataSet.Tables[0].DefaultView;
treeList1.ForceInitialize();
treeList1.ExpandAll();
treeList1.BestFitColumns();
}
}
//这段代码,要是不处理,则确保表里有相关的字段:GroupName
string currentGroupName;
private void treeList1_GetStateImage(object sender, GetStateImageEventArgs e) {
string[] groupNames = new string[] { "Administration", "Inventory", "Manufacturing", "Quality", "Research", "Sales" };
currentGroupName = (string)e.Node.GetValue("GroupName");
e.NodeImageIndex = Array.FindIndex(groupNames, new Predicate<string>(IsCurrentGroupName));
}
private bool IsCurrentGroupName(string groupName) { return currentGroupName.Contains(groupName); }
数据库表如下:
USE [AutoPack]
GO
/****** Object: Table [dbo].[TBL_TREE_HIERARCHY] Script Date: 02/25/2014 17:45:37 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[TBL_TREE_HIERARCHY](
[ID] [int] IDENTITY(1,1) NOT NULL,
[ParentID] [int] NULL,
[pkgName] [nvarchar](511) NULL,
[NODE_DESCRIPTION] [nvarchar](511) NULL,
[ImageIndex] [int] NULL,
[UVERSION] [nvarchar](511) NULL,
[VER] [nvarchar](511) NULL,
[Title] [nvarchar](511) NULL,
[GroupName] [nvarchar](511) NULL,
[pkgFilename] [nvarchar](511) NULL
) ON [PRIMARY]
GO
内容,我随意写的
ID ParentID pkgName NODE_DESCRIPTION ImageIndex UVERSION VER Title GroupName pkgFilename
1 0 a Mathematics 0 A 1 wert Administration a.out
2 1 b Algebra NULL A 2 fgg Inventory b.out
3 1 c Geometry 0 A 3 s Research c.out
4 3 d Triangle 0 A 1 ert Research d.out
5 4 e By Relative Length 0 A NULL f Research e.out
6 4 f By Internal Angle 0 A 232 s Research f.out
7 5 g Equilateral Triangle 0 A 124 dfgwr Research g.out
8 5 h Scalene Triangle 0 A 3 NULL Quality h.out
9 5 i Isosceles Triangle 0 A 5 sfdg Quality i.out
10 6 j Oblique > 90 Degree:Obtuse Angled Traingle 0 A 5 s Quality j.out
11 6 k Oblique < 90 Degree:Acute Angled Traingle 0 A 5 f Quality k.out
12 6 l Right Angled Triangle 0 A 4 sfg Quality l.out
13 2 m Elementary Algebra 0 A 4 sf Quality m.out
14 2 n Abstract Algebra 0 A 6 gsfgh Manufacturing n.out
15 2 o Linear Algebra 0 A 345 fgj Manufacturing o.out
16 7 p All Sides are Equal 0 A 3 fghj Manufacturing p.out
下一步,把这里改了在namespace DevExpress.XtraTreeList.Demos {
partial class NodesFiltering {
...
this.treeListColumn1.Caption = "包名称";
this.treeListColumn1.FieldName = "pkgName";
this.treeListColumn1.MinWidth = 33;
this.treeListColumn1.Name = "treeListColumn1";
this.treeListColumn1.Visible = true;
this.treeListColumn1.VisibleIndex = 0;
this.treeListColumn1.Width = 105;
//
// treeListColumn2
//
this.treeListColumn2.Caption = "文件名";
this.treeListColumn2.FieldName = "pkgFilename";
this.treeListColumn2.Name = "treeListColumn2";
this.treeListColumn2.Visible = true;
this.treeListColumn2.VisibleIndex = 1;
this.treeListColumn2.Width = 106;
其它列删除
好了,跑起来,