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

简介: 循序渐进介绍基于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 是我们在选择不同节点的时候触发的事件,用于我们对数据进行重新查询的处理,实现的代码如下所示。

/// <summary>
    /// 树列表选中触发事件
    /// </summary>
    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类里面 ,我们定义了一些属性,如下代码所示。

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

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

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

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

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

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

/// <summary>
    /// 触发处理命令
    /// </summary>
    [RelayCommand]
    private async Task GetTree()
    {
        var treeeNodeList = new List<OuNodeInfo>();
        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<IOuService>.Instance.GetTreeByID(groupInfo.Id);
                node.Children = sublist;
                treeeNodeList.Add(node);
            }
        }
        this.TreeItems = treeeNodeList;
        this.FilteredTreeItems = new List<OuNodeInfo>(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命令定义,如下是编辑、新增字典大类的处理

/// <summary>
    /// 新增、编辑字典大类
    /// </summary>
    [RelayCommand]
    private async Task EditDictType(DictTypeNodeDto info)
    {
        //获取新增、编辑页面接口
        var page = App.GetService<DictTypeEditPage>();
        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}" />

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

/// <summary>
/// 初始化处理字典大类
/// </summary>
private async void Init()
{       
    //获取字典大类列表
    var dictItems = await BLLFactory<IDictTypeService>.Instance.GetAllType(null);    
    var listItem = new List<CListItem>();
    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树形列表的过滤处理

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

/// <summary>
    /// 触发处理命令
    /// </summary>
    [RelayCommand]
    private async Task GetTree()
    {
        var treeeNodeList = new List<OuNodeInfo>();
        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<IOuService>.Instance.GetTreeByID(groupInfo.Id);
                node.Children = sublist;
                treeeNodeList.Add(node);
            }
        }
        this.TreeItems = treeeNodeList;
        this.FilteredTreeItems = new List<OuNodeInfo>(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 进行查询处理,根据查询的内容进行匹配处理。

/// <summary>
    /// 过滤查询事件
    /// </summary>
    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);
        }
    }
    /// <summary>
    /// 递归查询嵌套节点的内容,根据规则进行匹配
    /// </summary>
    /// <typeparam name="T">数据类型</typeparam>
    /// <param name="nodes">节点集合</param>
    /// <param name="name">节点名称</param>
    /// <returns></returns>
    public List<T> FindNodes<T>(IEnumerable<T> nodes, string name) where T : OuNodeInfo
    {
        var result = new List<T>();
        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;
    }

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

 

链接附注

如对我们的代码生成工具有兴趣,可以到官网下载使用《代码生成工具Database2Sharp》。

如需了解我们官网对《SqlSugar开发框架》的介绍,可以参考《SqlSugar开发框架》了解。

如需阅读我们对于《SqlSugar开发框架》文章介绍,可以参考博客园的随笔标签《SqlSugar随笔 , WPF随笔》学习了解。

 

专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
 转载请注明出处:撰写人:伍华聪  http://www.iqidi.com
   

相关文章
|
2月前
|
C# 开发者 Windows
WPF 应用程序开发:一分钟入门
本文介绍 Windows Presentation Foundation (WPF),这是一种用于构建高质量、可缩放的 Windows 桌面应用程序的框架,支持 XAML 语言,方便 UI 设计与逻辑分离。文章涵盖 WPF 基础概念、代码示例,并深入探讨常见问题及解决方案,包括数据绑定、控件样式与模板、布局管理等方面,帮助开发者高效掌握 WPF 开发技巧。
163 65
|
3月前
|
C# UED 开发者
WPF与性能优化:掌握这些核心技巧,让你的应用从卡顿到丝滑,彻底告别延迟,实现响应速度质的飞跃——从布局到动画全面剖析与实例演示
【8月更文挑战第31天】本文通过对比优化前后的方法,详细探讨了提升WPF应用响应速度的策略。文章首先分析了常见的性能瓶颈,如复杂的XAML布局、耗时的事件处理、不当的数据绑定及繁重的动画效果。接着,通过具体示例展示了如何简化XAML结构、使用后台线程处理事件、调整数据绑定设置以及利用DirectX优化动画,从而有效提升应用性能。通过这些优化措施,WPF应用将更加流畅,用户体验也将得到显著改善。
223 1
|
3月前
|
安全 C# 数据安全/隐私保护
WPF安全加固全攻略:从数据绑定到网络通信,多维度防范让你的应用固若金汤,抵御各类攻击
【8月更文挑战第31天】安全性是WPF应用程序开发中不可或缺的一部分。本文从技术角度探讨了WPF应用面临的多种安全威胁及防护措施。通过严格验证绑定数据、限制资源加载来源、实施基于角色的权限管理和使用加密技术保障网络通信安全,可有效提升应用安全性,增强用户信任。例如,使用HTML编码防止XSS攻击、检查资源签名确保其可信度、定义安全策略限制文件访问权限,以及采用HTTPS和加密算法保护数据传输。这些措施有助于全面保障WPF应用的安全性。
51 0
|
3月前
|
C# 开发者 Windows
全面指南:WPF无障碍设计从入门到精通——让每一个用户都能无障碍地享受你的应用,从自动化属性到焦点导航的最佳实践
【8月更文挑战第31天】为了确保Windows Presentation Foundation (WPF) 应用程序对所有用户都具备无障碍性,开发者需关注无障碍设计原则。这不仅是法律要求,更是社会责任,旨在让技术更人性化,惠及包括视障、听障及行动受限等用户群体。
81 0
|
3月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
81 0
|
3月前
|
前端开发 C# 设计模式
“深度剖析WPF开发中的设计模式应用:以MVVM为核心,手把手教你重构代码结构,实现软件工程的最佳实践与高效协作”
【8月更文挑战第31天】设计模式是在软件工程中解决常见问题的成熟方案。在WPF开发中,合理应用如MVC、MVVM及工厂模式等能显著提升代码质量和可维护性。本文通过具体案例,详细解析了这些模式的实际应用,特别是MVVM模式如何通过分离UI逻辑与业务逻辑,实现视图与模型的松耦合,从而优化代码结构并提高开发效率。通过示例代码展示了从模型定义、视图模型管理到视图展示的全过程,帮助读者更好地理解并应用这些模式。
96 0
|
3月前
|
容器 C# Docker
WPF与容器技术的碰撞:手把手教你Docker化WPF应用,实现跨环境一致性的开发与部署
【8月更文挑战第31天】容器技术简化了软件开发、测试和部署流程,尤其对Windows Presentation Foundation(WPF)应用程序而言,利用Docker能显著提升其可移植性和可维护性。本文通过具体示例代码,详细介绍了如何将WPF应用Docker化的过程,包括创建Dockerfile及构建和运行Docker镜像的步骤。借助容器技术,WPF应用能在任何支持Docker的环境下一致运行,极大地提升了开发效率和部署灵活性。
119 0
|
3月前
|
存储 C# 关系型数据库
“云端融合:WPF应用无缝对接Azure与AWS——从Blob存储到RDS数据库,全面解析跨平台云服务集成的最佳实践”
【8月更文挑战第31天】本文探讨了如何将Windows Presentation Foundation(WPF)应用与Microsoft Azure和Amazon Web Services(AWS)两大主流云平台无缝集成。通过具体示例代码展示了如何利用Azure Blob Storage存储非结构化数据、Azure Cosmos DB进行分布式数据库操作;同时介绍了如何借助Amazon S3实现大规模数据存储及通过Amazon RDS简化数据库管理。这不仅提升了WPF应用的可扩展性和可用性,还降低了基础设施成本。
82 0
|
3月前
|
vr&ar C# 图形学
WPF与AR/VR的激情碰撞:解锁Windows Presentation Foundation应用新维度,探索增强现实与虚拟现实技术在现代UI设计中的无限可能与实战应用详解
【8月更文挑战第31天】增强现实(AR)与虚拟现实(VR)技术正迅速改变生活和工作方式,在游戏、教育及工业等领域展现出广泛应用前景。本文探讨如何在Windows Presentation Foundation(WPF)环境中实现AR/VR功能,通过具体示例代码展示整合过程。尽管WPF本身不直接支持AR/VR,但借助第三方库如Unity、Vuforia或OpenVR,可实现沉浸式体验。例如,通过Unity和Vuforia在WPF中创建AR应用,或利用OpenVR在WPF中集成VR功能,从而提升用户体验并拓展应用功能边界。
68 0
|
3月前
|
区块链 C# 存储
链动未来:WPF与区块链的创新融合——从智能合约到去中心化应用,全方位解析开发安全可靠DApp的最佳路径
【8月更文挑战第31天】本文以问答形式详细介绍了区块链技术的特点及其在Windows Presentation Foundation(WPF)中的集成方法。通过示例代码展示了如何选择合适的区块链平台、创建智能合约,并在WPF应用中与其交互,实现安全可靠的消息存储和检索功能。希望这能为WPF开发者提供区块链技术应用的参考与灵感。
62 0