You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Framework中实现TreeView展开节点后加载FTP目录

WinForm TreeView FTP目录懒加载实现方案

核心思路

仅在初始化时加载FTP根目录的第一层内容,给每个目录节点添加临时空节点(让TreeView显示展开按钮);当用户点击节点展开按钮时,再加载该节点对应的子目录,替换临时节点,避免一次性加载所有层级导致的登录耗时过长问题。

具体实现步骤

1. 重构目录加载方法

修改原有递归加载逻辑,让方法只加载当前FTP路径的直接子目录/文件,同时把节点对应的FTP完整路径存入Tag属性,方便后续展开时调用。另外修正原有的文件/目录判断逻辑(原逻辑用是否含.判断不可靠,后续会给出优化方案):

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

namespace GISFileManagerClient
{
    public static class FtpTreeHelper
    {
        // 加载指定FTP路径的直接子节点(不递归)
        public static void LoadDirectChildren(TreeNode parentNode, string ftpPath, string username, string password)
        {
            parentNode.Nodes.Clear(); // 清空节点,避免重复加载
            try
            {
                var request = (FtpWebRequest)WebRequest.Create(ftpPath);
                request.Credentials = new NetworkCredential(username, password);
                request.Method = WebRequestMethods.Ftp.ListDirectory;
                request.KeepAlive = true;

                using (var response = (FtpWebResponse)request.GetResponse())
                using (var responseStream = response.GetResponseStream())
                using (var reader = new StreamReader(responseStream))
                {
                    string directoryContents = reader.ReadToEnd();
                    string[] items = directoryContents.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

                    foreach (string item in items)
                    {
                        string itemName = Path.GetFileName(item);
                        if (string.IsNullOrEmpty(itemName))
                            continue;

                        TreeNode node = new TreeNode(itemName);
                        // 存储当前节点对应的FTP完整路径
                        node.Tag = $"{ftpPath}/{itemName}";

                        // 临时判断是否为目录(后续建议优化此逻辑)
                        bool isDirectory = !itemName.Contains(".");
                        if (isDirectory)
                        {
                            // 添加空节点,让TreeView显示展开按钮
                            node.Nodes.Add(new TreeNode());
                        }

                        parentNode.Nodes.Add(node);
                    }
                }
            }
            catch (WebException ex)
            {
                MessageBox.Show($"加载失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

2. 初始化TreeView根节点

在Form的加载事件中,仅加载FTP根目录的第一层内容:

private void Form1_Load(object sender, EventArgs e)
{
    string ftpRoot = "ftp://你的服务器地址/";
    string username = "你的FTP用户名";
    string password = "你的FTP密码";

    // 清空TreeView并创建根节点
    treeView1.Nodes.Clear();
    TreeNode rootNode = new TreeNode("FTP根目录");
    rootNode.Tag = ftpRoot;
    treeView1.Nodes.Add(rootNode);

    // 加载根节点的直接子目录
    FtpTreeHelper.LoadDirectChildren(rootNode, ftpRoot, username, password);
}

3. 绑定展开事件实现懒加载

给TreeView添加BeforeExpand事件处理,在用户点击展开按钮时加载对应节点的子目录:

private void treeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
{
    // 判断节点是否未加载过子目录(仅含一个临时空节点)
    if (e.Node.Nodes.Count == 1 && string.IsNullOrEmpty(e.Node.Nodes[0].Text))
    {
        string ftpPath = e.Node.Tag.ToString();
        string username = "你的FTP用户名"; // 建议存到全局变量或配置文件,避免硬编码
        string password = "你的FTP密码";

        // 加载当前节点的子目录
        FtpTreeHelper.LoadDirectChildren(e.Node, ftpPath, username, password);
    }
}

4. 优化建议

  • 文件/目录判断优化:原逻辑用是否包含.判断目录不可靠(比如目录名可能带点,文件名可能不带点),建议用以下方法准确判断:
    // 判断指定FTP路径是否为目录
    private static bool IsFtpDirectory(string ftpPath, string username, string password)
    {
        try
        {
            var request = (FtpWebRequest)WebRequest.Create(ftpPath);
            request.Credentials = new NetworkCredential(username, password);
            request.Method = WebRequestMethods.Ftp.ListDirectory;
            using (var response = (FtpWebResponse)request.GetResponse())
            {
                return true;
            }
        }
        catch (WebException ex)
        {
            var response = (FtpWebResponse)ex.Response;
            // 550错误表示路径不存在或不是目录
            if (response.StatusCode == FtpStatusCode.ActionNotTakenFileUnavailable)
            {
                return false;
            }
            throw;
        }
    }
    
  • 异步加载避免UI卡顿:将加载方法改为异步,防止加载目录时UI冻结:
    public static async Task LoadDirectChildrenAsync(TreeNode parentNode, string ftpPath, string username, string password)
    {
        parentNode.Nodes.Clear();
        try
        {
            var request = (FtpWebRequest)WebRequest.Create(ftpPath);
            request.Credentials = new NetworkCredential(username, password);
            request.Method = WebRequestMethods.Ftp.ListDirectory;
            request.KeepAlive = true;
    
            using (var response = (FtpWebResponse)await request.GetResponseAsync())
            using (var responseStream = response.GetResponseStream())
            using (var reader = new StreamReader(responseStream))
            {
                string directoryContents = await reader.ReadToEndAsync();
                string[] items = directoryContents.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
    
                foreach (string item in items)
                {
                    string itemName = Path.GetFileName(item);
                    if (string.IsNullOrEmpty(itemName))
                        continue;
    
                    TreeNode node = new TreeNode(itemName);
                    node.Tag = $"{ftpPath}/{itemName}";
    
                    bool isDirectory = await Task.Run(() => IsFtpDirectory($"{ftpPath}/{itemName}", username, password));
                    if (isDirectory)
                    {
                        node.Nodes.Add(new TreeNode());
                    }
    
                    // 跨线程更新UI需调用Invoke
                    parentNode.TreeView.Invoke((Action)(() => parentNode.Nodes.Add(node)));
                }
            }
        }
        catch (WebException ex)
        {
            parentNode.TreeView.Invoke((Action)(() => MessageBox.Show($"加载失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error)));
        }
    }
    
    对应展开事件改为异步:
    private async void treeView1_BeforeExpand(object sender, TreeViewCancelEventArgs e)
    {
        if (e.Node.Nodes.Count == 1 && string.IsNullOrEmpty(e.Node.Nodes[0].Text))
        {
            string ftpPath = e.Node.Tag.ToString();
            string username = "你的FTP用户名";
            string password = "你的FTP密码";
    
            await FtpTreeHelper.LoadDirectChildrenAsync(e.Node, ftpPath, username, password);
        }
    }
    

内容的提问来源于stack exchange,提问作者P0tat0627

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 17:14:53