如何使用Caliburn Micro在WPF MVVM中创建带动态嵌套数据的TreeView
WPF MVVM TreeView with Caliburn Micro (Dynamic/Nested Data)
Let's walk through the complete implementation to get your dynamic nested TreeView up and running with Caliburn Micro.
1. Complete the ViewModel Layer
First, let's finish the ShellViewModel (including the missing BuildTree logic) and add support for item selection. We'll also include a sample data structure to mimic your server response.
using Caliburn.Micro; using System.Collections.ObjectModel; using System.Linq; namespace YourNamespace { public class ShellViewModel : Screen // Inherit from Screen for Caliburn's lifecycle support { // Root collection bound to the TreeView via Caliburn convention public ObservableCollection<TreeItem> Root { get; } = new ObservableCollection<TreeItem>(); // Track selected TreeView item private TreeItem _selectedTreeItem; public TreeItem SelectedTreeItem { get => _selectedTreeItem; set { _selectedTreeItem = value; NotifyOfPropertyChange(() => SelectedTreeItem); // Add custom logic here when selection changes (e.g., load details) } } public ShellViewModel() { // Replace this with your actual server.GetData() call var serverData = GetSampleServerData(); BuildTree(serverData); } // Build nested TreeItem hierarchy from server data private void BuildTree(System.Collections.Generic.List<SampleServerData> serverData) { // Get top-level items (no parent) var rootLevelItems = serverData.Where(d => d.ParentId == null).ToList(); foreach (var rootData in rootLevelItems) { var treeRoot = new TreeItem { Name = rootData.Name }; // Recursively add child items AddChildItems(treeRoot, serverData); Root.Add(treeRoot); } } // Recursive method to populate child items private void AddChildItems(TreeItem parentItem, System.Collections.Generic.List<SampleServerData> serverData) { // Get all children for the current parent (adjust filter to match your data's parent-child relationship) var childData = serverData.Where(d => d.ParentId == parentItem.Name).ToList(); foreach (var childDataItem in childData) { var childTreeItem = new TreeItem { Name = childDataItem.Name }; // Recurse to add grandchildren AddChildItems(childTreeItem, serverData); parentItem.Children.Add(childTreeItem); } } // Optional: Handle double-click on TreeView items public void OnTreeItemDoubleClick(TreeItem clickedItem) { // Add your custom action here (e.g., open a detail view) System.Windows.MessageBox.Show($"You double-clicked: {clickedItem.Name}"); } // Sample server data structure (replace with your actual server model) private System.Collections.Generic.List<SampleServerData> GetSampleServerData() { return new System.Collections.Generic.List<SampleServerData> { new SampleServerData { Name = "Project A", ParentId = null }, new SampleServerData { Name = "Module 1", ParentId = "Project A" }, new SampleServerData { Name = "Submodule 1.1", ParentId = "Module 1" }, new SampleServerData { Name = "Module 2", ParentId = "Project A" }, new SampleServerData { Name = "Project B", ParentId = null }, new SampleServerData { Name = "Component X", ParentId = "Project B" } }; } } // Your existing TreeItem model (already correctly implemented!) public class TreeItem : PropertyChangedBase { private string name; public string Name { get => name; set { name = value; NotifyOfPropertyChange(() => Name); } } public ObservableCollection<TreeItem> Children { get; } = new ObservableCollection<TreeItem>(); } // Sample server data model (adjust to match your actual server response) public class SampleServerData { public string Name { get; set; } public string ParentId { get; set; } } }
2. Create the View (ShellView.xaml)
Next, define the TreeView in your view with a HierarchicalDataTemplate to handle nested items. We'll use Caliburn Micro's convention-based binding and add support for double-click actions.
<UserControl x:Class="YourNamespace.ShellView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cal="http://www.caliburnproject.org" xmlns:local="clr-namespace:YourNamespace"> <Grid Margin="10"> <!-- TreeView binds to the "Root" collection automatically via Caliburn's naming convention --> <TreeView x:Name="Root" SelectedItem="{Binding SelectedTreeItem, Mode=TwoWay}" BorderThickness="1" BorderBrush="#DDD"> <!-- Hierarchical template to render TreeItem and its nested children --> <TreeView.ItemTemplate> <HierarchicalDataTemplate DataType="{x:Type local:TreeItem}" ItemsSource="{Binding Children}"> <!-- TextBlock with double-click action bound to ViewModel method --> <TextBlock Text="{Binding Name}" Padding="2" cal:Message.Attach="[Event MouseDoubleClick] = [Action OnTreeItemDoubleClick($dataContext)]"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </Grid> </UserControl>
Key Notes & Features
- Dynamic Updates: Since
TreeItem.Childrenis anObservableCollection, any runtime changes (adding/removing items) will automatically reflect in the TreeView. - Caliburn Conventions: The TreeView's name
Rootmatches the ViewModel'sRootproperty, so Caliburn handles the binding automatically—no need for explicitItemsSource="{Binding Root}". - Selection Handling: The
SelectedTreeItemproperty in the ViewModel stays in sync with the TreeView's selected item via two-way binding. - Recursive Hierarchy: The
BuildTreeandAddChildItemsmethods recursively construct the nested tree from your server data (adjust the parent-child filtering logic to match your actual data structure).
内容的提问来源于stack exchange,提问作者MagB
相关产品推荐
相关产品推荐

