在我们展示一些参考信息的时候,有所会用树形列表来展示结构信息,如对于有父子关系的多层级部门机构,以及一些常用如字典大类节点,也都可以利用树形列表的方式进行展示,本篇随笔介绍基于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;
}

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

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

  1. 基于SqlSugar的开发框架循序渐进介绍(3)-- 实现代码生成工具Database2Sharp的整合开发

    我喜欢在一个项目开发模式成熟的时候,使用代码生成工具Database2Sharp来配套相关的代码生成,对于我介绍的基于SqlSugar的开发框架,从整体架构确定下来后,我就着手为它们量身定做相关的代码 ...

  2. 推荐一个基于Vue2.0的的一款移动端开发的UI框架,特别好用。。。

    一丶YDUI 一只注重审美,且性能高效的移动端&微信UI. 下面为地址自己研究去吧! 我的项目正在用,以前用的Mint-ui但是现在感觉还是这个好一点,官方给出的解释很清楚,很实用. 官方地址 ...

  3. 基于SqlSugar的开发框架循序渐进介绍(4)-- 在数据访问基类中对GUID主键进行自动赋值处理

    我们在设计数据库表的时候,往往为了方便,主键ID一般采用字符串类型或者GUID类型,这样对于数据库表记录的迁移非常方便,而且有时候可以在处理关联记录的时候,提前对应的ID值.但有时候进行数据记录插入的 ...

  4. 基于SqlSugar的开发框架循序渐进介绍(5)-- 在服务层使用接口注入方式实现IOC控制反转

    在前面随笔,我们介绍过这个基于SqlSugar的开发框架,我们区分Interface.Modal.Service三个目录来放置不同的内容,其中Modal是SqlSugar的映射实体,Interface ...

  5. 基于SqlSugar的开发框架循序渐进介绍(6)-- 在基类接口中注入用户身份信息接口

    在基于SqlSugar的开发框架中,我们设计了一些系统服务层的基类,在基类中会有很多涉及到相关的数据处理操作的,如果需要跟踪具体是那个用户进行操作的,那么就需要获得当前用户的身份信息,包括在Web A ...

  6. 基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录

    在我们对数据进行重要修改调整的时候,往往需要跟踪记录好用户操作日志.一般来说,如对重要表记录的插入.修改.删除都需要记录下来,由于用户操作日志会带来一定的额外消耗,因此我们通过配置的方式来决定记录那些 ...

  7. 基于SqlSugar的开发框架循序渐进介绍(12)-- 拆分页面模块内容为组件,实现分而治之的处理

    在早期的随笔就介绍过,把常规页面的内容拆分为几个不同的组件,如普通的页面,包括列表查询.详细资料查看.新增资料.编辑资料.导入资料等页面场景,这些内容相对比较独立,而有一定的代码量,本篇随笔介绍基于V ...

  8. 基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用

    在我们实际项目开发过程中,往往需要根据实际情况,对组件进行封装,以更简便的在界面代码中使用,在实际的前端应用中,适当的组件封装,可以减少很多重复的界面代码,并且能够非常简便的使用,本篇随笔介绍基于El ...

  9. 基于SqlSugar的开发框架循序渐进介绍(14)-- 基于Vue3+TypeScript的全局对象的注入和使用

    刚完成一些前端项目的开发,腾出精力来总结一些前端开发的技术点,以及继续完善基于SqlSugar的开发框架循序渐进介绍的系列文章,本篇随笔主要介绍一下基于Vue3+TypeScript的全局对象的注入和 ...

  10. 基于SqlSugar的开发框架循序渐进介绍(17)-- 基于CSRedis实现缓存的处理

    在一个应用系统的开发框架中,往往很多地方需要用到缓存的处理,有些地方是为了便于记录用户的数据,有些地方是为了提高系统的响应速度,如有时候我们在发送一个短信验证码的时候,可以在缓存中设置几分钟的过期时间 ...

随机推荐

  1. 为什么会出现 setTimeout 倒计时误差

    setTimeout 倒计时误差的出现主要与 JavaScript 的事件循环机制和计时器的执行方式有关. 在 JavaScript 中,事件循环是用于管理和调度代码执行的机制.setTimeout ...

  2. 在DevExpress的GridView的列中,使用RepositoryItemSearchLookUpEdit控件实现产品列表信息的展示和选择

    有时候,我们为了方便,我们往往使用扩展函数的代码方式创建很多GridView的操作功能,如在随笔<在DevExpress中使用BandedGridView表格实现多行表头的处理>中介绍过多 ...

  3. Ascend C sqrt算子实战

    摘要:编写一个Ascend C的sqrt算子,并通过内核调用方式在cpu和npu模式下进行验证. 本文分享自华为云社区<[2023 · CANN训练营第一季]--Ascend C sqrt算子实 ...

  4. vscode使用chatGPT

    vscode使用chatGPT 一.下载chatPGT 在拓展中找到chatGPT,我这里下载的是中文版 二.使用 1.使用快捷键 ctrl+shift+p进行查找 chatGPT 2.点击请输入问题 ...

  5. Java 网络编程 —— RMI 框架

    概述 RMI 是 Java 提供的一个完善的简单易用的远程方法调用框架,采用客户/服务器通信方式,在服务器上部署了提供各种服务的远程对象,客户端请求访问服务器上远程对象的方法,它要求客户端与服务器端都 ...

  6. Tomcat启动时出现乱码的解决方式

    在网上下载了一个版本号为apache-tomcat-8.5.38的Tomcat,因为这个Tomcat一直没有用过,所以今天启动时出现了如下乱码: 解决方案: 找到Tomcat目录下conf文件夹中的l ...

  7. 即构推出低延迟直播产品L3,可将直播延迟降到1s

    近日,全球云通讯服务提供商ZEGO即构科技推出低延迟直播产品Low-Latency Live,简称L3.这款产品对传统CDN直播中"延迟较大.弱网抗性差.观众端内容不同步"等问题进 ...

  8. Hexo博客Next主题bilibili视频Markdown插入文章

    问题及需求 B站视频无广告有弹幕,非常简洁,经常看B站视频,在文章引用B站的视频 在不用插件的情况下用官方的iframe方式引入视频,默认的方式导入视频屏幕会很小 一般我们都是自己改width和hei ...

  9. Hexo博客Next主题valine评论系统邮件提醒

    简介 Valine:一款快速.简洁且高效的无后端评论系统. Valine-Admin Github 项目地址,具体教程以 最新版 为准 Valine-Admin项目地址 简介 Valine Admin ...

  10. [Spring+SpringMVC+Mybatis]框架学习笔记(九):Mybatis主配置文件和映射文件

    第9章 Mybatis主配置文件和映射文件 9.1 用Mybatis进行开发的两种方式 在正式的开发环境中用Mybatis进行开发有两种方式: 1)原始的接口和实现类的方式 缺点: 重复代码太多,sq ...