啊,又无聊了。考试之后的无限空洞。于是今天开始推出无聊扔作业系列(扔鸡蛋扔番茄……
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上一般。
================================================================
那么这次无聊扔作业就到此结束……=_=||
(继续扔鸡蛋扔番茄……