TreeView 根据子项选择父复选框 - WPF MVVM

问题描述 投票:0回答:1

我是 WPF 新手。我找到了一些示例,并在 C# WPF Mvvm 中整理了一个树视图示例。我根据父母的选择选中或取消选中孩子的复选框。我不确定如何访问父绑定或属性以根据子项检查或取消选中它。如果选择了所有孩子,我希望对父母进行检查,如果只有一个孩子未检查,我希望父母不被检查。如何使用 MVVM 视图模型实现此目的?任何建议将不胜感激。

Xaml 视图

<Window x:Class="WpfApp1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApp1"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
    <local:MainViewModel></local:MainViewModel>
</Window.DataContext>
<Window.Resources>
    <HierarchicalDataTemplate DataType="{x:Type local:CheckableItem}" ItemsSource="{Binding 
        Children, Mode=TwoWay}">
        <StackPanel Orientation="Horizontal">
            <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type TreeView}}, 
                                                Path=DataContext.CheckBoxCommand}" CommandParameter="{Binding}"/>
            <TextBlock Text="{Binding Name}"/>
        </StackPanel>
    </HierarchicalDataTemplate>
</Window.Resources>

<StackPanel>
    <Label Content="Miscellaneous Imports" HorizontalAlignment="Center" />
    <ScrollViewer>
        <TreeView ItemsSource="{Binding MiscellaneousImports, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="10" Height="450"/>
    </ScrollViewer>
</StackPanel>

视图模型

public class MainViewModel : INotifyPropertyChanged
{

    public event PropertyChangedEventHandler? PropertyChanged;

    public ICommand CheckBoxCommand { get; set; }
    public MainViewModel()
    {
        LoadCheckableItems();
        CheckBoxCommand = new DelegateCommand<CheckableItem>(OnCheckBoxChecked);
    }

    private void OnCheckBoxChecked(CheckableItem item)
    {
        throw new NotImplementedException();
    }

    private ObservableCollection<CheckableItem> miscellaneousImports;
    public ObservableCollection<CheckableItem> MiscellaneousImports
    {
        
        get { return miscellaneousImports; }
        set
        {
            miscellaneousImports = value;
            OnPropertyChanged("MiscellaneousImports");
        }
    }
    private void LoadCheckableItems()
    {
        List<CheckableItem> lstItems = new List<CheckableItem>();

        CheckableItem item = new CheckableItem
        { Name = "Coffee", Children = new ObservableCollection<CheckableItem>() { new CheckableItem { Name="Medium" }, new CheckableItem {Name="Dark" } } };
        lstItems.Add(item);

        MiscellaneousImports = new ObservableCollection<CheckableItem> ( lstItems );
    }
    protected void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

复选框集合类

public class CheckableItem:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }
        public ObservableCollection<CheckableItem> Children { get; set; }
        private bool _isChecked;
        public bool IsChecked
        {
            get { return _isChecked; }
            set
            {
                _isChecked = value;
                OnPropertyChanged("IsChecked");
                if (Children != null)
                {
                    foreach (CheckableItem child in Children)
                    {
                        child.IsChecked = IsChecked;
                    }
                    OnPropertyChanged(nameof(Children));
                }
            }
        }
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
     
    }
wpf mvvm checkbox treeview
1个回答
0
投票

您要求在视图模型中实现该行为。但您应该考虑在项目模型本身内部实现该行为。项目模型已经实现了选择行为,因为它将选择状态委托给了它的所有子项。
在项目模型内部或拥有项目的视图模型内部实现行为。不要混合两者。保持一致。

但是,您必须修改项目模型并添加

IsSelected
事件和
Parent
属性才能实现向根的遍历。
此外,为了提高性能,您不应在父级更改后立即修改整个子树。相反,请等到子级变得可见(展开)。为此,项目模型还必须定义一个
IsExpanded
属性,您可以将其绑定到
TreeViewItem.IsExpanded
属性。

通常数据模型更加抽象,而不是 UI 控件的表示。因此,该类型及其成员不会被命名,例如

CheckBoxModel
类和
IsChecked
属性。

CheckableItem.cs

class CheckableItem : INotifyPropertyChanged
{
  public CheckableItem(CheckableItem parent)
  {
    this.Parent = parent;
    this.Children = new ObservableCollection<CheckableItem>();
    this.Chldren.CollectionChanged += OnChildCollectionChanged;
  }

  private void HandleParentSelection()
  {
    if (this.IsSelected)
    {
      SelectChildren();
    }
    else
    {
      UnselectChildren();
    }
  }

  private void HandleChildSelection()
  {
    if (this.IsSelected)
    {
      SelectChildren();
    }
    else
    {
      UnselectChildren();
    }
  }

  private void SelectChildren()
  {
    foreach (CheckableItem child in this.Children)
    {
      child.IsSelected = true;
    }
  }

  private void UnselectChildren()
  {
    foreach (CheckableItem child in this.Children)
    {
      child.IsSelected = false;
    }
  }      

  private void OnIsExpandedChanged()
  {    
    if (!this.HasPendingChildSelections)
    {
      return;
    }

    this.HasPendingChildSelections = false;
    HandleChildSelection();
  }

  private void OnIsSelectedChanged()
  {
    if (!this.IsExpanded)
    {
      this.HasPendingChildSelections = true;
      return;
    }

    HandleChildSelection();
  }

  private void OnChildCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
  {
    switch (e.Action)
    {
      case NotifyCollectionChangedAction.Add:
        foreach (CheckableItem newItem in e.NewItems.Cast<CheckableItem>())
        {
          newItem.SelectionChanged += OnChildSelectionChanged;
        }
        break;
      case NotifyCollectionChangedAction.Remove:
      case NotifyCollectionChangedAction.Replace:
        foreach (CheckableItem oldItem in e.OldItems.Cast<CheckableItem>())
        {
          oldItem.SelectionChanged -= OnChildSelectionChanged;
        }
        break;
      case NotifyCollectionChangedAction.Reset:
        foreach (CheckableItem oldItem in this.Children)
        {
          oldItem.SelectionChanged -= OnChildSelectionChanged;
        }
        break;
      case NotifyCollectionChangedAction.Move:
        break;
    }
  }

  private void OnChildSelectionChanged(object sender, SelectionChangedEventArgs e)
  {
    // Select parent (this) if all children are selected
    // or unselect if any child is unselected
    this.IsSelected = this.Children.All(child => child.IsSelected);
  }

  protected virtual void OnSelectionChanged(bool isSelected)
    => this.SelectionChanged?.Invoke(this, new SelectionChangedEventArgs(isSelected);

  // TODO::Create SelectionChangedEventArgs class that extends EventArgs
  public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
  
  public CheckableItem Parent { get; } 
  public ObservableCollection<CheckableItem> Children { get; }

  private bool isExpanded;
  public bool IsExpanded // Bind to TreeViewItem.IsExpanded (TwoWay)
  {
    get => this.isExpanded; 
    set
    {
      if (this.isExpanded == value)
      {
        return;
      }

      this.isExpanded = value;
      OnPropertyChanged(nameof(this.IsExpanded));
      OnIsExpandedChanged();
    }
  }

  private bool isSelected;
  public bool IsSelected // Bind to CheckBox.IsSelected (TwoWay)
  {
    get => this.isSelected; 
    set
    {
      if (this.isSelected == value)
      {
        return;
      }

      this.isSelected = value;
      OnPropertyChanged(nameof(this.IsSelected));
      OnIsSelectedChanged();
    }
  }

  private bool HasPendingChildSelections { get; set; }
}
<TreeView>
  <TreeView.ItemContainerStyle>
    <Style TargetType="TreeViewItem">
      <Setter Property="IsExpanded"
              Value="{Binding IsExpanded, Mode=TwoWay" />
    </Style>
  </TreeView.ItemContainerStyle>
</TreeView>
© www.soinside.com 2019 - 2024. All rights reserved.