经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 软件/图像 » unity » 查看文章
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(5) -- 树列表TreeView的使用
来源:cnblogs  作者:伍华聪  时间:2023/9/19 8:38:21  对本文有异议

在我们展示一些参考信息的时候,有所会用树形列表来展示结构信息,如对于有父子关系的多层级部门机构,以及一些常用如字典大类节点,也都可以利用树形列表的方式进行展示,本篇随笔介绍基于WPF的方式,使用TreeView来洗实现结构信息的展示,以及对它的菜单进行的设置、过滤查询等功能的实现逻辑。

1、TreeView树形列表的展示

我们前面随笔介绍到的用户信息的展示,左侧就是一个树形的类表,通过展示多层级的部门机构信息,可以快速的查找对应部门的用户信息,如下界面所示。

我们来看看界面中树形列表部分的Xaml代码如下所示。

  1. <TreeView
  2. x:Name="deptTree"
  3. Margin="0,10,10,0"
  4. FontSize="16"
  5. ItemsSource="{Binding ViewModel.FilteredTreeItems}"
  6. SelectedItemChanged="deptTree_SelectedItemChanged">
  7. <TreeView.ItemContainerStyle>
  8. <Style TargetType="{x:Type TreeViewItem}">
  9. <Setter Property="IsExpanded" Value="True" />
  10. </Style>
  11. </TreeView.ItemContainerStyle>
  12. <TreeView.ItemTemplate>
  13. <HierarchicalDataTemplate DataType="{x:Type core:OuNodeInfo}" ItemsSource="{Binding Path=Children}">
  14. <StackPanel Orientation="Horizontal">
  15. <Button
  16. hc:IconElement.Geometry="{StaticResource PageModeGeometry}"
  17. Background="Transparent"
  18. BorderBrush="Transparent"
  19. BorderThickness="0"
  20. Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" />
  21. <Label
  22. Padding="-10"
  23. Background="Transparent"
  24. BorderBrush="Transparent"
  25. BorderThickness="0"
  26. Content="{Binding Path=Name}" />
  27. </StackPanel>
  28. </HierarchicalDataTemplate>
  29. </TreeView.ItemTemplate>
  30. </TreeView>

其中的ItemsSource是指定TreeView的数据源的,它是一个ItemsControl,因此它有数据源ItemsSource树形,如其他ListBox也是这样的控件基类。

  1. public class TreeView : ItemsControl

而 SelectedItemChanged 是我们在选择不同节点的时候触发的事件,用于我们对数据进行重新查询的处理,实现的代码如下所示。

  1. /// <summary>
  2. /// 树列表选中触发事件
  3. /// </summary>
  4. private async void deptTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
  5. {
  6. var item = e.NewValue as OuNodeInfo;
  7. if (item != null)
  8. {
  9. this.ViewModel.SelectNode = item;
  10. await this.ViewModel.GetData();
  11. }
  12. }

其中用户列表界面的ViewModel类里面 ,我们定义了一些属性,如下代码所示。

  1. /// <summary>
  2. /// 用户列表-视图模型对象
  3. /// </summary>
  4. public partial class UserListViewModel : BaseListViewModel<UserInfo, int, UserPagedDto>
  5. {
  6. /// <summary>
  7. /// 查询过滤内容
  8. /// </summary>
  9. [ObservableProperty]
  10. private string _searchKey = "";
  11. /// <summary>
  12. /// 树形数据列表
  13. /// </summary>
  14. [ObservableProperty]
  15. private List<OuNodeInfo>? treeItems;
  16. /// <summary>
  17. /// 树形数据列表(过滤列表)
  18. /// </summary>
  19. [ObservableProperty]
  20. private List<OuNodeInfo>? filteredTreeItems;
  21. /// <summary>
  22. /// 选中的当前节点
  23. /// </summary>
  24. [ObservableProperty]
  25. private OuNodeInfo selectNode;

刚才我们通过设置选中的节点,然后触发查询GetData就是在UserListViewModel 视图模型类里面,重新构建条件进行处理的,最后还是调用基类BaseListViewModel里面的封装类的实现方法GetData。

  1. /// <summary>
  2. /// 设置父类后查询数据
  3. /// </summary>
  4. /// <returns></returns>
  5. public override Task GetData()
  6. {
  7. //选中的大类Id
  8. if (this.SelectNode != null)
  9. {
  10. this.PageDto.Dept_ID = this.SelectNode.Id.ToString();
  11. }
  12. return base.GetData();
  13. }

而我们的属性列表的数据源,主要就是通过页面Page的构造函数的时候,触发的数据处理,就是GetTreeCommand的调用。

  1. /// <summary>
  2. /// UserListPage.xaml 交互逻辑
  3. /// </summary>
  4. public partial class UserListPage : INavigableView<UserListViewModel>
  5. {
  6. /// <summary>
  7. /// 视图模型对象
  8. /// </summary>
  9. public UserListViewModel ViewModel { get; }
  10. /// <summary>
  11. /// 构造函数
  12. /// </summary>
  13. /// <param name="viewModel">视图模型对象</param>
  14. public UserListPage(UserListViewModel viewModel)
  15. {
  16. ViewModel = viewModel;
  17. DataContext = this;
  18. InitializeComponent();
  19. //展示树列表
  20. ViewModel.GetTreeCommand.ExecuteAsync(null);
  21. }

其中GetTree的命令方法如下所示。

  1. /// <summary>
  2. /// 触发处理命令
  3. /// </summary>
  4. [RelayCommand]
  5. private async Task GetTree()
  6. {
  7. var treeeNodeList = new List<OuNodeInfo>();
  8. var list = await SecurityHelper.GetMyTopGroup(App.ViewModel!.UserInfo);
  9. foreach (var groupInfo in list)
  10. {
  11. if (groupInfo != null && !groupInfo.IsDeleted)
  12. {
  13. var node = new OuNodeInfo(groupInfo);
  14. var sublist = await BLLFactory<IOuService>.Instance.GetTreeByID(groupInfo.Id);
  15. node.Children = sublist;
  16. treeeNodeList.Add(node);
  17. }
  18. }
  19. this.TreeItems = treeeNodeList;
  20. this.FilteredTreeItems = new List<OuNodeInfo>(this.TreeItems);
  21. }

我们通过构建一个用户的顶级部门列表(如管理员可以看全部,公司管理员只能看公司节点),然后给它们填充一个子级的部门列表即可。

另外,在Xaml的界面代码里面,我们可以看到下面的代码

  1. <HierarchicalDataTemplate DataType="{x:Type core:OuNodeInfo}" ItemsSource="{Binding Path=Children}">

这个就是层级树形的内容设置,其中DataType指定对象的类型为OuNodeInfo类,而子节点的的数据源属性名称就是Children属性。

它的内容部分就是我们子节点的呈现界面代码模板了

  1. <TreeView.ItemTemplate>
  2. <HierarchicalDataTemplate DataType="{x:Type core:OuNodeInfo}" ItemsSource="{Binding Path=Children}">
  3. <StackPanel Orientation="Horizontal">
  4. <Button
  5. hc:IconElement.Geometry="{StaticResource PageModeGeometry}"
  6. Background="Transparent"
  7. BorderBrush="Transparent"
  8. BorderThickness="0"
  9. Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" />
  10. <Label
  11. Padding="-10"
  12. Background="Transparent"
  13. BorderBrush="Transparent"
  14. BorderThickness="0"
  15. Content="{Binding Path=Name}" />
  16. </StackPanel>
  17. </HierarchicalDataTemplate>
  18. </TreeView.ItemTemplate>

同理,对于字典模块的字典大类的展示,由于它们也是多层级的,因此也可以用TreeView进行展示,界面效果如下所示。

 

2、TreeView树形列表的右键菜单的处理

在上面的字典模块里面,我们左侧的字典大类,还需要一些右键菜单来实现字典大类的新增、编辑、清空数据项等维护功能的,因此给TreeView设置了右键菜单,如下所示效果。

 它的TreeView的Xaml代码如下所示。

  1. <TreeView
  2. x:Name="dictTypeTree"
  3. Margin="0,10,10,0"
  4. ContextMenu="{StaticResource TreeMenu}"
  5. FontSize="16"
  6. ItemsSource="{Binding ViewModel.FilteredTreeItems}"

其中 ContextMenu="{StaticResource TreeMenu}" 就是设置树列表右键菜单的。

这个部分定义在页面的资源里面,如下代码所示,指定相关菜单的图片、文本,以及对应的Command方法即可。

  1. <Page.Resources>
  2. <ContextMenu x:Key="TreeMenu">
  3. <MenuItem Command="{Binding EditDictTypeCommand}" Header="添加字典大类">
  4. <MenuItem.Icon>
  5. <Image Source="/Images/Add.png" />
  6. </MenuItem.Icon>
  7. </MenuItem>
  8. <MenuItem
  9. Command="{Binding EditDictTypeCommand}"
  10. CommandParameter="{Binding ViewModel.SelectDictType}"
  11. Header="编辑字典大类">
  12. <MenuItem.Icon>
  13. <Image Source="/Images/edit.png" />
  14. </MenuItem.Icon>
  15. </MenuItem>
  16. <MenuItem Command="{Binding ViewModel.DeleteTypeCommand}" Header="删除字典大类">
  17. <MenuItem.Icon>
  18. <Image Source="/Images/remove.png" />
  19. </MenuItem.Icon>
  20. </MenuItem>
  21. <MenuItem Command="{Binding ViewModel.GetTreeCommand}" Header="刷新列表">
  22. <MenuItem.Icon>
  23. <Image Source="/Images/refresh.png" />
  24. </MenuItem.Icon>
  25. </MenuItem>
  26. <MenuItem Command="{Binding ViewModel.ClearDataCommand}" Header="清空字典大类数据">
  27. <MenuItem.Icon>
  28. <Image Source="/Images/clear.png" />
  29. </MenuItem.Icon>
  30. </MenuItem>
  31. </ContextMenu>
  32. </Page.Resources>

我们来看看后台代码的Comand命令定义,如下是编辑、新增字典大类的处理

  1. /// <summary>
  2. /// 新增、编辑字典大类
  3. /// </summary>
  4. [RelayCommand]
  5. private async Task EditDictType(DictTypeNodeDto info)
  6. {
  7. //获取新增、编辑页面接口
  8. var page = App.GetService<DictTypeEditPage>();
  9. page!.ViewModel.DictType = this.ViewModel.SelectDictType;
  10. if (info != null)
  11. {
  12. //编辑则接受传入对象
  13. page!.ViewModel.Item = info;
  14. page!.ViewModel.IsEdit = true;
  15. }
  16. else
  17. {
  18. //新增则初始化
  19. var pid = "-1";
  20. if(this.ViewModel.SelectDictType != null)
  21. {
  22. pid = this.ViewModel.SelectDictType.Id;
  23. }
  24. //前后顺序不能变化,否则无法检测属性变化
  25. page!.ViewModel.Item = new DictTypeInfo() { Id=Guid.NewGuid().ToString(), PID = pid, Seq = "001" };
  26. page!.ViewModel.IsEdit = false;
  27. }
  28. //导航到指定页面
  29. ViewModel.Navigate(typeof(DictTypeEditPage));
  30. }

同样就是获得 选中节点,如果新增作为父节点、如果是编辑,则获得当前节点的信息进行编辑即可。

这个界面里面,需要指定下拉列表的数据源作为父节点,主要就是获取当前所有字典大类即可。这里的下拉列表是ComboBox的控件,控件代码如下所示。

  1. <ComboBox
  2. x:Name="txtPID"
  3. Grid.Column="0"
  4. Margin="5"
  5. HorizontalAlignment="Left"
  6. hc:InfoElement.TitlePlacement="Left"
  7. hc:TitleElement.Title="父项名称"
  8. DisplayMemberPath="Text"
  9. IsReadOnly="{Binding ViewModel.IsReadOnly, Mode=OneWay}"
  10. ItemsSource="{Binding ViewModel.ParentTypeList}"
  11. SelectedValue="{Binding ViewModel.Item.PID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
  12. SelectedValuePath="Value"
  13. SelectionChanged="txtPID_SelectionChanged"
  14. Style="{StaticResource ComboBoxExtend}" />

在视图模型里面,初始化的时候,把它进行加载即可。

  1. /// <summary>
  2. /// 初始化处理字典大类
  3. /// </summary>
  4. private async void Init()
  5. {
  6. //获取字典大类列表
  7. var dictItems = await BLLFactory<IDictTypeService>.Instance.GetAllType(null);
  8. var listItem = new List<CListItem>();
  9. foreach (var item in dictItems)
  10. {
  11. listItem.Add(new CListItem(item.Key, item.Value));
  12. }
  13. listItem.Insert(0, new CListItem("顶级目录项", "-1"));
  14. this.ParentTypeList = listItem;
  15. this._isInitialized = true;
  16. }

 

3、TreeView树形列表的过滤处理

我们在构建树形列表的时候,绑定的是一个过滤的列表对象,需要根据实际情况进行改变即可。

  1. /// <summary>
  2. /// 触发处理命令
  3. /// </summary>
  4. [RelayCommand]
  5. private async Task GetTree()
  6. {
  7. var treeeNodeList = new List<OuNodeInfo>();
  8. var list = await SecurityHelper.GetMyTopGroup(App.ViewModel!.UserInfo);
  9. foreach (var groupInfo in list)
  10. {
  11. if (groupInfo != null && !groupInfo.IsDeleted)
  12. {
  13. var node = new OuNodeInfo(groupInfo);
  14. var sublist = await BLLFactory<IOuService>.Instance.GetTreeByID(groupInfo.Id);
  15. node.Children = sublist;
  16. treeeNodeList.Add(node);
  17. }
  18. }
  19. this.TreeItems = treeeNodeList;
  20. this.FilteredTreeItems = new List<OuNodeInfo>(this.TreeItems);
  21. }

界面的Xaml代码如下所示。

  1. <hc:SearchBar
  2. Margin="0,10,10,0"
  3. hc:InfoElement.Placeholder="输入内容过滤"
  4. hc:InfoElement.ShowClearButton="True"
  5. IsRealTime="True"
  6. SearchStarted="SearchBar_OnSearchStarted"
  7. Style="{StaticResource SearchBarPlus}" />

如果在查询框里面输入内容,可以进行树列表的过滤,如下效果所示。

在输入内容的时候,我们触发一个事件SearchBar_OnSearchStarted 进行查询处理,根据查询的内容进行匹配处理。

  1. /// <summary>
  2. /// 过滤查询事件
  3. /// </summary>
  4. private void SearchBar_OnSearchStarted(object sender, HandyControl.Data.FunctionEventArgs<string> e)
  5. {
  6. this.ViewModel.SearchKey = e.Info;
  7. if (e.Info.IsNullOrEmpty())
  8. {
  9. this.ViewModel.FilteredTreeItems = this.ViewModel.TreeItems;
  10. }
  11. else
  12. {
  13. this.ViewModel.FilteredTreeItems = FindNodes(this.ViewModel.TreeItems!, e.Info);
  14. }
  15. }
  16. /// <summary>
  17. /// 递归查询嵌套节点的内容,根据规则进行匹配
  18. /// </summary>
  19. /// <typeparam name="T">数据类型</typeparam>
  20. /// <param name="nodes">节点集合</param>
  21. /// <param name="name">节点名称</param>
  22. /// <returns></returns>
  23. public List<T> FindNodes<T>(IEnumerable<T> nodes, string name) where T : OuNodeInfo
  24. {
  25. var result = new List<T>();
  26. foreach (var node in nodes)
  27. {
  28. if (node.Name.Contains(name, StringComparison.OrdinalIgnoreCase))
  29. {
  30. result.Add(node);
  31. }
  32. foreach (var childCategory in FindNodes(node.Children, name))
  33. {
  34. result.Add((T)childCategory);
  35. }
  36. }
  37. return result;
  38. }

这样就可以实现数据的查询过滤,实际规则可以自己根据需要进行调整。

 

原文链接:https://www.cnblogs.com/wuhuacong/p/17711601.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号