本人才疏學淺,望大家多給意見,有更好的做法大加分享分享
下面是題目:
已知表table_department中有兩個字段,分別為d_id,d_name。d_id記錄的是部門編碼, d_name記錄的是部門名稱,各部門的組織方式如下:
A為頂級部門,A部門的下級部門使用AA、BA、CA……表示
AA的下級部門使用AAA、BAA、CAA……表示
BA的夏季部門使用ABA、BBA、CBA……表示
以此類推。
新建一個應用程序,寫一個頁面或窗體,講table_department表中的數據,按樹狀排列顯示,如下所示:
A總經辦
-AA生產部
--AAA保修部
--BAA非保部
-BA物流部
--ABA物流一部
--BBA物流二部
--CBA物流三部
-CA市場部
--ACA市場拓展
--BCA營銷部
---ABCA電器營銷部
---BBCA電子營銷部
…………
………… 加分項:
表table_department用XML實現
===================================================
這個問題我覺得會有比較多的解法,我這里暫且考慮面試的問題,所以給出的解法不會考慮太多嚴謹的東西
首先建庫建表添加數據
SQL腳本為
=================================================
1 -- 創建數據庫 2 if db_id('testdb') is not null 3 drop database testdb; 4 go 5 create database testdb; 6 7 -- 使用數據庫 8 use testdb; 9 --創建數據表 10 if object_id('table_department', 'U') is null 11 create table table_department 12 ( 13 d_id varchar(10), 14 d_name nvarchar(50) 15 ); 16 go 17 -- 添加數據 18 insert into table_department(d_id, d_name) values('A', '總經辦'); 19 insert into table_department(d_id, d_name) values('AA', '生產部'); 20 insert into table_department(d_id, d_name) values('BA', '物流部'); 21 insert into table_department(d_id, d_name) values('CA', '市場部'); 22 insert into table_department(d_id, d_name) values('AAA', '保修部'); 23 insert into table_department(d_id, d_name) values('BAA', '非保部'); 24 insert into table_department(d_id, d_name) values('ABA', '物流一部'); 25 insert into table_department(d_id, d_name) values('BBA', '物流二部'); 26 insert into table_department(d_id, d_name) values('CBA', '物流三部'); 27 insert into table_department(d_id, d_name) values('ACA', '市場拓展'); 28 insert into table_department(d_id, d_name) values('BCA', '經營部'); 29 insert into table_department(d_id, d_name) values('ABCA', '電器經營部'); 30 insert into table_department(d_id, d_name) values('BBCA', '電子經營部');
========================================
第一種解法
也是最簡單傻瓜式的解法,使用ADO.Net讀取數據. 並將讀到的數據,根據d_id字段的數據創建TreeView節點,並加載數據
觀察樹節點的規律,每個節點只有d_id的現實,只有最后一個節點現實完整的d_id和d_name,並且每個d_id的字符表示一個層次結構
因此可以寫一個方法,根據包含d_id和d_name的字符串創建節點和添加數據
簡單步驟:
1、 首先該方法要往TreeView添加數據,因此該方法一定要有一個TreeNode參數(鑒於根節點只有一個,可以將A添加為根節點,或直接就將"公司"作為根節點)
2、 觀察d_id的字符串,根節點在最右邊,子節點在左邊(估計是為了故意設計的面試題,這樣不好排序)
實際這個很簡單,將d_id字符串轉換成字符數組,從后往左遍歷,並在TreeNode中創建節點,如果節點存在就不用創建
3、 因此方法原型可以定義為:
1 private void ShowFromString(string d_id, string id, string d_name, TreeNode tn) 2 { 3 // 實現代碼 4 // d_id創建結構使用 5 // id記錄部門id號 6 // d_name記錄部門名稱 7 // tn表示當前節點 8 }
4、 接下來看方法如何實現
由於TreeNode是有層次顯示的,所以這里使用遞歸最為容易(循環感覺也可以實現)
4.1 首先將d_id編程字符數組,並得到最后一個字符,這個頂級節點
1 char[] chs = d_id.ToCharArray(); 2 string nodeStr = chs[chs.Length - 1].ToString();
4.2 在TreeNode中檢索是否存在這個節點. 檢索存在就是看TreeNode的子節點中是否有Tag與nodeStr匹配的(這里可使用Linq,不過既然簡單用最原始的)
寫一個方法,由於部門的名字是不會重復的,所以這么寫
1 private bool IsExist(string nodeText, TreeNode tn) 2 { 3 bool isTrue = false; 4 for(int i = 0; i < tn.Nodes.Count; i++) 5 { 6 if(tn.Nodes[i].Tag as string == nodeText) 7 { 8 isTrue = true; 9 break; 10 } 11 } 12 return isTrue; 13 }
該方法只要找到TreeNode直接子節點中存在與給定字符串相同的節點就返回true,否則返回false
4.3 判斷是否存在節點,如果不存在就創建,如果存在就得到這個節點
這里需要注意的是,所有節點的邏輯結構均有Tag屬性來確認,而Text屬性最終使用d_id與d_name替換,因此這里是一個臨時的值
TreeNode tnObj = null; if(!IsExist(nodeStr, tn)) { tnObj = tn.Nodes.Add(nodeStr); tnObj.Tag = nodeStr; } else { // 得到這個節點 }
4.4 考慮如果存在就得到該節點,但是不要寫循環一次了,太麻煩,因此修改IsExist方法
1 private bool IsExist(string nodeText, TreeNode tn, out TreeNode tnObj) 2 { 3 tnObj = null; 4 bool isTrue = false; 5 for(int i = 0; i < tn.Nodes.Count; i++) 6 { 7 if(tn.Nodes[i].Tag as string == nodeText) 8 { 9 isTrue = true; 10 tnObj = tn.Nodes[i]; // 將找到的節點直接返回 11 break; 12 } 13 } 14 return isTrue; 15 }
因此4.3步的代碼可以改為
1 if(!IsExist(nodeStr, tn, out tnObj)) 2 { 3 tnObj = tn.Nodes.Add(nodeStr); 4 tnObj.Tag = nodeStr; 5 }
這個方法的思路來自int.TryParse方法,如果找到了,那么返回true,那么tnObj中就有了該節點
如果沒有找到那么創建一個,反正tnObj中就有當前子節點
4.5 這里應該判斷是不是最后一個節點,如果是最后一個節點那么就應該將id和d_name加到Text屬性上
由於使用遞歸完成,因此再次調用這個方法的時候,會將存儲d_id的char數組最后一個字符去掉
因此使用chs.Length == 1即可判斷是否為最后一個節點
1 if(chs.Length == 1) 2 { 3 // 將當前節點即為結束節點 4 tnObj.Text = string.Format("{0} {1}", id, d_name); 5 } 6 else 7 { 8 // 如果不是最終節點,則遞歸 9 ShowFromString(new string(chs, 0, chs.Length - 1), id, d_name, tnObj); 10 }
5、 整合一下方法
1 private void ShowFromString(string d_id, string id, string d_name, TreeNode tn) 2 { 3 char[] chs = d_id.ToCharArray(); 4 string nodeStr = chs[chs.Length - 1].ToString(); 5 TreeNode tnObj = null; 6 if(!IsExist(nodeStr, tn, out tnObj)) 7 { 8 tnObj = tn.Nodes.Add(nodeStr); 9 tnObj.Tag = nodeStr; 10 } 11 if(chs.Length == 1) 12 { 13 tnObj.Text = string.Format("{0} {1}", id, d_name); 14 } 15 else 16 { 17 ShowFromString(new string(chs, 0, chs.Length - 1), id, d_name, tnObj); 18 } 19 } 20 private bool IsExist(string nodeText, TreeNode tn, out TreeNode tnObj) 21 { 22 tnObj = null; 23 bool isTrue = false; 24 for(int i = 0; i < tn.Nodes.Count; i++) 25 { 26 if(tn.Nodes[i].Tag as string == nodeText) 27 { 28 isTrue = true; 29 tnObj = tn.Nodes[i]; // 將找到的節點直接返回 30 break; 31 } 32 } 33 return isTrue; 34 }
6、 添加窗體的Load事件,並添加代碼
1 private void Form1_Load(object sender, EventAges e) 2 { 3 // 添加根節點公司, 就是在公司下面添加節點 4 TreeNode tn = tvCompany.Nodes.Add("公司"); 5 // 處理數據庫,讀數據 6 using(SqlConnection conn = new SqlConnection(@"server=.\sqlexpress;database=testdb;integrated security=true")) 7 { 8 using(SqlCommand cmd = new SqlCommand("select d_id, d_name from table_department", conn)) 9 { 10 conn.Open(); 11 using(SqlDataReader reader = cmd.ExecuteReader()) 12 { 13 if(reader.HasRow) 14 { 15 while(reader.Read()) 16 { 17 string d_id = reader.GetString(0); 18 string d_name = reader.GetString(1); 19 ShowFromString(d_id, d_id, d_name, tn); 20 } 21 } 22 } 23 } 24 } 25 }
7、 最后要用XML存儲,遞歸遍歷節點,創建XML數據,就像遍歷文件夾一樣
7.1 添加遞歸方法
1 private void GetXML(TreeNode tn, XElement ele) 2 { 3 // 得到tn下的數據,並加到ele中 4 for (int i = 0; i < tn.Nodes.Count; i++) 5 { 6 // 創建對應節點 7 XElement ele1 = new XElement(tn.Nodes[i].Text.Replace(" ", "_")); // 由於XML中節點名中不允許有空格,所以去掉 8 ele.Add(ele1); 9 10 // 遞歸 11 GetXML(tn.Nodes[i], ele1); 12 } 13 }
7.2 添加按鈕事件
1 private void createXML_Click(object sender, EventArgs e) 2 { 3 XDocument xDoc = new XDocument(); 4 xDoc.Add(new XElement("Company")); 5 6 GetXML(tvCompany.Nodes[0], xDoc.Root); 7 8 xDoc.Save("company.xml"); 9 }
========================================
第二種方法
第一種方法比較簡單,關鍵在於如何處理d_id結構而已,而且順序讀取和創建
實際上TreeNode與XML結構一致,是可以同樣處理的,也就是說先從數據庫中取出數據,生成XML數據,在遞歸遍歷XML數據創建TreeNode
1、 從數據庫中讀取數據,並創建XML文件
樹形結構有一個特點,就是每一個節點只允許有一個父節點和一個子節點,所以可以從數據庫中取出所有數據,得到所有數據的節點片段數據
在根據一定算法將節點連起來
1.1 添加一個方法,該方法完成從數據庫中讀取數據,並得到XML集合(數組也行,個人比較喜歡集合)
1 private List<XElement> GetElementByDatabase() 2 { 3 // 代碼 4 }
1.2 讀取數據庫,創建XML集合
1 private List<XElement> GetElementByDatabase() 2 { 3 List<XElement> list = new List<XElement>(); 4 using (SqlConnection conn = new SqlConnection(@"server=.\sqlexpress;database=testdb;integrated security=true")) 5 { 6 using (SqlCommand cmd = new SqlCommand("select d_id, d_name from table_department", conn)) 7 { 8 conn.Open(); 9 using (SqlDataReader reader = cmd.ExecuteReader()) 10 { 11 if (reader.HasRows) 12 { 13 while (reader.Read()) 14 { 15 string d_id = reader[0].ToString(); 16 string d_name = reader[1].ToString(); 17 // 開始生成XML數據 18 list.Add(new XElement("department", 19 new XAttribute("d_id", d_id), 20 new XAttribute("d_name", d_name) 21 )); 22 } 23 } 24 } 25 } 26 } 27 return list; 28 }
2、 處理XML片段集合的結構,這個結構沒有構成樹狀結構,因此寫一個方法將這個XML片段集合變成一個XML樹片段
這里算法有很多,也可以使用Linq查詢,但是我不打算詳細描述算法,因為有些比較抽象
這里用一個不一定最快,但是很直觀的算法
2.1 添加一個方法
1 public XElement GetXMLTree(List<XElement> listXML) 2 { 3 // 代碼 4 }
2.2 了解到XML每一個節點至多只有一個父節點和子節點,因此只要將處理好節點的去掉即可
同時每一個節點都是通過d_id分層次,而這個層次很有規律,父節點剛好比子節點多一個字符,也就是說
父節點的d_id與子節點的d_id.Substring(1)相同
所以就可以從最長的節點開始找,依次為每一個節點找父節點即可
1 public XElement GetXMLTree(List<XElement> listXML) 2 { 3 // 先為listXML降序排序,因為d_id越長,節點越深 4 listXML.Sort((XElement x1, XElement x2) => { return x2.Attribute("d_id").Value.Length - x1.Attribute("d_id").Value.Length; }); 5 // 從左開始為每一個節點找父節點,很顯然,最長的節點最深 6 // 一旦找到父節點,添加進去,就可以將該節點從集合中移除 7 for (int i = 0; i < listXML.Count; i++) 8 { 9 XElement curr = listXML[i]; 10 for (int j = i + 1; j < listXML.Count; j++) 11 { 12 // 判斷是否為父子關系 13 if (curr.Attribute("d_id").Value.Substring(1) == listXML[j].Attribute("d_id").Value) 14 { 15 listXML[j].Add(curr); 16 } 17 } 18 } 19 return listXML[listXML.Count - 1]; 20 }
3、 XML結構有了,那么就可以保存該數據了
另外遍歷XML結構,加載到TreeView控件中
1 private void ShowTreeNode(TreeNode tn, XElement ele) 2 { 3 foreach (XElement item in ele.Elements()) 4 { 5 TreeNode tn1 = tn.Nodes.Add(string.Format("{0} {1}", item.Attribute("d_id").Value, item.Attribute("d_name").Value)); 6 if (item.HasElements) 7 { 8 ShowTreeNode(tn1, item); 9 } 10 } 11 }
4、 添加Load方法
1 XElement element = null; // 記錄要保存的XML數據 2 private void Form1_Load(object sender, EventArgs e) 3 { 4 List<XElement> xelements = GetElementByDatabase(); 5 6 element = GetXMLTree(xelements); 7 8 ShowTreeNode(tvCompany.Nodes.Add("公司"), element); 9 }
5、 添加保存XML的代碼(添加XElement字段)
1 private void btnSave_Click(object sender, EventArgs e) 2 { 3 XDocument xDoc = new XDocument(element); 4 xDoc.Save("xml.xml"); 5 MessageBox.Show("OK"); 6 }
第三種方法
寫了第二種方法就不太想寫第三種方法了,介紹一下基本思想吧
為數據表創建一個對象模型,但是多出一個字段,就是記錄反序的d_id
那么就可以利用排序等手段創建對象集合
同時解析每一個字符創建TreeView節點了
=================================================
好了,就給出成型的兩個算法吧!如果有時間在慢慢看. 本人見識有限,還請大家多提意見,如果有更好的思路,借鑒一下啊!!!
2012年10月10日晚
