在我們展示一些參考信息的時候,有所會用樹形列表來展示結構信息,如對於有父子關係的多層級部門機構,以及一些常用如字典大類節點,也都可以利用樹形列表的方式進行展示,本篇隨筆介紹基於WPF的方式,使用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; }
這樣就可以實現數據的查詢過濾,實際規則可以自己根據需要進行調整。
專註於代碼生成工具、.Net/.NetCore 框架架構及軟體開發,以及各種Vue.js的前端技術應用。著有Winform開發框架/混合式開發框架、微信開發框架、Bootstrap開發框架、ABP開發框架、SqlSugar開發框架等框架產品。
轉載請註明出處:撰寫人:伍華聰 http://www.iqidi.com