I have a problem with IsSelected property. It doesn't send values from view to view-model. I posted my code below ViewModel:
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));
}
}
View:
<TreeView ItemsSource="{Binding Seznam}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
It still doesn't stop at breakpoint that I put on get { return isSelected; }
With your updated post, it is clear that you haven't implemented your view models correctly. In particular, your TreeView.ItemsSource
is bound to the Seznam
property of your only view model. This is a collection of int
values.
This means that the data context for each item container in the TreeView
, where you attempt to bind to the IsSelected
property, is an int
value. And of course, int
values don't even have an IsSelected
property.
(By the way, I am skeptical about your claim that "There are no binding errors". If you looked at the debug output, you certainly should have seen a binding error, where attempting to bind to the non-existent IsSelected
property.)
And think about this for a moment: supposing the item container did manage to bind to the Viewmodel.IsSelected
property. How many item containers do you think there are? And how many instances of Viewmodel
do you think there are? You should believe that there are many item containers, i.e. one for each item in your collection. And that there is just one instance of Viewmodel
. So, how would all those items' selection state even map to the single Viewmodel.IsSelected
property?
The right way to do this would be to create a separate view model object for the collection, with a property for your int
value, as well as properties for the IsSelected
and IsExpanded
states (since you originally had mentioned wanting both).
Here is the example I wrote earlier just to prove to myself that the usual approach would work as expected. You should not have any trouble adapting it to suit your needs…
Per-item view model:
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})"; }
}
}
Main view model for 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 for 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>
And for completeness…
The boilerplate base class for INotifyPropertyChanged
implementation:
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));
}
}