如何让TreeView节点按钮根据节点类型执行对应删除操作?
问题描述
我在TreeView的每个节点上都配置了一个按钮,希望点击按钮时根据所在节点的类型执行对应操作,但还没找到实现方法。我有Material、Brand、Serie、Product四张数据库表,比如当DeleteButton_Click事件触发时,如果按钮处于Brand节点,该如何让它执行对应的删除逻辑?
XAML代码
<Button Width="18" Height="20" Click="DeleteButton_Click" Margin="2 0 0 0" Tag="{Binding DataContext}"> <Button.Style> <Style TargetType="Button"> <Setter Property="Visibility" Value="Collapsed"/> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type TreeViewItem}}, Path=IsSelected}" Value="True"> <Setter Property="Visibility" Value="Visible"/> </DataTrigger> </Style.Triggers> </Style> </Button.Style> <Button.Content> <Grid> <TextBlock Text="Sil" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/> </Grid> </Button.Content> </Button>
TreeNodeViewModel代码
namespace Bornander.UI.ViewModels { public static class ObservableCollectionExtensions { public static void AddRange(this ObservableCollection collection, IEnumerable items) { foreach (var item in items) { collection.Add(item); } } } public class TreeNodeViewModel : INotifyPropertyChanged { private readonly ObservableCollection<TreeNodeViewModel> children; private readonly string name; private static int nextId = 1; public int Id { get; } private bool expanded; private bool match = true; private bool isLastChild; private bool isSelected; private object item; public TreeNodeViewModel(string name, IEnumerable<TreeNodeViewModel> children) { this.name = name; this.children = new ObservableCollection<TreeNodeViewModel>(); this.children.AddRange(children); Id = nextId++; } public TreeNodeViewModel(string name) : this(name, Enumerable.Empty<TreeNodeViewModel>()) { } public void AddChildren(ObservableCollection<TreeNodeViewModel> children) { foreach (var child in children) { this.children.Add(child); child.Parent = this; } } public void AddChildren(params TreeNodeViewModel[] children) { foreach (var child in children) { this.children.Add(child); child.Parent = this; } } public override string ToString() { return name; } private bool IsCriteriaMatched(string criteria) { return String.IsNullOrEmpty(criteria) || name.Contains(criteria); } private void CheckChildren(string criteria, TreeNodeViewModel parent) { foreach (var child in parent.Children) { if (child.IsLeaf && !child.IsCriteriaMatched(criteria)) { child.IsMatch = false; } CheckChildren(criteria, child); } } public void ApplyCriteria(string criteria, Stack<TreeNodeViewModel> ancestors) { if (IsCriteriaMatched(criteria)) { IsMatch = true; foreach (var ancestor in ancestors) { ancestor.IsMatch = true; ancestor.IsExpanded = !String.IsNullOrEmpty(criteria); CheckChildren(criteria, ancestor); } IsExpanded = false; } else { IsMatch = false; } ancestors.Push(this); foreach (var child in Children) { child.ApplyCriteria(criteria, ancestors); } ancestors.Pop(); } public IEnumerable<TreeNodeViewModel> Children => children; public string Name => name; public bool IsExpanded { get => expanded; set { if (value == expanded) { return; } expanded = value; if (expanded) { foreach (var child in Children) { child.IsMatch = true; } } } } public bool IsMatch { get => match; set { if (value == match) { return; } match = value; OnPropertyChanged(nameof(IsMatch)); } } public bool IsLastChild { get => isLastChild; set { if (value == isLastChild) { return; } isLastChild = value; OnPropertyChanged(nameof(IsLastChild)); } } public bool IsSelected { get => isSelected; set { if (value == isSelected) { return; } isSelected = value; OnPropertyChanged(nameof(IsSelected)); } } public object Item { get => item; set { if (value == item) { return; } item = value; OnPropertyChanged(nameof(Item)); } } public bool IsLeaf => !Children.Any(); public TreeNodeViewModel Parent { get; private set; } public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } }
解决方案
方法一:直接在点击事件中判断类型
你的TreeNodeViewModel里已经有Item属性,可以用来存储对应的数据实体(比如Brand、Material等对象)。在按钮点击事件中,获取当前节点的ViewModel,判断Item的类型,再执行对应的删除逻辑:
private void DeleteButton_Click(object sender, RoutedEventArgs e) { var button = sender as Button; var nodeViewModel = button?.DataContext as TreeNodeViewModel; if (nodeViewModel == null) return; // 根据Item的类型执行对应删除逻辑 switch (nodeViewModel.Item) { case Brand brand: // 执行Brand的删除操作,比如调用数据库删除方法 DeleteBrand(brand); break; case Material material: DeleteMaterial(material); break; case Serie serie: DeleteSerie(serie); break; case Product product: DeleteProduct(product); break; default: // 处理未知类型 break; } // 删除后记得更新TreeView的节点列表 nodeViewModel.Parent?.Children.Remove(nodeViewModel); } // 示例删除方法 private void DeleteBrand(Brand brand) { // 这里写删除Brand的数据库操作逻辑 using (var db = new YourDbContext()) { db.Brands.Remove(brand); db.SaveChanges(); } }
方法二:MVVM命令模式(更优雅的实现)
如果想更贴合MVVM模式,可以给TreeNodeViewModel添加删除命令,甚至创建不同类型的子类ViewModel,各自实现自己的删除逻辑:
- 首先定义一个通用的RelayCommand:
public class RelayCommand : ICommand { private readonly Action<object> _execute; private readonly Func<object, bool> _canExecute; public event EventHandler CanExecuteChanged; public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null) { _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } public bool CanExecute(object parameter) => _canExecute?.Invoke(parameter) ?? true; public void Execute(object parameter) => _execute(parameter); public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); }
- 修改
TreeNodeViewModel,添加删除命令,并让子类重写删除逻辑:
public class TreeNodeViewModel : INotifyPropertyChanged { // ... 原有代码 ... public ICommand DeleteCommand { get; protected set; } public TreeNodeViewModel(string name, IEnumerable<TreeNodeViewModel> children) { // ... 原有构造代码 ... DeleteCommand = new RelayCommand(ExecuteDelete); } protected virtual void ExecuteDelete(object parameter) { // 基类默认实现,留空让子类重写 } // ... 原有代码 ... } // 创建Brand节点的ViewModel子类 public class BrandTreeNodeViewModel : TreeNodeViewModel { private readonly Brand _brand; private readonly YourDbContext _dbContext; public BrandTreeNodeViewModel(Brand brand, YourDbContext dbContext) : base(brand.Name) { _brand = brand; _dbContext = dbContext; Item = brand; } protected override void ExecuteDelete(object parameter) { // 执行Brand的删除逻辑 _dbContext.Brands.Remove(_brand); _dbContext.SaveChanges(); // 从父节点移除自己 Parent?.Children.Remove(this); } }
- 修改XAML,绑定命令代替点击事件:
<Button Width="18" Height="20" Command="{Binding DeleteCommand}" Margin="2 0 0 0"> <!-- 原有样式和内容 --> </Button>
这种方式把删除逻辑封装在对应的ViewModel里,更符合MVVM的职责分离原则,代码也更易维护。
内容的提问来源于stack exchange,提问作者Mertau
相关产品推荐
相关产品推荐

