34、深入探索Windows Forms应用程序中的文件操作与TreeView事件处理

深入探索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事件处理的实现方法,并根据实际需求进行优化和扩展。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值