又来了,这次是Level 100,木哈哈,简单总结及有些小细节需要注意的地方。
应用程序的设计风格大致上分三种:MDI,SDI,资源管理器。所谓MDI的意思是多文档应用程序,例如Excel,程序的特点是有父窗口,也叫容器窗口,其他窗口均包含在父窗口内,是大多数复杂应用程序的设计风格。SDI就是单文档界面应用程序,例如Winrar,特点是只有一个底层窗口,所有弹出窗口均以ShowDialog方式显示,适合功能简单,需求不高的应用程序。资源管理器,这种设计风格基本上都要使用到两个控件-TreeView及ListView,这种设计风格很少独立使用,多与以上两种混合使用,尤以MDI风格居多。MDI/SDI共有的特点还有菜单栏(MenuStrip)、工具栏(ToolStrip)、状态栏(StatusStrip)。
MDI/SDI所用到的普通控件就那么几种,MenuStrip,ToolStrip,StatusStrip,ImageList,Timer等等,都是拖拽就能搞定,简单的很,但是几点需要注意。1.如果是MDI风格,记得将主窗体的IsMdiContainer属性设置为true,并new新窗口对象后指定其父窗体,且不要调用ShowDialog()方法。2.控件一般都有几个属性比较需要注意,一个是Dock属性,一个是Anchor,一个是AutoSize,Dock是定义要绑定到控件的边框,简单的说就是控件的排列方式,Anchor是当主窗体缩放时,控件的边缘与哪边距离保持不变,AutoSize是设置控件是否自动调整大小以适应容器大小,比较明显一个例子,VS2003里加个StatusBar,然后加几个Panel,然后就开始调Panel大小,当用VS2005加个StatusStrip,加几个StatusLabel,好了,大小调不了了,这里就需要设置StatusLabel的AutoSize属性,改为False就OK了。3.除了上面三个,还要注意各个控件的Items Collection,各种Style及各种Alignment。
我这篇文章想多写一点TreeView及ListView,这两种控件不是我们拖拖拽拽就搞的定的,而且TreeView与数据库联系紧密,也涉及到一个很麻烦的东西-递归(Recursion),ListView则涉及到四种呈现方式,在某些方面,ListView还是比DataGridView优秀的,话不多说,扔一个例子。
示例1:实现简单的Windows资源管理器
窗体:
界面左边是个TreeView,右边是个ListView,因为是简单实现,没有各种ico,当在左边选择节点时,右边显示相应文件夹中的内容(注意,即包含文件夹,也包含文件)。
OK,先分析,TreeView中的内容在窗体Load的时候需要做什么工作?贱人甲跳出来大喊:“查询出所有节点及节点下的节点及节点下的节点下的节点……显示到TreeView中”林大少将之一脚T飞,这是不科学的,贱人乙跳出来大喊:“老子电脑牛B,性能有保障!”林大少将之一脚T飞,这也是不科学的,我这破电脑不说了,要是你160G里全是文件,窗体Load时查询全部文件,那得了,数分钟内你别干别的了,等吧。为了提高程序性能,最科学的是窗体生成时,产生两级节点,为什么产生两级?不产生两级你盘符前面就没+号。。太丑,然后应该再写TreeView的
BeforeExpand事件,当点击节点时,继续产生下一级的下一级节点,以此类推。
代码:
private void Form1_Load(object sender, EventArgs e) //窗体生成事件 { this.listView1.Columns.Add("Name", 250, HorizontalAlignment.Left); try { //获得以本地计算机硬盘驱动器名字为成员的string数组 string[] drivers = Environment.GetLogicalDrives(); for (int i = 0; i < drivers.Length; i++) { //以每个驱动器名为参数构造节点 TreeNode node = new TreeNode(drivers[i]); //将每个节点添加到TreeView控件中 this.treeView1.Nodes.Add(node); //调用获得该节点下子节点方法 GetChild(node); } } catch //注意一定要写Try catch,不要忘记你的光盘驱动器。。 { } } private void GetChild(TreeNode node) //设置参数节点下的节点方法 { try { //获得该节点绝对路径 string path = GetPath(node); //只对路径操作,因此用Directory或者DirectoryInfo DirectoryInfo di = new DirectoryInfo(path); //获得该路径下的所有文件夹路径集合 DirectoryInfo[] directory = di.GetDirectories(); for (int i = 0; i < directory.Length; i++) { //以该集合内所有文件夹名做为参数构造节点 TreeNode tn = new TreeNode(directory[i].Name); //将构造的节点挂到父节点上 node.Nodes.Add(tn); } } catch //必须,光盘驱动器 { } } private string GetPath(TreeNode node) //获得节点路径方法 { //下面单独说,递归方法 if (node.Parent == null) { return node.Text; } return Path.Combine(GetPath(node.Parent), node.Text); } private void treeView1_BeforeExpand(object sender,TreeViewCancelEventArgs e) //展开节点事件 { //获得事件源的节点 TreeNode node = e.Node; //获得该节点下的所有节点 TreeNodeCollection tnc = node.Nodes; for (int i = 0; i < tnc.Count; i++) { //设置节点下的所有节点 GetChild(tnc[i]); } } |
大体过程就是这样,除了那个递归方法,其他的只是注意下写法,还有展开节点事件中,不要取得事件源的节点就把它当参数去调GetChild方法,仔细考虑考虑应该知道为什么。
递归是个比较难理解的方法,很容易被绕进去,我举个简单的例子,我们可以大概看一下递归是个什么流程。
示例2:用递归做1到10的递加。
代码:
private int getNum(int i) { if (i == 10) { return 10; } return i + getNum(++i); } int total=getNum(1); |
OK就这么几行,你光死盯着看绝对要被绕进去,你加断点一步一步走都不一定看的明白,我建议还是拿笔写写,当传个1进去,程序怎么走的?好,程序走到i+getNum(++i),写出来就是1+getNum(2),OK,调用自身,2进来了运行到i+getNum(++i),变成了1+2+getNum(3)了,依次类推,最后变成1+2+3+...+9+getNum(10),当10进来的时候,符合条件,return一个10,表达式于是变成1+2+3+...+9+10,return了回去,于是就实现了1到10的累加,需要注意的是如果需要使用递归算法,需要指定Break条件,不然跟死循环没什么区别,OK,反过来看示例1的递归取得节点的绝对路径方法:
private string GetPath(TreeNode node) { if (node.Parent == null) { return node.Text; } return Path.Combine(GetPath(node.Parent), node.Text); } |
好了这个跟刚才我们写的那个原理一样,如果看不懂,还是建议拿笔写写看,Path.Combine是将两个string组合生成一个路径字符串,好了写来看看,当node传进来运行到Path.Combine(GetPath(node.Parent), node.Text),接着把node.Parent当参数调自身,表达式变成Path.Combine((Path.Combine(node.Parent.Parent),node.Parent.Text),node.Text),举个实际的例子,打个比方,E:/Program Files/MSDN/MSDN8.0这个文件夹,当我们要取MSDN8.0的绝对路径的时候,我们传的是MSDN8.0,进了方法,Path.Combine(GetPath("MSDN"),"MSDN8.0"),好,再次调用,Path.Combine((Path.Combine(GetPath("Program Files")),"MSDN"),"MSDN8.0"),再调(别嫌麻烦。。)Path.Combine((Path.Combine(Path.Combine(GetPath("E:"),"Program Files")),"MSDN"),"MSDN8.0"),好了,再次调用的时候满足条件了,因为E:上面没节点了,也就是GetPath("E:")将返回node.Text,也就是返回"E:",那最里层的Combine方法将返回"E:/Program Files",往外走,倒数第二层返回"E:/Program Files/MSDN",继续走,最后一层返回"E:/Program Files/MSDN/MSDN8.0/",这就是这个递归程序路线的大致走向,因此你可以看出,要灵活的运用递归是有一定难度的,除了需要大量的积累,还需要有点感觉。
最后ListView里显示节点内文件、文件夹代码:
private void getContent(TreeNode node) { try { //获得节点路径 sting path = GetPath(node); DirectoryInfo di = new DirectoryInfo(path); FileInfo[] fi = di.GetFiles(); DirectoryInfo[] directory=di.GetDirectories(); for (int i = 0; i < fi.Length; i++) { ListViewItem lvi = new ListViewItem(); lvi.SubItems[0].Text = fi[i].Name; this.listView1.Items.Add(lvi); } for (int i = 0; i < directory.Length; i++) { ListViewItem lvi = new ListViewItem(); lvi.SubItems[0].Text = directory[i].Name; this.listView1.Items.Add(lvi); } } catch { } } private void treeView1_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e) { TreeNode node = e.Node; if (node != null) { this.listView1.Clear(); getContent(node); } } |
又跟HTML代码较上劲了,写注释老是出问题,不写了,只需要注意几个地方就行了,System.IO.Directory或者System.IO.DirectoryInfo都是目录级别操作的类,而File或者FileInfo都是文件级别操作的类,每种两个,Directory及File类主要是工具类,其中的方法都是静态方法,如果没有特殊需要可以使用这两个类,剩下两个都是有实例方法的类,与前面两个大体相同,少许的不同,比如FileInfo及DirectoryInfo都有Name属性,可以直接获得文件或者文件路径的文件名,看需求情况,另外ListView介绍的比较少,主要注意下该控件的四种文件呈现方式及ListViewItem第一列的赋值方法就可以了,其他的与ListBox用法差不多。