第三天 -- 《2014-07-18 三层架构》1 -- 递归调用、根据实体集合构建树、将树解析实体集合。方法的传递。
一、上午《01、回顾》--《03图解递归》--递归,根据实体集合构建树
1、数据库表结构、树结构

2、表转树(递归添加节点)
递归调用:方法体内调用方法本身,要求:一不能是死循环,即存在结束递归调用的条件。二方法调用方法本身。递归调用的图解(见下图)
注意这么几个关键点:
<1>第1层循环创建的是第1层的节点,后面每层循环都以上一层某节点为父节点,创建该层节点。
<2>调用方法相当于在方法的调用处创建方法的代码。同理递归调用,也是在每次调用处创建方法的代码(见下图)。值得注意的是,每次创建的是相同的方法代码,只是传入参数在变化。
<3>方法执行完毕会回到其主调方法的下一句继续执行。递归调用中这意味着当边界条件满足时,递归返回(见下图)。

1 //UI层主要代码 2 ... 3 BLL.AreasManager _am = new BLL.AreasManager(); 4 private List<Areas> _list = null;//实体集合 5 6 private void FrmAreas_Load(object sender, EventArgs e) 7 {//窗体加载时,通过三层获取实体集合。再构建树 8 _list = _am.GetAllAreasList(false); 9 10 BuildTreeList(0, null);//第一次去找id为0的 11 } 12 13 14 private void BuildTreeList(int parentId, TreeNode parentNode) 15 {//递归构建树 16 foreach (Areas item in _list) 17 { 18 if (item.APid == parentId) 19 {//如果节点的父节点id与指定id相等.==这是结束递归的条件:集合中不再有哪个元素的父id等于指定Id(最叶子层了) 20 TreeNode node = new TreeNode(item.AName); 21 node.Tag = item;//将实体对象绑到节点Tag上,便于以后获取实体 22 23 if (parentNode == null) 24 {//如果节点本身就是根节点,也就是说没有父节点 25 tvlist.Nodes.Add(node);//加到TreeView控件根节点 26 } 27 else 28 { 29 parentNode.Nodes.Add(node); 30 } 31 //现在已经加好了一个节点,下面还要加其他节点 32 BuildTreeList(item.AID, node);//递归调用 33 } 34 35 } 36 }
3、获取树节点上的实体对象
如果之前构建树时,已经将实体绑到对应节点的Tag上,可以通过Tag(见下面代码)获取节点上的实体对象
1 if (tvlist.SelectedNode != null) 2 {//如果选中节点 3 Areas ar = tvlist.SelectedNode.Tag as Areas; 4 if(ar != null) 5 {//如果已经获得实体对象 6 7 } 8 } 9 else 10 { 11 MessageBox.Show("请先选择节点"); 12 }
4、树转表(递归获取子节点Tag,转为实体对象)
从根节点开始循环遍历所有节点,将节点的Tag(前提是构建树的时候已经把每个节点Tag绑上对应实体对象)转为实体对象,添加到集合。
1 //树转表主要代码 2 private List<Areas> _listAreas;//待填充的实体集合 3 ... 4 private void btn_ToList_Click(object sender, EventArgs e) 5 {//点击按钮,将树解析到实体集合 6 _listAreas = new List<Areas>(); 7 ParseTreeList(this.tvlist.Nodes); 8 } 9 10 private void ParseTreeList(TreeNodeCollection nodes) 11 {//递归解析树 12 foreach (TreeNode node in nodes) 13 { 14 Areas a = node.Tag as Areas;//将节点Tag转为实体对象,添加到集合 15 if (a != null) 16 { 17 _listAreas.Add(a); 18 } 19 if (node.Nodes.Count != 0) 20 {//如果存在子节点,递归下一层。==“无子节点”是结束递归的条件 21 ParseTreeList(node.Nodes); 22 } 23 24 } 25 }
二、上午《04、将对象存储到节点的Tag值中》--《05、使用递归的方式获取地区信息》--递归,获取节点路径
1、获取选中节点的全路径(每个节点的Name)
1 tvlist.PathSeparator = "|";//指定节点路径的间隔符 2 ... 3 4 if (tvlist.SelectedNode != null) 5 {//如果选中节点 6 string path = tvlist.SelectedNode.FullPath;//节点全路径 7 } 8 else 9 { 10 MessageBox.Show("请先选择节点"); 11 }
2、递归遍历选中节点的父节点,获取全路径(获取节点绑定的实体对象Id)
private void btnOk_Click(object sender, EventArgs e) {//点击按钮,构建选中节点的全路径和对应实体的全Id路径 if (tvlist.SelectedNode != null) { StringBuilder sb = new StringBuilder(); BuildIdPath(sb, tvlist.SelectedNode);//递归遍历父节点,构建对应实体的全Id路径 _idPath = sb.ToString();//Id路径 _address = tvlist.SelectedNode.FullPath;//节点的全路径 } else { MessageBox.Show("请先选择地点"); } } //递归构建对应实体的全Id路径 private void BuildIdPath(StringBuilder sb, TreeNode node) { if (node.Parent != null) {//先递归遍历父节点 BuildIdPath(sb, node.Parent); } //最后从根节点开始,回递构建Id路径 Areas area = node.Tag as Areas;//获取实体(前提是当初构建树的时候,每个节点Tag绑上实体对象) if (area != null) { if (node.Parent != null) {//如果节点不是根节点 sb.Append("|"); } sb.Append(area.AID); } }
三、上午《06、使用接口实现窗体值的回传》--《08、使用委托做为方法的参数》--递归,获取节点路径
有时希望子窗体能够访问父窗体的方法、属性时,可以用以下几种方式。推荐使用最后一种。
1、构建子窗体时(构造器),传入父窗体对象。--(违背封装原则)
1 //FrmPerson.cs 2 private void btnChoice_Click(object sender, EventArgs e) 3 {//点击按钮,创建打开FrmAreas子窗体 4 FrmAreas frm = new FrmAreas(this);//将父窗体对象传入子窗体 5 6 frm.ShowDialog(); 7 } 8 9 //FrmAreas.cs 10 //FrmAreas窗体重载构造器。接受一个父窗体FrmPerson对象 11 public FrmAreas(FrmPerson frm) 12 { 13 InitializeComponent(); 14 this.frm = frm;//赋值给一个父窗体FrmPerson字段 15 }
2、构建子窗体时(构造器),传入父窗体的接口引用。--(挺好,但是要定义接口)
<1>父窗体需要实现某个接口,将父窗体引用转为接口类型引用,传入子窗体构造器(实例化子窗体)。
<2>子窗体重载构造器,接受一个接口类型引用。
//定义一个接口 public interface IFillData { void FillAreas(string text, string aid);//将传入字符串显示、绑定在主窗体某个控件上 } //FrmPerson.cs 父窗体FrmPerson定义 public partial class FrmPerson : Form, IFillData { private void btnChoice_Click(object sender, EventArgs e) {//点击按钮,创建打开FrmAreas子窗体 FrmAreas frm = new FrmAreas((IFillData)this);//父窗体引用转为接口类型引用,传入子窗体构造器 frm.ShowDialog(); } public void FillAreas(string text, string aid) {//这是实现接口的方法 txtDistrict.Text = text; txtDistrict.Tag = aid; } } //FrmAreas.cs //FrmAreas窗体重载构造器。接受一个IFillData接口类型引用 private IFillData Ifrm;//接口类型字段 ... public FrmAreas(IFillData Ifrm) { InitializeComponent(); this.Ifrm = Ifrm;//赋值给一个IFillData接口类型字段 }
3、传递委托对象。--(方便、安全)
1 //FrmPerson.cs 2 private void btnChoice_Click(object sender, EventArgs e) 3 {//使用Action泛型委托,为FrmAreas构造器传入委托对象。 4 //实例化一个委托对象。它有一个方法:作用是将输入字符串显示、绑定到文本控件。 5 Action<string, string> fillAreas = (text,aid) => { txtDistrict.Text = text; txtDistrict.Tag = aid; } ; 6 FrmAreas frm = new FrmAreas(fillAreas); 7 8 frm.ShowDialog(); 9 } 10 11 //FrmAreas.cs 12 //FrmAreas窗体重载构造器。接受一个委托对象 13 private Action<string, string> _fillArea;//委托对象 14 ... 15 public FrmAreas(Action<string, string> fillArea) 16 { 17 InitializeComponent(); 18 this._fillArea = fillArea; 19 }
浙公网安备 33010602011771号