`
RednaxelaFX
  • 浏览: 3015085 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

[无聊扔作业系列](一)一个浏览文件系统的控件

    博客分类:
  • C#
阅读更多
啊,又无聊了。考试之后的无限空洞。于是今天开始推出无聊扔作业系列(扔鸡蛋扔番茄……

Anyway,这是这个学期的.NET课程的第一次实践作业。快两个月前写的东西了...
虽说是课程不过其实没教什么,实践题也都是超简单的类型。也好,不然大四还要做繁琐的基础作业就更郁闷了。

================================================================

题目:
引用
1、请开发一个.NET组件,并在HTML网页中调用。该组件包含了一个tree控件,该控件显示了当前的硬盘目录结构;
2、增强功能。 实现同名不同版本的组件自动部署(不做)。新版本的组件是在上题的基础上,为该组件添加另一个listview控件,用来显示当前选中的目录中的文件。

作业要求:
  • 开发一个.net组件。通过treeview控件展示磁盘的目录结构,listview控件显示选中目录中的文件。
  • 在html中调用上面实现的组件,并予以显示。
这作业要求看起来很简单。有趣的地方是,为了“在html中调用上面实现的组件”,要求安装IIS,把组件部署到IIS上,然后在HTML中显示的这点……吧。

我的开发环境:
Visual Studio 2008 Beta 2
里面用到了C# 3.0和相应的.NET Framework 3.5 Beta 2。
由于使用了.NET Framework 3.5才支持的Lambda Expression,之前版本的都无法使用这程序。

================================================================

OK。那么开工吧~在做这次作业之前,我都还没试过用Visual Studio写C#的WinForm程序。正好可以拿designer来把玩把玩。

然后得到的自动生成designer code如下:
ViewFolderControl.Designer.cs
namespace DotNetAssignment2
{
    partial class ViewFolderControl
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose( bool disposing ) {
            if ( disposing && ( components != null ) ) {
                components.Dispose();
            }
            base.Dispose( disposing );
        }

        #region Component Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent( ) {
            this.panel1 = new System.Windows.Forms.Panel();
            this.splitContainer1 = new System.Windows.Forms.SplitContainer();
            this.FileSystemTreeView = new System.Windows.Forms.TreeView();
            this.FileListView = new System.Windows.Forms.ListView();
            this.panel2 = new System.Windows.Forms.Panel();
            this.statusStrip1 = new System.Windows.Forms.StatusStrip();
            this.CurrentPathLabel = new System.Windows.Forms.ToolStripStatusLabel();
            this.panel1.SuspendLayout();
            this.splitContainer1.Panel1.SuspendLayout();
            this.splitContainer1.Panel2.SuspendLayout();
            this.splitContainer1.SuspendLayout();
            this.panel2.SuspendLayout();
            this.statusStrip1.SuspendLayout();
            this.SuspendLayout();
            // 
            // panel1
            // 
            this.panel1.Anchor = ( ( System.Windows.Forms.AnchorStyles ) ( ( ( ( System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom )
                        | System.Windows.Forms.AnchorStyles.Left )
                        | System.Windows.Forms.AnchorStyles.Right ) ) );
            this.panel1.Controls.Add( this.splitContainer1 );
            this.panel1.Location = new System.Drawing.Point( 0, 0 );
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size( 731, 459 );
            this.panel1.TabIndex = 0;
            // 
            // splitContainer1
            // 
            this.splitContainer1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.splitContainer1.Location = new System.Drawing.Point( 0, 0 );
            this.splitContainer1.Name = "splitContainer1";
            // 
            // splitContainer1.Panel1
            // 
            this.splitContainer1.Panel1.Controls.Add( this.FileSystemTreeView );
            // 
            // splitContainer1.Panel2
            // 
            this.splitContainer1.Panel2.Controls.Add( this.FileListView );
            this.splitContainer1.Size = new System.Drawing.Size( 731, 459 );
            this.splitContainer1.SplitterDistance = 251;
            this.splitContainer1.TabIndex = 0;
            // 
            // FileSystemTreeView
            // 
            this.FileSystemTreeView.Dock = System.Windows.Forms.DockStyle.Fill;
            this.FileSystemTreeView.Location = new System.Drawing.Point( 0, 0 );
            this.FileSystemTreeView.Name = "FileSystemTreeView";
            this.FileSystemTreeView.Size = new System.Drawing.Size( 251, 459 );
            this.FileSystemTreeView.TabIndex = 0;
            // 
            // FileListView
            // 
            this.FileListView.Dock = System.Windows.Forms.DockStyle.Fill;
            this.FileListView.Font = new System.Drawing.Font( "Tahoma", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ( ( byte ) ( 134 ) ) );
            this.FileListView.Location = new System.Drawing.Point( 0, 0 );
            this.FileListView.MultiSelect = false;
            this.FileListView.Name = "FileListView";
            this.FileListView.ShowItemToolTips = true;
            this.FileListView.Size = new System.Drawing.Size( 476, 459 );
            this.FileListView.Sorting = System.Windows.Forms.SortOrder.Ascending;
            this.FileListView.TabIndex = 0;
            this.FileListView.UseCompatibleStateImageBehavior = false;
            this.FileListView.View = System.Windows.Forms.View.Details;
            // 
            // panel2
            // 
            this.panel2.Controls.Add( this.statusStrip1 );
            this.panel2.Dock = System.Windows.Forms.DockStyle.Bottom;
            this.panel2.Location = new System.Drawing.Point( 0, 459 );
            this.panel2.Name = "panel2";
            this.panel2.Size = new System.Drawing.Size( 731, 25 );
            this.panel2.TabIndex = 1;
            // 
            // statusStrip1
            // 
            this.statusStrip1.Items.AddRange( new System.Windows.Forms.ToolStripItem[ ] {
            this.CurrentPathLabel} );
            this.statusStrip1.Location = new System.Drawing.Point( 0, 3 );
            this.statusStrip1.Name = "statusStrip1";
            this.statusStrip1.Size = new System.Drawing.Size( 731, 22 );
            this.statusStrip1.TabIndex = 0;
            this.statusStrip1.Text = "statusStrip1";
            // 
            // CurrentPathLabel
            // 
            this.CurrentPathLabel.Name = "CurrentPathLabel";
            this.CurrentPathLabel.Size = new System.Drawing.Size( 0, 17 );
            // 
            // ViewFolderControl
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 12F );
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add( this.panel2 );
            this.Controls.Add( this.panel1 );
            this.Name = "ViewFolderControl";
            this.Size = new System.Drawing.Size( 731, 484 );
            this.panel1.ResumeLayout( false );
            this.splitContainer1.Panel1.ResumeLayout( false );
            this.splitContainer1.Panel2.ResumeLayout( false );
            this.splitContainer1.ResumeLayout( false );
            this.panel2.ResumeLayout( false );
            this.panel2.PerformLayout();
            this.statusStrip1.ResumeLayout( false );
            this.statusStrip1.PerformLayout();
            this.ResumeLayout( false );

        }

        #endregion

        private System.Windows.Forms.Panel panel1;
        private System.Windows.Forms.Panel panel2;
        private System.Windows.Forms.StatusStrip statusStrip1;
        private System.Windows.Forms.SplitContainer splitContainer1;
        private System.Windows.Forms.TreeView FileSystemTreeView;
        private System.Windows.Forms.ListView FileListView;
        private System.Windows.Forms.ToolStripStatusLabel CurrentPathLabel;
    }
}


这种繁琐又无聊的事情果然还是交给designer来生成好。然后该写那关键的业务逻辑——显示文件系统结构和目录里的文件。本来嘛,(文件)树的问题当然是很容易让人联想到递归;不过这里要是一次过把整个文件系统都扫一遍,把整颗树加载完了之后整体显示在TreeView里的话定然会很慢。所以解决的办法就是只加载两层;每次用户在TreeView里展开一个节点的时候,再去读下一层的文件系统。解决了左边显示文件系统结构的TreeView之后,顺便把选中的节点作为参数传给右边的ListView,这样就可以联动显示目录中的文件了。

代码如下:
ViewFolderControl.cs
/*
 * ViewFolderControl.cs, 2007/09/25, rev 2
 * Written by RednaxelaFX
 */

using System;
using System.IO;
using System.Windows.Forms;

namespace DotNetAssignment2
{
    /// <summary>
    /// A user control that navigates over the file system.
    /// </summary>
    public partial class ViewFolderControl : UserControl
    {
        public ViewFolderControl( ) {
            // Required designer method
            InitializeComponent( );

            // initialize the two controls
            InitializeFileSystemTreeView( );
            InitializeFileListView( );

            // select the first node in TreeView,
            // namely C:\ on Windows or / on UNIX-like systems
            this.FileSystemTreeView.SelectedNode = this.FileSystemTreeView.Nodes[ 0 ];

            // add event handlers
            this.FileSystemTreeView.BeforeExpand += FileSystemTreeView_BeforeExpand;
            this.FileSystemTreeView.AfterSelect += FileSystemTreeView_AfterSelect;
        }

        private void InitializeFileSystemTreeView( ) {
            try {
                // get logical drives on Windows, or root "/" on Unix-like systems
                string[ ] drives = Directory.GetLogicalDrives( );
                if ( drives != null ) {
                    Array.Sort( drives );
                }

                // add root nodes for each drive
                foreach ( string s in drives ) {
                    TreeNode node = new TreeNode( s );
                    node.Tag = s; // save full file name in Tag
                    this.FileSystemTreeView.Nodes.Add( node );
                    AddDirectory( node, s );
                }
            } catch ( Exception e ) {
                MessageBox.Show( String.Format( "Error: {0}{1}{2}",
                                               e.Message,
                                               Environment.NewLine,
                                               e.StackTrace ),
                                               "Error" );
            }
        }

        /*
         * Initialize FileListView with column headers.
         */
        private void InitializeFileListView( ) {
            // Initialize column headers
            ColumnHeader colHeader = null;

            // First header - filename
            colHeader = new ColumnHeader( );
            colHeader.Text = "Filename";
            this.FileListView.Columns.Add( colHeader );

            // Second header - file size in bytes
            colHeader = new ColumnHeader( );
            colHeader.Text = "Size";
            colHeader.TextAlign = HorizontalAlignment.Right;
            this.FileListView.Columns.Add( colHeader );

            // Third header - last accessed time
            colHeader = new ColumnHeader( );
            colHeader.Text = "Last Accessed";
            colHeader.TextAlign = HorizontalAlignment.Right;
            this.FileListView.Columns.Add( colHeader );
        }

        #region Event handlers for FileSystemTreeView

        private void FileSystemTreeView_BeforeExpand( object sender, TreeViewCancelEventArgs e ) {

            // get the node that rose this event
            TreeNode node = e.Node;

            // set the node as selected
            this.FileSystemTreeView.SelectedNode = node;

            // add children to the children of this node,
            // so that the TreeView will tell whether
            // children of this node are leafs or not
            foreach ( TreeNode n in node.Nodes ) {
                // Count == 0 means this is either a leaf node
                // or its children have yet to be added
                if ( n.Nodes.Count == 0 ) {
                    AddDirectory( n, ( String ) n.Tag );
                }
            }
        }

        private void FileSystemTreeView_AfterSelect( object sender, EventArgs e ) {
            // get current path
            string path = ( string ) this.FileSystemTreeView.SelectedNode.Tag;

            // update status strip to loading status
            this.CurrentPathLabel.Text = "Loading...";
            this.statusStrip1.Invalidate( );
            this.statusStrip1.Update( );

            // load FileListView with files in current dir
            FillListView( path );

            // update status strip to current path
            this.CurrentPathLabel.Text = path;
            this.statusStrip1.Invalidate( );
        }

        #endregion

        /*
         * Loads FileListView with files in the spcified path.
         * Clears previous contents in prior to update.
         */
        private void FillListView( string path ) {

            try {
                // ignore empty paths
                if ( path == null || path.Equals( String.Empty ) ) return;

                ListViewItem item = null;
                ListViewItem.ListViewSubItem subitem = null;

                // retrieve the files from path directory
                DirectoryInfo dir = new DirectoryInfo( path );
                FileInfo[ ] files = dir.GetFiles( );
                this.FileListView.Items.Clear( );

                // begin update
                this.FileListView.BeginUpdate( );

                if ( files != null ) {
                    // sort the file list in ascend order
                    Array.Sort( files, ( x, y ) => x.Name.CompareTo( y.Name ) );

                    // add files as ListViewItems
                    foreach ( FileInfo info in files ) {
                        item = new ListViewItem( );
                        item.Text = info.Name;
                        item.Tag = info.FullName;
                        // item.ImageIndex = 1; // icon index, for use with ImageList
                        item.ToolTipText = String.Format( "{0}{1}{2} bytes{1}{3}",
                            info.Name, Environment.NewLine,
                            info.Length.ToString( ),
                            info.LastAccessTime.ToString( ) );

                        subitem = new ListViewItem.ListViewSubItem( );
                        subitem.Text = info.Length.ToString( );
                        item.SubItems.Add( subitem );

                        subitem = new ListViewItem.ListViewSubItem( );
                        subitem.Text = info.LastAccessTime.ToString( );
                        item.SubItems.Add( subitem );

                        this.FileListView.Items.Add( item );
                    }
                }

                // Automatic adjustment of column width,
                // -1 for longest item, -2 for longer of header and longest item
                this.FileListView.Columns[ 0 ].Width = -2;
                this.FileListView.Columns[ 1 ].Width = -2;
                this.FileListView.Columns[ 2 ].Width = -2;

                // end update, update the ListView
                this.FileListView.EndUpdate( );
            } catch ( Exception e ) {
                MessageBox.Show( String.Format( "Error: {0}{1}{2}",
                                               e.Message,
                                               Environment.NewLine,
                                               e.StackTrace ),
                                               "Error" );
                this.FileListView.Items.Clear( );
                this.FileListView.EndUpdate( );
            }
        }

        /*
         * Adds children directories to the specified node.
         */
        private void AddDirectory( TreeNode node, string path ) {

            // ignore empty paths
            if ( path == null || path.Equals( String.Empty ) ) return;

            try {
                // retrieve subdirectories from path directory
                DirectoryInfo dir = new DirectoryInfo( path );
                DirectoryInfo[ ] dirs = dir.GetDirectories( );

                if ( dirs != null ) {
                    // sort the directory list in ascend order
                    Array.Sort( dirs, ( x, y ) => x.Name.CompareTo( y.Name ) );

                    // add directories as children nodes
                    foreach ( DirectoryInfo info in dirs ) {
                        TreeNode childNode = new TreeNode( info.Name );
                        childNode.Tag = info.FullName;
                        // set icon index when "not-selected"
                        childNode.ImageIndex = 2;
                        // set icon index when "selected"
                        childNode.SelectedImageIndex = 0;
                        // add child node
                        node.Nodes.Add( childNode );
                    }
                }
            } catch ( Exception ) {
                /*
                MessageBox.Show( String.Format( "Error: {0}{1}{2}",
                                               e.Message,
                                               Environment.NewLine,
                                               e.StackTrace ),
                                               "Error" );
                 */
            }
        }
    }
}


这里没什么特别的。唯一能称得上有趣的地方,就是其中的这么一小块:
( x, y ) => x.Name.CompareTo( y.Name )

在147行和206行分别出现过一次。这就是C# 3.0中所谓的Lambda Expression的一个使用。看起来很简洁吧?本来按照“不要写重复的代码”原则,我应该把这两处相同的代码用同一个变量去表示的(例如说写个protected/private的static变量)。问题是这两个变量的类型不同:
147行的那个,类型是Func<FileInfo, FileInfo, int>,
206行的那个,类型是Func<DirectoryInfo, DirectoryInfo, int>
要写成一个变量不可能,要写成两个变量的话那又没必要了。于是就这么原样放着了。
[color=darkblue]编辑: 如果是局部变量,本来可以通过C# 3.0的局部变量类型推断来解决类型不同的问题……问题是一个Lambda Expression并不能被赋值给一个隐式类型的变量,换句话说不能写出类似var f = x => x + 1;的语句,否则会得到编译错误:
引用
Microsoft (R) Visual C# 2008 Compiler Beta 2 version 3.05.20706.1 for Microsoft (R) .NET Framework version 3.5
版权所有 (C) Microsoft Corporation。保留所有权利。

test.cs(10,17): error CS0815: 无法将“lambda 表达式”赋值给隐式类型的局部变量

嘛,类型推断不是完全做不了,虽然“裸”的Lambda Expression确实是没办法就这么出现在隐式类型局部变量声明的右手边,但有个work-around:
Func<A, R> MakeFunction<A, R>( Func<A, R> f ) { return f; }

这样就可以写出var f = MakeFunction( ( int x ) => x + 1 );了。注意这里还是需要给类型推断引擎以足够的类型信息,所以不写那个int(或者随便什么其它支持+运算符的类型)是不行的。这个MakeFunction的用法来自Eric Lippert的blog

上面说明了类型不匹配带来的问题。况且,即使类型问题能解决,var关键字也只能在局部变量的声明中使用,成员变量用不了。所以这次作业的代码里很无奈,还是得把字面上一样的语句写两次了[/color]

当然,这个Lambda Expression也可以用C# 2.0里的匿名函数表示:(以147行的版本为例)
delegate ( FileInfo x, FileInfo y ) {
    return x.Name.CompareTo( y.Name );
}

不过匿名函数的写法明显比Lambda Expression的繁琐些……
关于Lambda Expression,可以参考下MSDN上一篇文章

================================================================

OK,控件写完了,把它编译成一个DLL Assembly,就成为一个符合题目要求的组件。在部署到IIS上之前,至少想先看看它能否被别的WinForm程序调用。于是写了个测试project,如下:
Form1.Designer.cs
namespace TestDriver
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose( bool disposing ) {
            if ( disposing && ( components != null ) ) {
                components.Dispose();
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent( ) {
            this.viewFolderControl1 = new DotNetAssignment2.ViewFolderControl();
            this.SuspendLayout();
            // 
            // viewFolderControl1
            // 
            this.viewFolderControl1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.viewFolderControl1.Location = new System.Drawing.Point( 0, 0 );
            this.viewFolderControl1.Name = "viewFolderControl1";
            this.viewFolderControl1.Size = new System.Drawing.Size( 730, 440 );
            this.viewFolderControl1.TabIndex = 0;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 12F );
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size( 730, 440 );
            this.Controls.Add( this.viewFolderControl1 );
            this.Name = "Form1";
            this.Text = "Folder Viewer";
            this.ResumeLayout( false );

        }

        #endregion

        private DotNetAssignment2.ViewFolderControl viewFolderControl1;
    }
}


Form1.cs
using System.Windows.Forms;

namespace TestDriver
{
    public partial class Form1 : Form
    {
        public Form1( ) {
            InitializeComponent();
        }
    }
}


Program.cs
using System;
using System.Windows.Forms;

namespace TestDriver
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main( ) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault( false );
            Application.Run( new Form1() );
        }
    }
}


然后我们就得到了:


简陋是简陋了点,不过凑和吧,反正是小作业……

================================================================

本地测试成功,该部署到IIS上了。

先确保IIS已正确安装。然后,根据课件的指示,在这里:Web页面嵌入复杂WinForm控件权限问题查阅如何在IIS上给本地程序足够的权限来访问文件系统。
解决方法是,在SDK Command Prompt下,输入下面命令:
引用
caspol -quiet -machine -addgroup All_Code -url http://localhost/* FullTrust -n OGTLogDBMS -d 作业本地访问权限


嗯,IIS也设置好了,把对应的HTML网页写出来:
<html>
<title>.NET Course Assignment for Chapter 2</title>
<body>
	<object id="ViewFolderCtrl" classid="http:DotNetAssignment2.dll#DotNetAssignment2.ViewFolderControl" />
</body>
</html>


OK。大功告成。显示出来的效果跟在本地程序一样所以不另外贴图了。

================================================================

光是在Windows上玩C#果然还是不够意思。把上面用到Lambda Expression的地方改回用delegate,然后把代码直接放到linux下也可以用的哦。我用的是Mono 1.2.5,在OpenSUSE 10.2上编译运行都正常。效果……基本上与在Windows上一般。

================================================================

那么这次无聊扔作业就到此结束……=_=||
(继续扔鸡蛋扔番茄……
分享到:
评论
4 楼 RednaxelaFX 2007-11-20  
哈哈,shawind说到这作业的一个痛处了.
老师在布置作业的时候并没有说明要"如何"让控件在HTML里显示出来.更糟糕的是,要求要用System.Windows.Forms的控件而不是System.Web.UI的.要在ASP.NET里把控件正确显示出来,显然应该用web系列的控件才对.
事实上这作业从来没说过要用ASP.NET.而且不用ASP.NET也很方便,创建WinForm的方法与步骤都跟平常的一样,接下来只要一个简短的HTML就完事 ^ ^
3 楼 shawind 2007-11-20  
直接在Visual Studio中建WebApp工程不知道能不能更方便的达成这样的目的.
2 楼 RednaxelaFX 2007-11-17  
超人是啥?

话说刚才稍微更新了一下……
不过说起来,可能是因为这程序太简单了,所以在语言间迁移反而“困难”——因为没什么复杂的逻辑,而语言间API的差异占到了矛盾的主要一方。

顺带一提,在OpenSUSE下我是用MonoDevelop...但是玩意不支持WinForm,想在linux上用C#写WinForm界面还得另外用些工具才顺手了……
1 楼 lwwin 2007-11-17  
很好玩,发现偶终于不是超人了TOT~
不过代码大部分都被忽略了因为偶完全不懂= =

相关推荐

Global site tag (gtag.js) - Google Analytics