Xaml 中的 TreeViewItem MVVM IsSelected 不向视图模型发送值

TreeViewItem MVVM IsSelected in Xaml doesn't send value to View-Model

我对 IsSelected 属性 有疑问。它不会将值从视图发送到视图模型。我在下面发布了我的代码 视图模型:

public class Viewmodel : INotifyPropertyChanged
{
    private ObservableCollection<int> seznam;
    public ObservableCollection<int> Seznam
    {
        get { return seznam; }
        set
        {
            seznam = value;
        }
    }

    public Viewmodel()
    {
        Seznam = new ObservableCollection<int>();
        for (int i = 0; i < 3; i++)
        {
            Seznam.Add(i);
        }
    }

    bool isSelected;
    public bool IsSelected
    {
        get { return isSelected; }
        set
        {
            isSelected = value;
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

查看:

        <TreeView ItemsSource="{Binding Seznam}">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
            </Style>
        </TreeView.ItemContainerStyle>
    </TreeView>

它仍然没有在我设置的断点处停止 get { return isSelected; }

不要在树中的每个节点上使用 IsSelected,而是在 TreeView 本身上使用 TreeView.SelectedItem。从这里您可以绑定,但是 属性 是只读的。

更新后的 post,很明显您没有正确实现视图模型。特别是,您的 TreeView.ItemsSource 绑定到您唯一的视图模型的 Seznam 属性。这是 collection 的 int 个值。

这意味着 TreeView 中您尝试绑定到 IsSelected 属性 的每个项目容器的数据上下文是一个 int 值。当然,int 值甚至没有 IsSelected 属性.

(顺便说一下,我对 your claim that "There are no binding errors" 持怀疑态度。如果您查看调试输出,您当然应该看到一个绑定错误,试图绑定到 non-existent IsSelected 属性.)

然后想一想:假设项目容器确实设法绑定到 Viewmodel.IsSelected 属性。你认为有多少个物品容器?您认为 Viewmodel 有多少个实例?您应该相信有很多物品容器,即您的collection中的每个物品一个。而且 Viewmodel 只有一个实例。那么,所有这些项目的选择状态如何映射到单个 Viewmodel.IsSelected 属性?

正确的方法是为 collection 创建一个单独的视图模型 object,同时为 int 值创建一个 属性作为 IsSelectedIsExpanded 状态的属性(因为您最初提到两者都想要)。

这是我之前写的例子,只是为了向自己证明通常的方法会按预期工作。根据您的需要调整它应该没有任何问题......

Per-item 查看模型:

class TreeItemViewModel : NotifyPropertyChangedBase
{
    public ObservableCollection<TreeItemViewModel> Items { get; }
        = new ObservableCollection<TreeItemViewModel>();

    private bool _isSelected;
    public bool IsSelected
    {
        get { return _isSelected; }
        set { _UpdateField(ref _isSelected, value, _OnBoolPropertyChanged); }
    }

    private bool _isExpanded;
    public bool IsExpanded
    {
        get { return _isExpanded; }
        set { _UpdateField(ref _isExpanded, value, _OnBoolPropertyChanged); }
    }

    private void _OnBoolPropertyChanged(bool obj)
    {
        _RaisePropertyChanged(nameof(FullText));
    }

    private string _text;
    public string Text
    {
        get { return _text; }
        set { _UpdateField(ref _text, value, _OnTextChanged); }
    }

    private void _OnTextChanged(string obj)
    {
        _RaisePropertyChanged(nameof(FullText));
    }

    public string FullText
    {
        get { return $"{Text} (IsSelected: {IsSelected}, IsExpanded: {IsExpanded})"; }
    }
}

window的主视图模型:

class MainViewModel : NotifyPropertyChangedBase
{
    public ObservableCollection<TreeItemViewModel> Items { get; }
        = new ObservableCollection<TreeItemViewModel>();

    public ICommand ClearSelection { get; }

    public MainViewModel()
    {
        ClearSelection = new ClearSelectionCommand(this);
    }

    class ClearSelectionCommand : ICommand
    {
        private readonly MainViewModel _parent;

        public ClearSelectionCommand(MainViewModel parent)
        {
            _parent = parent;
        }

#pragma warning disable 67
        public event EventHandler CanExecuteChanged;
#pragma warning restore 67

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            _parent._ClearSelection();
        }
    }

    private void _ClearSelection()
    {
        _ClearSelection(Items);
    }

    private static void _ClearSelection(IEnumerable<TreeItemViewModel> collection)
    {
        foreach (TreeItemViewModel item in collection)
        {
            _ClearSelection(item.Items);
            item.IsSelected = false;
            item.IsExpanded = false;
        }
    }
}

XAML 对于 window:

<Window x:Class="TestSO44513864TreeViewIsSelected.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:p="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:l="clr-namespace:TestSO44513864TreeViewIsSelected"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
  <Window.DataContext>
    <l:MainViewModel>
      <l:MainViewModel.Items>
        <l:TreeItemViewModel Text="One">
          <l:TreeItemViewModel.Items>
            <l:TreeItemViewModel Text="One A"/>
            <l:TreeItemViewModel Text="One B"/>
          </l:TreeItemViewModel.Items>
        </l:TreeItemViewModel>
        <l:TreeItemViewModel Text="Two"/>
        <l:TreeItemViewModel Text="Three"/>
      </l:MainViewModel.Items>
    </l:MainViewModel>
  </Window.DataContext>

  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition/>
    </Grid.RowDefinitions>
    <Button Content="Clear Selection" Command="{Binding ClearSelection}"
            HorizontalAlignment="Left"/>
    <TreeView ItemsSource="{Binding Items}" Grid.Row="1">
      <TreeView.ItemContainerStyle>
        <p:Style TargetType="TreeViewItem">
          <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
          <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
        </p:Style>
      </TreeView.ItemContainerStyle>
      <TreeView.ItemTemplate>
        <HierarchicalDataTemplate DataType="l:TreeItemViewModel"
                                  ItemsSource="{Binding Items}">
          <TextBlock Text="{Binding FullText}"/>
        </HierarchicalDataTemplate>
      </TreeView.ItemTemplate>
    </TreeView>
  </Grid>
</Window>

为了完整起见...

用于 INotifyPropertyChanged 实施的样板基础 class:

class NotifyPropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void _UpdateField<T>(ref T field, T newValue,
        Action<T> onChangedCallback = null,
        [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, newValue))
        {
            return;
        }

        T oldValue = field;

        field = newValue;
        onChangedCallback?.Invoke(oldValue);
        _RaisePropertyChanged(propertyName);
    }

    protected void _RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}