I’m trying to write a mutli select treeview behavior, however while doing so I’m getting this cryptic error “Items collection must be empty before using ItemsSource.”
The following is my xaml code:
<Window x:Class="TreeView.Spike.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Spike="clr-namespace:TreeView.Spike"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*"/>
<ColumnDefinition Width=".5*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="40"></RowDefinition>
</Grid.RowDefinitions>
<TreeView ItemsSource="{Binding Nodes}" Grid.Row="0" x:Name="treeView" Grid.Column="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}">
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add"></MenuItem>
<MenuItem Header="Delete"></MenuItem>
</ContextMenu>
</TreeView.ContextMenu>
<Spike:MultipleItemSelectionAttachedBehavior AllSelectedItems="{Binding Path=AllSelectedNodes}"/>
</TreeView>
</Grid>
</Window>
My attached behavior:
public class MultipleItemSelectionAttachedBehavior:Behavior<System.Windows.Controls.TreeView>
{
public static DependencyProperty AllSelectedItemsProperty =
DependencyProperty.RegisterAttached("AllSelectedItems", typeof(object), typeof(MultipleItemSelectionAttachedBehavior),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Inherits));
private static readonly PropertyInfo IsSelectionChangeActiveProperty = typeof(System.Windows.Controls.TreeView).GetProperty("IsSelectionChangeActive",
BindingFlags.NonPublic | BindingFlags.Instance);
public object AllSelectedItems
{
get
{
return (object)GetValue(AllSelectedItemsProperty);
}
set
{
SetValue(AllSelectedItemsProperty, value);
}
}
public static bool GetAllSelectedItems(DependencyObject obj)
{
return (bool)obj.GetValue(AllSelectedItemsProperty);
}
public static void SetAllSelectedItems(DependencyObject obj, bool value)
{
obj.SetValue(AllSelectedItemsProperty, value);
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += AssociatedObject_SelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectedItemChanged -= AssociatedObject_SelectedItemChanged;
}
void AssociatedObject_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (IsSelectionChangeActiveProperty == null) return;
var selectedItems = new List<Node>();
var treeViewItem = AssociatedObject.SelectedItem as Node;
if (treeViewItem == null) return;
// allow multiple selection
// when control key is pressed
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
var isSelectionChangeActive = IsSelectionChangeActiveProperty.GetValue(AssociatedObject, null);
IsSelectionChangeActiveProperty.SetValue(AssociatedObject, true, null);
selectedItems.ForEach(item => item.IsSelected = true);
IsSelectionChangeActiveProperty.SetValue(AssociatedObject, isSelectionChangeActive, null);
}
else
{
// deselect all selected items except the current one
selectedItems.ForEach(item => item.IsSelected = (item == treeViewItem));
selectedItems.Clear();
}
if (!selectedItems.Contains(treeViewItem))
{
selectedItems.Add(treeViewItem);
}
else
{
// deselect if already selected
treeViewItem.IsSelected = false;
selectedItems.Remove(treeViewItem);
}
AllSelectedItems = selectedItems;
}
}
..and my ViewModel
public class ViewModel :NotificationObject
{
public ViewModel()
{
AllSelectedNodes = new ObservableCollection<Node>();
}
private ObservableCollection<Node> _allSelectedNodes;
public ObservableCollection<Node> AllSelectedNodes
{
get { return _allSelectedNodes; }
set
{
_allSelectedNodes = value;
RaisePropertyChanged(() => AllSelectedNodes);
}
}
}
My Model:
public class Node:NotificationObject
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
private bool _isExpanded = true;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
_isExpanded = value;
RaisePropertyChanged(() => IsExpanded);
}
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
RaisePropertyChanged(() => IsSelected);
}
}
private ObservableCollection<Node> _nodes;
public ObservableCollection<Node> Nodes
{
get { return _nodes; }
set
{
_nodes = value;
RaisePropertyChanged(() => Nodes);
}
}
public static IList<Node> Create()
{
return new List<Node>()
{
new Node()
{
Name = "Activity",
Nodes = new ObservableCollection<Node>()
{
new Node() {Name = "Company",Nodes = new ObservableCollection<Node>(){ new Node() {Name = "Company1",Existing = false}}},
new Node() {Name = "Strategy",Nodes = new ObservableCollection<Node>(){ new Node() {Name = "Strategy1"}}},
new Node() {Name = "Vehicle",Nodes = new ObservableCollection<Node>(){ new Node() {Name = "Vehicle1",Existing = false}}}
}
}
};
}
}
..and my initialization clode:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var viewModel = new ViewModel();
this.DataContext = viewModel;
viewModel.Nodes = new ObservableCollection<Node>(Node.Create());
}
}
I have no clue what is going wrong here, could you please help?
You’re missing the
<i:Interaction.Behaviors>element:replace:
<Spike:MultipleItemSelectionAttachedBehavior AllSelectedItems="{Binding Path=AllSelectedNodes}"/>for:
The problem is that the default content property for the TreeView is its
Itemsproperty, therefore putting that XAML element of the behavior inside of the TreeView element without specifying the<i:Interaction.Behaviors>Attached property, is telling WPF that you want your behavior as an Item in the TreeView, therefore when trying to set itsItemsSourceproperty you recieve the error, because there is already an Item inside of it.