You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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,各自实现自己的删除逻辑:

  1. 首先定义一个通用的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);
}
  1. 修改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);
    }
}
  1. 修改XAML,绑定命令代替点击事件:
<Button Width="18" Height="20" Command="{Binding DeleteCommand}" Margin="2 0 0 0">
    <!-- 原有样式和内容 -->
</Button>

这种方式把删除逻辑封装在对应的ViewModel里,更符合MVVM的职责分离原则,代码也更易维护。


内容的提问来源于stack exchange,提问作者Mertau

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 15:03:09