我是 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));
}
}
您要求在视图模型中实现该行为。但您应该考虑在项目模型本身内部实现该行为。项目模型已经实现了选择行为,因为它将选择状态委托给了它的所有子项。
在项目模型内部或拥有项目的视图模型内部实现行为。不要混合两者。保持一致。
但是,您必须修改项目模型并添加
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>