如何在WPF(C#)中加载并展示本地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
相关产品推荐
相关产品推荐

