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

如何在WPF(C#)中加载并展示本地SharePoint文档库及子站点

实现WPF TreeView展示SharePoint层级内容的方案

我之前做过类似的WPF与SharePoint集成的需求,给你梳理下具体的实现思路和关键代码片段:

1. 准备依赖包

首先得在你的WPF项目里安装SharePoint客户端对象模型(CSOM)的NuGet包,直接搜Microsoft.SharePointOnline.CSOM就行,这个包能让桌面应用直接调用SharePoint的API。

2. 定义层级节点模型

咱需要一个能表示SharePoint各种节点(站点、文档库、文件夹、文件)的类,还要实现INotifyPropertyChanged方便WPF的数据绑定:

public class SharePointNode : INotifyPropertyChanged
{
    private string _name;
    private string _url;
    private NodeType _nodeType;
    private ObservableCollection<SharePointNode> _children;
    private bool _isLoading;

    public enum NodeType
    {
        Site,
        DocumentLibrary,
        Folder,
        File
    }

    public string Name
    {
        get => _name;
        set { _name = value; OnPropertyChanged(); }
    }

    public string Url
    {
        get => _url;
        set { _url = value; OnPropertyChanged(); }
    }

    public NodeType Type
    {
        get => _nodeType;
        set { _nodeType = value; OnPropertyChanged(); }
    }

    public ObservableCollection<SharePointNode> Children
    {
        get => _children ?? (_children = new ObservableCollection<SharePointNode>());
        set { _children = value; OnPropertyChanged(); }
    }

    public bool IsLoading
    {
        get => _isLoading;
        set { _isLoading = value; OnPropertyChanged(); }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

3. XAML中配置TreeView

接下来在WPF的XAML里设置TreeView的模板,让它根据节点类型展示不同的样式,同时支持层级展开:

<TreeView x:Name="spTreeView" 
          SelectedItemChanged="SpTreeView_SelectedItemChanged"
          ItemExpanded="SpTreeView_ItemExpanded">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Margin="0 0 5 0">
                    <TextBlock.Style>
                        <Style TargetType="TextBlock">
                            <Style.Triggers>
                                <DataTrigger Binding="{Binding Type}" Value="{x:Static local:SharePointNode.NodeType.Site}">
                                    <Setter Property="Text" Value="🏢"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding Type}" Value="{x:Static local:SharePointNode.NodeType.DocumentLibrary}">
                                    <Setter Property="Text" Value="📚"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding Type}" Value="{x:Static local:SharePointNode.NodeType.Folder}">
                                    <Setter Property="Text" Value="📁"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding Type}" Value="{x:Static local:SharePointNode.NodeType.File}">
                                    <Setter Property="Text" Value="📄"/>
                                </DataTrigger>
                                <DataTrigger Binding="{Binding IsLoading}" Value="True">
                                    <Setter Property="Text" Value="🔄"/>
                                </DataTrigger>
                            </Style.Triggers>
                        </Style>
                    </TextBlock.Style>
                </TextBlock>
                <TextBlock Text="{Binding Name}"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>
<!-- 用来展示选中文件夹的文件列表 -->
<ListView x:Name="fileListView" Margin="10 0 0 0">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="文件名" DisplayMemberBinding="{Binding Name}"/>
            <GridViewColumn Header="URL" DisplayMemberBinding="{Binding Url}"/>
        </GridView>
    </ListView.View>
</ListView>

4. 加载根节点数据

在窗口的Loaded事件里,先加载根站点的子站点和文档库:

private async void Window_Loaded(object sender, RoutedEventArgs e)
{
    var rootUrl = "https://mysite.mycompany.com";
    var rootNode = new SharePointNode { Name = "根站点", Url = rootUrl, Type = SharePointNode.NodeType.Site };
    spTreeView.Items.Add(rootNode);
    // 异步加载子节点,避免UI卡顿
    await LoadSiteChildrenAsync(rootNode);
}

private async Task LoadSiteChildrenAsync(SharePointNode siteNode)
{
    siteNode.IsLoading = true;
    try
    {
        using (var ctx = new ClientContext(siteNode.Url))
        {
            // 使用当前Windows凭据登录,适合域环境
            ctx.Credentials = CredentialCache.DefaultCredentials;
            
            // 加载子站点
            ctx.Load(ctx.Web.Webs, webs => webs.Include(w => w.Title, w => w.Url));
            // 加载文档库(过滤掉系统列表)
            ctx.Load(ctx.Web.Lists, lists => lists.Include(l => l.Title, l => l.RootFolder)
                .Where(l => l.BaseType == BaseType.DocumentLibrary && !l.Hidden));
            
            await ctx.ExecuteQueryAsync();

            // 添加子站点节点
            foreach (var web in ctx.Web.Webs)
            {
                siteNode.Children.Add(new SharePointNode
                {
                    Name = web.Title,
                    Url = web.Url,
                    Type = SharePointNode.NodeType.Site
                });
            }

            // 添加文档库节点
            foreach (var list in ctx.Web.Lists)
            {
                siteNode.Children.Add(new SharePointNode
                {
                    Name = list.Title,
                    Url = list.RootFolder.ServerRelativeUrl,
                    Type = SharePointNode.NodeType.DocumentLibrary
                });
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"加载失败:{ex.Message}");
    }
    finally
    {
        siteNode.IsLoading = false;
    }
}

5. 懒加载子节点(展开时加载)

为了避免一次性加载所有数据导致UI卡顿,我们在节点展开时再加载它的子内容:

private async void SpTreeView_ItemExpanded(object sender, RoutedEventArgs e)
{
    if (e.OriginalSource is TreeViewItem item && item.DataContext is SharePointNode node)
    {
        if (node.Children.Count == 0 && !node.IsLoading)
        {
            switch (node.Type)
            {
                case SharePointNode.NodeType.Site:
                    await LoadSiteChildrenAsync(node);
                    break;
                case SharePointNode.NodeType.DocumentLibrary:
                    await LoadFolderChildrenAsync(node, node.Url);
                    break;
                case SharePointNode.NodeType.Folder:
                    await LoadFolderChildrenAsync(node, node.Url);
                    break;
            }
        }
    }
}

private async Task LoadFolderChildrenAsync(SharePointNode parentNode, string folderUrl)
{
    parentNode.IsLoading = true;
    try
    {
        using (var ctx = new ClientContext("https://mysite.mycompany.com"))
        {
            ctx.Credentials = CredentialCache.DefaultCredentials;
            var folder = ctx.Web.GetFolderByServerRelativeUrl(folderUrl);
            
            // 加载子文件夹
            ctx.Load(folder.Folders, f => f.Include(fld => fld.Name, fld => fld.ServerRelativeUrl));
            // 加载文件
            ctx.Load(folder.Files, fl => fl.Include(f => f.Name, f => f.ServerRelativeUrl));
            
            await ctx.ExecuteQueryAsync();

            // 添加子文件夹
            foreach (var subFolder in folder.Folders)
            {
                parentNode.Children.Add(new SharePointNode
                {
                    Name = subFolder.Name,
                    Url = subFolder.ServerRelativeUrl,
                    Type = SharePointNode.NodeType.Folder
                });
            }

            // 添加文件
            foreach (var file in folder.Files)
            {
                parentNode.Children.Add(new SharePointNode
                {
                    Name = file.Name,
                    Url = file.ServerRelativeUrl,
                    Type = SharePointNode.NodeType.File
                });
            }
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show($"加载文件夹内容失败:{ex.Message}");
    }
    finally
    {
        parentNode.IsLoading = false;
    }
}

6. 处理文件夹点击事件

当用户点击文件夹节点时,展示该文件夹下的所有文件:

private void SpTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
    if (e.NewValue is SharePointNode selectedNode && selectedNode.Type == SharePointNode.NodeType.Folder)
    {
        // 直接从选中节点的Children里过滤出文件
        var files = selectedNode.Children.Where(n => n.Type == SharePointNode.NodeType.File).ToList();
        fileListView.ItemsSource = files;
    }
    else
    {
        fileListView.ItemsSource = null;
    }
}

一些注意事项

  • 权限:如果是SharePoint Online,可能需要用SharePointOnlineCredentials代替CredentialCache.DefaultCredentials,需要传入用户名和密码(或者使用MSAL进行身份验证)。
  • 性能:可以给节点添加缓存逻辑,避免重复加载同一节点的内容。
  • 异常处理:实际项目中要完善异常捕获,比如网络错误、权限不足等情况的提示。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:29:07