深入探索Windows Forms应用程序中的文件操作与TreeView事件处理
1. 填充TreeView节点
在构建Windows Forms应用程序时,填充TreeView节点是一个重要的步骤。当创建新节点时,需要传入新的父节点、父节点的完整路径以及相应的标志,同时将当前层级加1。例如,如果初始层级为1,下一次调用时层级将变为2。
在调用
TreeNode
构造函数时,使用
DirectoryInfo
对象的
Name
属性,而调用
GetSubDirectoryNodes()
方法时,则使用
FullName
属性。以目录
C:\Windows\Media\Sounds
为例,
FullName
属性返回完整路径,而
Name
属性仅返回
Sounds
。将名称传递给节点,因为这是要在TreeView中显示的内容;将带路径的完整名称传递给
GetSubDirectoryNodes()
方法,以便该方法能在磁盘上定位所有子目录。
获取目录中的文件时,如果
getFileNames
标志为
true
,则调用
DirectoryInfo
对象的
GetFiles()
方法,返回一个
FileInfo
对象数组。然后可以遍历该集合,访问
FileInfo
对象的
Name
属性,并将该名称传递给
TreeNode
的构造函数,将其添加到父节点的
Nodes
集合中,从而创建子节点。由于文件没有子目录,因此这里不需要递归。
if (getFileNames)
{
// Get any files for this node.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
TreeNode fileNode = new TreeNode(file.Name);
parentNode.Nodes.Add(fileNode);
}
}
2. 处理TreeView事件
在这个应用程序中,需要处理多种TreeView事件,包括用户点击取消、复制、清除或删除按钮,以及点击左侧TreeView中的复选框、右侧TreeView中的节点或任一视图中的加号。
2.1 点击源TreeView
源TreeView对象的用户会勾选想要复制的文件和目录。每次用户点击表示文件或目录的复选框时,会触发多个事件,需要处理的事件是
AfterCheck
。可以实现一个自定义事件处理方法
tvwSource_AfterCheck()
,并将其绑定到事件处理程序。
tvwSource.AfterCheck +=
new System.Windows.Forms.TreeViewEventHandler
(this.tvwSource_AfterCheck);
private void tvwSource_AfterCheck (
object sender, System.Windows.Forms.TreeViewEventArgs e)
{
SetCheck(e.Node,e.Node.Checked);
}
SetCheck()
方法会递归地为所有包含的文件夹设置复选标记。对于
Nodes
集合中的每个
TreeNode
,检查它是否为叶子节点(即其
Nodes
集合的计数为0)。如果是叶子节点,则将其
Check
属性设置为传入的参数值;如果不是叶子节点,则进行递归调用。
private void SetCheck(TreeNode node, bool check)
{
foreach (TreeNode n in node.Nodes)
{
n.Checked = check;
if (n.Nodes.Count != 0)
{
SetCheck(n,check);
}
}
}
2.2 展开目录
每次点击源(或目标)TreeView中目录旁边的
+
号时,需要展开该目录。为此,需要为
BeforeExpand
事件创建一个事件处理程序。由于源和目标TreeView的事件处理程序相同,可以创建一个共享的事件处理程序。
private void tvwExpand(object sender, TreeViewCancelEventArgs e)
{
TreeView tvw = (TreeView)sender;
bool getFiles = tvw == tvwSource;
TreeNode currentNode = e.Node;
string fullName = currentNode.FullPath;
currentNode.Nodes.Clear();
GetSubDirectoryNodes(currentNode, fullName, getFiles, 1);
}
先将委托传入的对象转换为
TreeView
类型,然后确定是否要获取正在打开的目录中的文件。接着获取当前节点及其完整路径名,清除其所有子节点,再调用
GetSubDirectoryNodes()
方法重新填充子节点。
2.3 点击目标TreeView
目标TreeView的另一个事件处理程序是
AfterSelect
。当用户选择一个目录时,需要将其完整路径放入表单左上角的文本框中。可以通过递归向上遍历节点,找到每个父目录的名称,构建完整路径。
private void tvwTargetDir_AfterSelect (
object sender, System.Windows.Forms.TreeViewEventArgs e)
{
string theFullPath = GetParentString(e.Node);
if (theFullPath.EndsWith("\\"))
{
theFullPath = theFullPath.Substring(0,theFullPath.Length-1);
}
txtTargetDir.Text = theFullPath;
}
private string GetParentString( TreeNode node )
{
if ( node.Parent == null )
{
return node.Text;
}
else
{
return GetParentString( node.Parent ) + node.Text +
( node.Nodes.Count == 0 ? "" : "\\" );
}
}
3. 处理按钮事件
除了TreeView事件,还需要处理按钮事件,包括清除、复制和删除按钮。
3.1 处理清除按钮事件
处理清除按钮的
Click
事件很简单,只需调用
SetCheck()
方法,将根节点的所有子节点的复选标记清除。
private void btnClear_Click( object sender, System.EventArgs e )
{
foreach ( TreeNode node in tvwSource.Nodes )
{
SetCheck( node, false );
}
}
3.2 实现复制按钮事件
处理复制按钮的
Click
事件时,首先需要获取用户选择的文件列表。可以委托
GetFileList()
方法来完成这个任务。
private void btnCopy_Click (object sender, System.EventArgs e)
{
List<FileInfo> fileList = GetFileList();
foreach ( FileInfo file in fileList )
{
try
{
lblStatus.Text = "Copying " + txtTargetDir.Text +
"\\" + file.Name + "...";
Application.DoEvents();
file.CopyTo( txtTargetDir.Text + "\\" +
file.Name, chkOverwrite.Checked );
}
catch ( Exception ex )
{
MessageBox.Show( ex.Message );
}
}
lblStatus.Text = "Done.";
}
GetFileList()
方法会遍历源TreeView控件,调用
GetCheckedFiles()
方法获取所有选中文件的完整路径,然后创建一个
FileInfo
对象列表,并对其进行排序。
private List<FileInfo> GetFileList( )
{
List<string> fileNames = new List<string>( );
foreach (TreeNode theNode in tvwSource.Nodes)
{
GetCheckedFiles(theNode, fileNames);
}
List<FileInfo> fileList = new List<FileInfo>( );
foreach (string fileName in fileNames)
{
FileInfo file = new FileInfo(fileName);
if (file.Exists)
{
fileList.Add(file);
}
}
IComparer<FileInfo> comparer = ( IComparer<FileInfo> ) new FileComparer();
fileList.Sort(comparer);
return fileList;
}
为了对
FileInfo
对象列表进行排序,需要创建一个实现
IComparer<FileInfo>
接口的
FileComparer
类。
public class FileComparer : IComparer<FileInfo>
{
public int Compare(FileInfo file1, FileInfo file2)
{
if ( file1.Length > file2.Length )
{
return -1;
}
if ( file1.Length < file2.Length )
{
return 1;
}
return 0;
}
}
3.3 处理删除按钮事件
处理删除按钮的
Click
事件时,首先询问用户是否确定要删除文件。如果用户确认,则获取选中文件列表,并遍历该列表,逐个删除文件。
private void btnDelete_Click( object sender, System.EventArgs e )
{
System.Windows.Forms.DialogResult result =
MessageBox.Show(
"Are you quite sure?",
"Delete Files",
MessageBoxButtons.OKCancel,
MessageBoxIcon.Exclamation,
MessageBoxDefaultButton.Button2 );
if ( result == System.Windows.Forms.DialogResult.OK )
{
List<FileInfo> fileNames = GetFileList( );
foreach ( FileInfo file in fileNames )
{
try
{
lblStatus.Text = "Deleting " +
txtTargetDir.Text + "\\" +
file.Name + "...";
Application.DoEvents();
file.Delete();
}
catch ( Exception ex )
{
MessageBox.Show( ex.Message );
}
}
lblStatus.Text = "Done.";
Application.DoEvents();
}
}
4. 完整示例代码
以下是完整的示例代码,展示了如何实现上述功能。
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
namespace FileCopier
{
partial class frmFileCopier : Form
{
private const int MaxLevel = 2;
public frmFileCopier()
{
InitializeComponent();
FillDirectoryTree(tvwSource, true);
FillDirectoryTree(tvwTarget, false);
}
public class FileComparer : IComparer<FileInfo>
{
public int Compare(FileInfo file1, FileInfo file2)
{
if ( file1.Length > file2.Length )
{
return -1;
}
if ( file1.Length < file2.Length )
{
return 1;
}
return 0;
}
public bool Equals(FileInfo x, FileInfo y) { throw new NotImplementedException(); }
public int GetHashCode(FileInfo x) { throw new NotImplementedException(); }
}
private void FillDirectoryTree(TreeView tvw, bool isSource)
{
tvw.Nodes.Clear();
string[] strDrives = Environment.GetLogicalDrives();
foreach ( string rootDirectoryName in strDrives )
{
try
{
DirectoryInfo dir = new DirectoryInfo(rootDirectoryName);
dir.GetDirectories();
TreeNode ndRoot = new TreeNode(rootDirectoryName);
tvw.Nodes.Add(ndRoot);
if ( isSource )
{
GetSubDirectoryNodes(ndRoot, ndRoot.Text, true, 1);
}
else
{
GetSubDirectoryNodes(ndRoot, ndRoot.Text, false, 1);
}
}
catch
{
}
Application.DoEvents();
}
}
private void GetSubDirectoryNodes(TreeNode parentNode, string fullName, bool getFileNames, int level)
{
DirectoryInfo dir = new DirectoryInfo(fullName);
DirectoryInfo[] dirSubs = dir.GetDirectories();
foreach ( DirectoryInfo dirSub in dirSubs )
{
if ( ( dirSub.Attributes & FileAttributes.Hidden ) != 0 )
{
continue;
}
TreeNode subNode = new TreeNode(dirSub.Name);
parentNode.Nodes.Add(subNode);
if ( level < MaxLevel )
{
GetSubDirectoryNodes(subNode, dirSub.FullName, getFileNames, level + 1);
}
}
if ( getFileNames )
{
FileInfo[] files = dir.GetFiles();
foreach ( FileInfo file in files )
{
TreeNode fileNode = new TreeNode(file.Name);
parentNode.Nodes.Add(fileNode);
}
}
}
private void btnCopy_Click(object sender, System.EventArgs e)
{
List<FileInfo> fileList = GetFileList();
foreach ( FileInfo file in fileList )
{
try
{
lblStatus.Text = "Copying " + txtTargetDir.Text +
"\\" + file.Name + "...";
Application.DoEvents();
file.CopyTo(txtTargetDir.Text + "\\" +
file.Name, chkOverwrite.Checked);
}
catch ( Exception ex )
{
MessageBox.Show(ex.Message);
}
}
lblStatus.Text = "Done.";
}
private void btnClear_Click(object sender, System.EventArgs e)
{
foreach ( TreeNode node in tvwSource.Nodes )
{
SetCheck(node, false);
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void GetCheckedFiles(TreeNode node, List<string> fileNames)
{
if ( node.Nodes.Count == 0 )
{
if ( node.Checked )
{
string fullPath = GetParentString(node);
fileNames.Add(fullPath);
}
}
else
{
foreach ( TreeNode n in node.Nodes )
{
GetCheckedFiles(n, fileNames);
}
}
}
private string GetParentString(TreeNode node)
{
if ( node.Parent == null )
{
return node.Text;
}
else
{
return GetParentString(node.Parent) + node.Text +
( node.Nodes.Count == 0 ? "" : "\\" );
}
}
private List<FileInfo> GetFileList()
{
List<string> fileNames = new List<string>( );
foreach (TreeNode theNode in tvwSource.Nodes)
{
GetCheckedFiles(theNode, fileNames);
}
List<FileInfo> fileList = new List<FileInfo>( );
foreach (string fileName in fileNames)
{
FileInfo file = new FileInfo(fileName);
if (file.Exists)
{
fileList.Add(file);
}
}
IComparer<FileInfo> comparer = ( IComparer<FileInfo> ) new FileComparer();
fileList.Sort(comparer);
return fileList;
}
private void btnDelete_Click(object sender, System.EventArgs e)
{
System.Windows.Forms.DialogResult result =
MessageBox.Show(
"Are you quite sure?",
"Delete Files",
MessageBoxButtons.OKCancel,
MessageBoxIcon.Exclamation,
MessageBoxDefaultButton.Button2 );
if ( result == System.Windows.Forms.DialogResult.OK )
{
List<FileInfo> fileNames = GetFileList( );
foreach ( FileInfo file in fileNames )
{
try
{
lblStatus.Text = "Deleting " +
txtTargetDir.Text + "\\" +
file.Name + "...";
Application.DoEvents();
file.Delete();
}
catch ( Exception ex )
{
MessageBox.Show(ex.Message);
}
}
lblStatus.Text = "Done.";
Application.DoEvents();
}
}
private void tvwTargetDir_AfterSelect(object sender, System.Windows.Forms.TreeViewEventArgs e)
{
string theFullPath = GetParentString(e.Node);
if ( theFullPath.EndsWith("\\"))
{
theFullPath = theFullPath.Substring(0, theFullPath.Length - 1);
}
txtTargetDir.Text = theFullPath;
}
private void tvwSource_AfterCheck(object sender, System.Windows.Forms.TreeViewEventArgs e)
{
SetCheck(e.Node, e.Node.Checked);
}
private void SetCheck(TreeNode node, bool check)
{
foreach ( TreeNode n in node.Nodes )
{
n.Checked = check;
if ( n.Nodes.Count != 0 )
{
SetCheck(n, check);
}
}
}
private void tvwExpand(object sender, TreeViewCancelEventArgs e)
{
TreeView tvw = ( TreeView ) sender;
bool getFiles = tvw == tvwSource;
TreeNode currentNode = e.Node;
string fullName = currentNode.FullPath;
currentNode.Nodes.Clear();
GetSubDirectoryNodes(currentNode, fullName, getFiles, 1);
}
}
}
通过以上步骤和代码示例,我们可以实现一个功能丰富的Windows Forms应用程序,用于文件的复制、删除和目录的管理,并能有效地处理TreeView和按钮事件。
下面是相关操作的流程图:
graph TD;
A[开始] --> B[填充TreeView节点];
B --> C[处理TreeView事件];
C --> C1[点击源TreeView];
C --> C2[展开目录];
C --> C3[点击目标TreeView];
C --> D[处理按钮事件];
D --> D1[处理清除按钮事件];
D --> D2[实现复制按钮事件];
D --> D3[处理删除按钮事件];
D1 --> E[结束];
D2 --> E;
D3 --> E;
以下是事件处理的总结表格:
| 事件类型 | 处理方法 | 功能描述 |
| ---- | ---- | ---- |
| 源TreeView的AfterCheck | tvwSource_AfterCheck | 递归设置选中节点及其子节点的复选标记 |
| TreeView的BeforeExpand | tvwExpand | 展开目录,重新填充子节点 |
| 目标TreeView的AfterSelect | tvwTargetDir_AfterSelect | 获取选中目录的完整路径并显示在文本框中 |
| 清除按钮的Click | btnClear_Click | 清除源TreeView中所有节点的复选标记 |
| 复制按钮的Click | btnCopy_Click | 获取选中文件列表,复制文件到目标目录 |
| 删除按钮的Click | btnDelete_Click | 询问用户是否确认,若确认则删除选中文件 |
深入探索Windows Forms应用程序中的文件操作与TreeView事件处理(续)
5. 代码详解与技术要点分析
5.1 递归在代码中的应用
递归在整个应用程序中起到了关键作用,特别是在处理目录结构和文件选择时。例如,
GetSubDirectoryNodes()
方法递归地获取子目录节点,
SetCheck()
方法递归地设置或清除节点的复选标记,
GetCheckedFiles()
方法递归地查找所有选中的文件,
GetParentString()
方法递归地构建节点的完整路径。
递归的核心思想是将一个大问题分解为多个相同类型的小问题,直到达到终止条件。以
SetCheck()
方法为例,其递归过程如下:
1. 遍历当前节点的所有子节点。
2. 将子节点的
Checked
属性设置为传入的
check
值。
3. 如果子节点还有子节点(即不是叶子节点),则递归调用
SetCheck()
方法处理该子节点。
递归的终止条件是节点没有子节点(即叶子节点),此时不再进行递归调用。这种递归方式可以确保操作(如设置复选标记)能够深入到目录结构的每一个层次。
5.2 文件排序的实现
在处理复制操作时,需要对用户选择的文件列表进行排序,以便将大文件优先复制,从而更紧密地填充目标磁盘。为了实现文件排序,我们创建了一个
FileComparer
类,该类实现了
IComparer<FileInfo>
接口。
IComparer<FileInfo>
接口要求实现
Compare()
方法,该方法接受两个
FileInfo
对象作为参数,并根据它们的大小关系返回一个整数值。在
FileComparer
类的
Compare()
方法中,我们将返回值进行了反转,使得文件列表按从大到小的顺序排序。
public class FileComparer : IComparer<FileInfo>
{
public int Compare(FileInfo file1, FileInfo file2)
{
if ( file1.Length > file2.Length )
{
return -1;
}
if ( file1.Length < file2.Length )
{
return 1;
}
return 0;
}
public bool Equals(FileInfo x, FileInfo y) { throw new NotImplementedException(); }
public int GetHashCode(FileInfo x) { throw new NotImplementedException(); }
}
在
GetFileList()
方法中,我们创建了
FileComparer
类的实例,并将其传递给
List<FileInfo>
的
Sort()
方法,从而实现了文件列表的排序。
IComparer<FileInfo> comparer = ( IComparer<FileInfo> ) new FileComparer();
fileList.Sort(comparer);
5.3 异常处理
在文件操作(如复制和删除)过程中,可能会出现各种异常,如文件不存在、磁盘空间不足等。为了确保应用程序的健壮性,我们使用了
try-catch
块来捕获和处理这些异常。
try
{
lblStatus.Text = "Copying " + txtTargetDir.Text +
"\\" + file.Name + "...";
Application.DoEvents();
file.CopyTo( txtTargetDir.Text + "\\" +
file.Name, chkOverwrite.Checked );
}
catch ( Exception ex )
{
MessageBox.Show( ex.Message );
}
在上述代码中,
try
块中执行文件复制操作,如果出现异常,
catch
块将捕获该异常,并通过
MessageBox.Show()
方法显示错误信息。在实际的商业应用中,可能需要根据具体情况采取更复杂的异常处理策略,如记录日志、提示用户采取纠正措施等。
6. 优化建议与扩展思路
6.1 性能优化
- 减少不必要的递归调用 :在递归过程中,可能会存在一些不必要的调用,例如在某些情况下已经可以确定不需要继续深入子目录。可以添加一些条件判断,提前终止递归,减少不必要的计算。
- 缓存数据 :对于一些频繁使用的数据,如目录结构和文件列表,可以考虑进行缓存,避免重复查询和计算,提高应用程序的响应速度。
6.2 功能扩展
- 添加进度条 :在文件复制和删除过程中,可以添加进度条来显示操作的进度,让用户更直观地了解操作的执行情况。
- 支持更多文件操作 :除了复制和删除操作,还可以添加其他文件操作,如移动、重命名等,以增强应用程序的功能。
- 多线程处理 :对于大量文件的复制和删除操作,可以考虑使用多线程来提高处理速度,避免界面卡顿。
7. 总结
通过对这个Windows Forms应用程序的深入分析,我们了解了如何在Windows Forms环境中实现文件操作和TreeView事件处理。主要涉及以下几个方面:
-
TreeView节点的填充
:通过递归方式获取目录结构,并将其显示在TreeView控件中。
-
TreeView事件的处理
:包括点击复选框、展开目录、选择目录等事件的处理,确保用户操作能够正确响应。
-
按钮事件的处理
:实现了清除、复制和删除按钮的功能,方便用户进行文件管理。
-
递归和排序的应用
:递归用于处理复杂的目录结构,排序用于优化文件复制的顺序。
-
异常处理
:通过
try-catch
块捕获和处理文件操作过程中可能出现的异常,提高应用程序的健壮性。
同时,我们也提出了一些优化建议和扩展思路,希望能够帮助开发者进一步完善和扩展这个应用程序。
下面是优化和扩展思路的列表总结:
-
性能优化
:
- 减少不必要的递归调用
- 缓存数据
-
功能扩展
:
- 添加进度条
- 支持更多文件操作
- 多线程处理
以下是应用程序整体流程的另一个流程图:
graph LR;
A[初始化应用程序] --> B[填充TreeView节点];
B --> C{用户操作};
C --> C1[点击TreeView复选框];
C --> C2[展开目录];
C --> C3[选择目标目录];
C --> C4[点击清除按钮];
C --> C5[点击复制按钮];
C --> C6[点击删除按钮];
C1 --> D1[设置复选标记];
C2 --> D2[展开目录并填充子节点];
C3 --> D3[显示目标目录路径];
C4 --> D4[清除复选标记];
C5 --> D5[获取文件列表并排序];
D5 --> D6[复制文件];
C6 --> D7[确认删除并删除文件];
D1 --> E[继续等待用户操作];
D2 --> E;
D3 --> E;
D4 --> E;
D6 --> E;
D7 --> E;
通过以上的分析和总结,开发者可以更好地理解和掌握Windows Forms应用程序中文件操作和TreeView事件处理的实现方法,并根据实际需求进行优化和扩展。
超级会员免费看
469

被折叠的 条评论
为什么被折叠?



