• 循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(5) -- 树列表TreeView的使用


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

    1、TreeView树形列表的展示

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

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

    复制代码
    <TreeView
        x:Name="deptTree"
        Margin="0,10,10,0"
        FontSize="16"
        ItemsSource="{Binding ViewModel.FilteredTreeItems}"
        SelectedItemChanged="deptTree_SelectedItemChanged">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsExpanded" Value="True" />
            Style>
        TreeView.ItemContainerStyle>
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate DataType="{x:Type core:OuNodeInfo}" ItemsSource="{Binding Path=Children}">
                <StackPanel Orientation="Horizontal">
                    <Button
                        hc:IconElement.Geometry="{StaticResource PageModeGeometry}"
                        Background="Transparent"
                        BorderBrush="Transparent"
                        BorderThickness="0"
                        Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" />            
                    <Label
                        Padding="-10"
                        Background="Transparent"
                        BorderBrush="Transparent"
                        BorderThickness="0"
                        Content="{Binding Path=Name}" />
                StackPanel>
            HierarchicalDataTemplate>
        TreeView.ItemTemplate>
    TreeView>
    复制代码

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

    public class TreeView : ItemsControl

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

    复制代码
        /// 
        /// 树列表选中触发事件
        /// 
        private async void deptTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            var item = e.NewValue as OuNodeInfo;
            if (item != null)
            {
                this.ViewModel.SelectNode = item;
                await this.ViewModel.GetData();
            }
        }
    复制代码

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

    复制代码
    /// 
    /// 用户列表-视图模型对象
    /// 
    public partial class UserListViewModel : BaseListViewModelint, UserPagedDto>
    {
        /// 
        /// 查询过滤内容
        /// 
        [ObservableProperty]
        private string _searchKey = "";
    
        /// 
        /// 树形数据列表
        /// 
        [ObservableProperty]
        private List? treeItems;
    
        /// 
        /// 树形数据列表(过滤列表)
        /// 
        [ObservableProperty]
        private List? filteredTreeItems;
    
        /// 
        /// 选中的当前节点
        /// 
        [ObservableProperty]
        private OuNodeInfo selectNode;
    复制代码

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

    复制代码
        /// 
        /// 设置父类后查询数据
        /// 
        /// 
        public override Task GetData()
        {
            //选中的大类Id
            if (this.SelectNode != null)
            {
                this.PageDto.Dept_ID = this.SelectNode.Id.ToString();
            }
    
            return base.GetData();
        }
    复制代码

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

    复制代码
    /// 
    /// UserListPage.xaml 交互逻辑
    /// 
    public partial class UserListPage : INavigableView
    {
        /// 
        /// 视图模型对象
        /// 
        public UserListViewModel ViewModel { get; }
        /// 
        /// 构造函数
        /// 
        /// 视图模型对象
        public UserListPage(UserListViewModel viewModel)
        {
            ViewModel = viewModel;
            DataContext = this;
    
            InitializeComponent();
    
            //展示树列表
            ViewModel.GetTreeCommand.ExecuteAsync(null);
        }
    复制代码

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

    复制代码
        /// 
        /// 触发处理命令
        /// 
        [RelayCommand]
        private async Task GetTree()
        {
            var treeeNodeList = new List();
            var list = await SecurityHelper.GetMyTopGroup(App.ViewModel!.UserInfo);
            foreach (var groupInfo in list)
            {
                if (groupInfo != null && !groupInfo.IsDeleted)
                {
                    var node = new OuNodeInfo(groupInfo);
                    var sublist = await BLLFactory.Instance.GetTreeByID(groupInfo.Id);
                    node.Children = sublist;
                    treeeNodeList.Add(node);
                }
            }
            this.TreeItems = treeeNodeList;
            this.FilteredTreeItems = new List(this.TreeItems);
        }
    复制代码

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

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

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

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

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

    复制代码
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate DataType="{x:Type core:OuNodeInfo}" ItemsSource="{Binding Path=Children}">
                <StackPanel Orientation="Horizontal">
                    <Button
                        hc:IconElement.Geometry="{StaticResource PageModeGeometry}"
                        Background="Transparent"
                        BorderBrush="Transparent"
                        BorderThickness="0"
                        Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" />            
                    <Label
                        Padding="-10"
                        Background="Transparent"
                        BorderBrush="Transparent"
                        BorderThickness="0"
                        Content="{Binding Path=Name}" />
                StackPanel>
            HierarchicalDataTemplate>
        TreeView.ItemTemplate>
    复制代码

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

     

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

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

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

    复制代码
    <TreeView
        x:Name="dictTypeTree"
        Margin="0,10,10,0"
        ContextMenu="{StaticResource TreeMenu}"
        FontSize="16"
        ItemsSource="{Binding ViewModel.FilteredTreeItems}"
    复制代码

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

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

    复制代码
        <Page.Resources>
            <ContextMenu x:Key="TreeMenu">
                <MenuItem Command="{Binding EditDictTypeCommand}" Header="添加字典大类">
                    <MenuItem.Icon>
                        <Image Source="/Images/Add.png" />
                    MenuItem.Icon>
                MenuItem>
                <MenuItem
                    Command="{Binding EditDictTypeCommand}"
                    CommandParameter="{Binding ViewModel.SelectDictType}"
                    Header="编辑字典大类">
                    <MenuItem.Icon>
                        <Image Source="/Images/edit.png" />
                    MenuItem.Icon>
                MenuItem>
                <MenuItem Command="{Binding ViewModel.DeleteTypeCommand}" Header="删除字典大类">
                    <MenuItem.Icon>
                        <Image Source="/Images/remove.png" />
                    MenuItem.Icon>
                MenuItem>
                <MenuItem Command="{Binding ViewModel.GetTreeCommand}" Header="刷新列表">
                    <MenuItem.Icon>
                        <Image Source="/Images/refresh.png" />
                    MenuItem.Icon>
                MenuItem>
                <MenuItem Command="{Binding ViewModel.ClearDataCommand}" Header="清空字典大类数据">
                    <MenuItem.Icon>
                        <Image Source="/Images/clear.png" />
                    MenuItem.Icon>
                MenuItem>
            ContextMenu>
        Page.Resources>
    复制代码

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

    复制代码
        /// 
        /// 新增、编辑字典大类
        /// 
        [RelayCommand]
        private async Task EditDictType(DictTypeNodeDto info)
        {
            //获取新增、编辑页面接口
            var page = App.GetService();
            page!.ViewModel.DictType = this.ViewModel.SelectDictType;
    
            if (info != null)
            {
                //编辑则接受传入对象
                page!.ViewModel.Item = info;
                page!.ViewModel.IsEdit = true;
            }
            else
            {
                //新增则初始化
                var pid = "-1";
                if(this.ViewModel.SelectDictType != null)
                {
                    pid = this.ViewModel.SelectDictType.Id;
                }
    
                //前后顺序不能变化,否则无法检测属性变化
                page!.ViewModel.Item = new DictTypeInfo() { Id=Guid.NewGuid().ToString(), PID = pid, Seq = "001" };
                page!.ViewModel.IsEdit = false;
            }
    
            //导航到指定页面
            ViewModel.Navigate(typeof(DictTypeEditPage));
        }
    复制代码

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

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

    复制代码
    <ComboBox
        x:Name="txtPID"
        Grid.Column="0"
        Margin="5"
        HorizontalAlignment="Left"
        hc:InfoElement.TitlePlacement="Left"
        hc:TitleElement.Title="父项名称"
        DisplayMemberPath="Text"
        IsReadOnly="{Binding ViewModel.IsReadOnly, Mode=OneWay}"
        ItemsSource="{Binding ViewModel.ParentTypeList}"
        SelectedValue="{Binding ViewModel.Item.PID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
        SelectedValuePath="Value"
        SelectionChanged="txtPID_SelectionChanged"
        Style="{StaticResource ComboBoxExtend}" />
    复制代码

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

    复制代码
    /// 
    /// 初始化处理字典大类
    /// 
    private async void Init()
    {       
        //获取字典大类列表
        var dictItems = await BLLFactory.Instance.GetAllType(null);    
        var listItem = new List();
        foreach (var item in dictItems)
        {
            listItem.Add(new CListItem(item.Key, item.Value));
        }
        listItem.Insert(0, new CListItem("顶级目录项", "-1"));
    
        this.ParentTypeList = listItem;
        this._isInitialized = true;
    }
    复制代码

     

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

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

    复制代码
        /// 
        /// 触发处理命令
        /// 
        [RelayCommand]
        private async Task GetTree()
        {
            var treeeNodeList = new List();
            var list = await SecurityHelper.GetMyTopGroup(App.ViewModel!.UserInfo);
            foreach (var groupInfo in list)
            {
                if (groupInfo != null && !groupInfo.IsDeleted)
                {
                    var node = new OuNodeInfo(groupInfo);
                    var sublist = await BLLFactory.Instance.GetTreeByID(groupInfo.Id);
                    node.Children = sublist;
                    treeeNodeList.Add(node);
                }
            }
    
            this.TreeItems = treeeNodeList;
            this.FilteredTreeItems = new List(this.TreeItems);
        }
    复制代码

    界面的Xaml代码如下所示。

    复制代码
    <hc:SearchBar
        Margin="0,10,10,0"
        hc:InfoElement.Placeholder="输入内容过滤"
        hc:InfoElement.ShowClearButton="True"
        IsRealTime="True"
        SearchStarted="SearchBar_OnSearchStarted"
        Style="{StaticResource SearchBarPlus}" />
    复制代码

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

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

    复制代码
        /// 
        /// 过滤查询事件
        /// 
        private void SearchBar_OnSearchStarted(object sender, HandyControl.Data.FunctionEventArgs<string> e)
        {
            this.ViewModel.SearchKey = e.Info;
            if (e.Info.IsNullOrEmpty())
            {
                this.ViewModel.FilteredTreeItems = this.ViewModel.TreeItems;
            }
            else
            {
                this.ViewModel.FilteredTreeItems = FindNodes(this.ViewModel.TreeItems!, e.Info);
            }
        }
        /// 
        /// 递归查询嵌套节点的内容,根据规则进行匹配
        /// 
        /// 数据类型
        /// 节点集合
        /// 节点名称
        /// 
        public List FindNodes(IEnumerable nodes, string name) where T : OuNodeInfo
        {
            var result = new List();
            foreach (var node in nodes)
            {
                if (node.Name.Contains(name, StringComparison.OrdinalIgnoreCase))
                {
                    result.Add(node);
                }
                foreach (var childCategory in FindNodes(node.Children, name))
                {
                    result.Add((T)childCategory);
                }
            }
            return result;
        }
    复制代码

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

     

  • 相关阅读:
    如何用Stable Diffusion模型生成个人专属创意名片?
    i.MX6ULL - 问题解决:NFS挂载失败 - VFS: Unable to mount root fs on unknown-block(2,0)
    机器学习代码问题总结
    母婴行业数字化发展趋势:内容多元化、服务定制化、人群全覆盖
    JavaWeb进阶案例-实现AJAX+Sevlet前后端分离开发
    Ansible-常用模块
    响应式基础
    Java基础:入门程序、常量、变量
    NL6621 实现获取天气情况
    【论文笔记】Encoding cloth manipulations using a graph of states and transitions
  • 原文地址:https://www.cnblogs.com/wuhuacong/p/17711601.html