WPF TreeView MultiSelect Behavior - c#

I have written a multi select behavior based on this post:WPF TreeView with Multiple Selection
However my behavior does't seem to work as expected..Can you please help?
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>
<i:Interaction.Behaviors>
<Spike:MultipleItemSelectionAttachedBehavior AllSelectedItems="{Binding Path=AllSelectedNodes}"/>
</i:Interaction.Behaviors>
</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());
}
}
The issue is that, it still selects only one while it's supposed to select multiple while holding back control key.

In AssociatedObject_SelectedItemChanged, how is the selectedItems count ever anything but zero when you just instantiate it or 1 when you add the treeViewItem to it a bit later? Try setting it to the AllSelectedItems property. Also, shouldn't the AllSelectedItems property be a list or IEnumerable of some sort?
if(AllSelectedItems == null)
{
AllSelectedItems = new List<Node>();
}
List<Node> selectedItems = AllSelectedItems;

you are missing the binding of the IsSelected property:
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>

you need to add Selection mode property to treeview like this
SelectionMode="Multiple"

Related

Dynamically changing tabitem via button command not working wfp

Im trying to create a function in a wpf program, where I can select an item in a listview, and press a button and it changes the tabitem and allows me to then edit the item from the listview that was selected. Im having issues with getting the tabitem to change for me.
For the navigation of my app, I have a ViewModelBase, which my AppointmentsViewModel inherits from. Inside the AppointmentsViewVM there is a tabcontrol with 4 items, by clicking each one it loads the requested view/viewmodel for that function.
This is not the only way I've tried to get this to work, Im currently on day 4. I could get the TabIndex to change in the TabControl earlier, but the tab would still not change for me. So I abandoned that and tried the below route (still no luck).
ViewModelBase
namespace MBR2.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public ICommand MainMenuViewDogs_Command { get; set; }
public ICommand MainMenuViewAppointments_Command { get; set; }
private object _SelectedViewModel;
public object SelectedViewModel
{
get { return _SelectedViewModel; }
set
{
_SelectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public ViewModelBase()
{
MainMenuViewDogs_Command = new BaseCommand(OpenDogs);
MainMenuViewAppointments_Command = new BaseCommand(OpenAppointments);
}
private void OpenDogs(object obj)
{
SelectedViewModel = new DogsViewModel();
}
private void OpenAppointments(object obj)
{
SelectedViewModel = new AppointmentsViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool _SelectedIndexView;
public bool SelectedIndexView
{
get { return _SelectedIndexView; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexView");
}
}
private bool _SelectedIndexAdd;
public bool SelectedIndexAdd
{
get { return _SelectedIndexAdd; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexAdd");
}
}
private bool _SelectedIndexEdit;
public bool SelectedIndexEdit
{
get { return _SelectedIndexEdit; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexEdit");
}
}
private bool _SelectedIndexDelete;
public bool SelectedIndexDelete
{
get { return _SelectedIndexDelete; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexDelete");
}
}
}
}
AppointmentsViewModel
{
public class AppointmentsViewModel : ViewModelBase
{
private AppointmentsAddVM _AppointmentsAddVM;
public AppointmentsAddVM AppointmentsAddVM { get { return _AppointmentsAddVM; } }
private AppointmentsEditVM _AppointmentsEditVM;
public AppointmentsEditVM AppointmentsEditVM { get { return _AppointmentsEditVM; } }
private AppointmentsDeleteVM _AppointmentsDeleteVM;
public AppointmentsDeleteVM AppointmentsDeleteVM { get { return _AppointmentsDeleteVM; } }
private AppointmentsViewVM _AppointmentsViewVM;
public AppointmentsViewVM AppointmentsViewVM { get { return _AppointmentsViewVM; } }
public ObservableCollection<object> ViewModelList { get; set; }
public AppointmentsViewModel()
{
this.ViewModelList = new ObservableCollection<object>();
_AppointmentsAddVM = new AppointmentsAddVM();
_AppointmentsEditVM = new AppointmentsEditVM();
_AppointmentsDeleteVM = new AppointmentsDeleteVM();
_AppointmentsViewVM = new AppointmentsViewVM();
this.ViewModelList.Add(_AppointmentsAddVM);
this.ViewModelList.Add(_AppointmentsEditVM);
this.ViewModelList.Add(_AppointmentsDeleteVM);
this.ViewModelList.Add(_AppointmentsViewVM);
}
}
}
AppointmentsView.xaml
<UserControl
x:Class="MBR2.Views.AppointmentsView"
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:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:vms="clr-namespace:MBR2.ViewModels.Appointments"
xmlns:views="clr-namespace:MBR2.Views.Appointments"
xmlns:viewmodels="clr-namespace:MBR2.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:AppointmentsViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vms:AppointmentsViewVM}">
<views:AppointmentsViewView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsAddVM}">
<views:AppointmentsAddView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsDeleteVM}">
<views:AppointmentsDeleteView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsEditVM}">
<views:AppointmentsEditView />
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="Appointments" Width="Auto" Height="Auto">
<DockPanel HorizontalAlignment="Center"
Height="Auto"
LastChildFill="False"
VerticalAlignment="Top"
Width="Auto">
<TabControl x:Name="VMTabControl">
<TabItem x:Name="ViewTab"
TabIndex="0"
Header="View"
IsSelected="{Binding SelectedIndexView}"
Content="{Binding AppointmentsViewVM}"></TabItem>
<TabItem x:Name="AddTab"
TabIndex="1"
Header="Add"
IsSelected="{Binding SelectedIndexAdd}"
Content="{Binding AppointmentsAddVM}"></TabItem>
<TabItem x:Name="EditTab"
TabIndex="2"
Header="Edit"
IsSelected="{Binding SelectedIndexEdit}"
Content="{Binding AppointmentsEditVM}"></TabItem>
<TabItem x:Name="DeleteTab"
TabIndex="3"
Header="Delete"
IsSelected="{Binding SelectedIndexDelete}"
Content="{Binding AppointmentsDeleteVM}"></TabItem>
</TabControl>
</DockPanel>
</Grid>
</UserControl>
And the associated AppointmentsViewVM
namespace MBR2.ViewModels.Appointments
{
public class AppointmentsViewVM : ViewModelBase, INotifyPropertyChanged
{
private List<AppointmentsView_Wrapper> _AppointmentsView;
public List<AppointmentsView_Wrapper> AppointmentsView
{
get { return _AppointmentsView; }
set
{
_AppointmentsView = value;
OnPropertyChanged("AppointmentsView");
}
}
private List<string> _NameColumn = new List<string>();
public List<string> NameColumn
{
get { return _NameColumn; }
set
{
_NameColumn = value;
OnPropertyChanged("NameColumn");
}
}
private List<string> _ApptDateColumn = new List<string>();
public List<string> ApptDateColumn
{
get { return _ApptDateColumn; }
set
{
_ApptDateColumn = value;
OnPropertyChanged("ApptDateColumn");
}
}
private List<string> _ApptTimeColumn = new List<string>();
public List<string> ApptTimeColumn
{
get { return _ApptTimeColumn; }
set
{
_ApptTimeColumn = value;
OnPropertyChanged("ApptTimeColumn");
}
}
private List<string> _ApptVetColumn = new List<string>();
public List<string> ApptVetColumn
{
get { return _ApptVetColumn; }
set
{
_ApptVetColumn = value;
OnPropertyChanged("ApptVetColumn");
}
}
private List<string> _ApptCreatedColumn = new List<string>();
public List<string> ApptCreatedColumn
{
get { return _ApptCreatedColumn; }
set
{
_ApptCreatedColumn = value;
OnPropertyChanged("ApptCreatedColumn");
}
}
private List<int> _ApptIDColumn = new List<int>();
public List<int> ApptIDColumn
{
get { return _ApptIDColumn; }
set
{
_ApptIDColumn = value;
OnPropertyChanged("ApptIDColumn");
}
}
private string _AppointmentEdit_Enabled = "False";
public string AppointmentEdit_Enabled
{
get { return _AppointmentEdit_Enabled; }
set
{
_AppointmentEdit_Enabled = value;
OnPropertyChanged("AppointmentEdit_Enabled");
}
}
private AppointmentsView_Wrapper _ApptIDSelected;
public AppointmentsView_Wrapper ApptIDSelected
{
get { return _ApptIDSelected; }
set
{
AppointmentEdit_Enabled = "True";
_ApptIDSelected = value;
OnPropertyChanged("ApptIDSelected");
}
}
public AppointmentData AppointmentData = new AppointmentData();
public Messaging Messaging = new Messaging();
public ICommand AppointmentsListView_Command => new DelegateCommand<object>(AppointmentsListView_Clicked);
public ICommand EditSelection_Command => new DelegateCommand<object>(EditSelection_Clicked);
public AppointmentsViewVM()
{
BuildPage();
}
public async void BuildPage()
{
AppointmentsView = await AppointmentData.Appointments_GetAll();
foreach(var item in AppointmentsView)
{
ApptIDColumn.Add(item.ApptID);
NameColumn.Add(item.DogName);
ApptDateColumn.Add(item.ApptDate);
ApptTimeColumn.Add(item.ApptTime);
ApptVetColumn.Add(item.ApptVet);
ApptCreatedColumn.Add(item.ApptCreated.ToString("dd/mm/yyyy"));
}
}
public void AppointmentsListView_Clicked(object obj)
{
Messaging.ShowAlert(ApptIDSelected.ApptID.ToString());
}
public void EditSelection_Clicked(object obj)
{
bool result = Messaging.AskQuestion(ApptIDSelected.ApptID.ToString());
if(result)
{
SelectedIndexView = false;
SelectedIndexAdd = false;
SelectedIndexEdit = true;
SelectedIndexDelete = false;
OnPropertyChanged("SelectedIndexView");
OnPropertyChanged("SelectedIndexAdd");
OnPropertyChanged("SelectedIndexEdit");
OnPropertyChanged("SelectedIndexDelete");
}
else
{
Messaging.ShowAlert("no");
}
}
}
}
Here's a minimal reproduction of something where you select in a listbox and that then selects a corresponding tab in a tabcontrol.
This is very minimal but we can perhaps imagine a more sophisticated viewmodel per item in the listbox with name and viewmodel or something.
This is mainwindow.
<Window.Resources>
<DataTemplate DataType="{x:Type local:Avm}">
<local:Aview/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Bvm}">
<local:Bview/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Cvm}">
<local:Cview/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewmodel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding ViewModels}"
x:Name="lb"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ViewModelName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TabControl Grid.Column="1"
ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding ElementName=lb, Path=SelectedItem, Mode=TwoWay}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding ViewModelName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
I have only done 3 views and viewmodels.
Note that the selecteditem of the listbox is bound twoway to the tabcontrol.
I have matching views and viewmodels A, B and C
MainWindowViewModel
public class MainWindowViewmodel : ViewModelBase
{
public ObservableCollection<Object> ViewModels { get; set; } = new ObservableCollection<Object>{
new Avm{ViewModelName="A viewmodel" },
new Bvm{ViewModelName="B viewmodel" },
new Cvm{ViewModelName="C viewmodel" }
};
}
Both the itemssource of listbox and tabcontrol are bound to that collection of viewmodels. Which are, as I mentioned, as simple as you get really.
Viewmodelbase
public class ViewModelBase : INotifyPropertyChanged
{
public string ViewModelName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Avm, Bvm and Cvm just inherit from that.
An example usercontrol view.
<Grid>
<TextBlock Text="{Binding ViewModelName}"/>
</Grid>
</UserControl>
When I spin that up, select and select an item in the listbox the matching tab is selected. And vice versa. Select a tab and it selects the same one in the listbox.

Raise event in "recursive" class

I have an TreeView that is bound to an class like this:
MainWindow.xaml
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary>
<Style x:Key="TreeViewItemStyle" TargetType="TreeViewItem">
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
<Setter Property="IsSelected" Value="{Binding IsInitiallySelected, Mode=OneTime}" />
<Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
</Style>
<HierarchicalDataTemplate x:Key="CheckBoxItemTemplate"
ItemsSource="{Binding Children.Values, Mode=OneTime}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center" />
<TextBlock Text="{Binding Name, StringFormat='({0})'}"
Margin="5,2,2,5"/>
</StackPanel>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Window.Resources>
<TreeView Grid.Column="0"
x:Name="MyTree"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
ItemsSource="{Binding Mode=OneTime}"
ItemTemplate="{StaticResource CheckBoxItemTemplate}"
Margin="5"
FontSize="14"
Padding ="10"
Width ="400"/>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication2
{
/// <summary>
/// Interaktionslogik für MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Foo Root;
public MainWindow()
{
ObjectDataProvider ODP = new ObjectDataProvider();
ODP.MethodName = "CreateFoos";
ODP.ObjectType = typeof(Foo);
DataContext = ODP;
InitializeComponent();
Root = MyTree.Items[0] as Foo;
Root.StatusChanged += Root_StatusChanged;
MyTree.Focus();
}
void Root_StatusChanged(object sender, EventArgs e)
{
Console.WriteLine("Changed!");
}
}
}
Foo.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace WpfApplication2
{
public class Foo : INotifyPropertyChanged
{
public event EventHandler StatusChanged;
bool? _isChecked = false;
public Dictionary<int, Foo> Children { get; set; }
string _Name = "";
Foo _parent;
Foo(string MyName)
{
_Name = MyName;
_isChecked = false;
Children = new Dictionary<int, Foo>();
}
void Initialize()
{
foreach (Foo child in this.Children.Values)
{
child._parent = this;
child.Initialize();
}
}
public static List<Foo> CreateFoos()
{
Foo Base = new Foo("Test");
Base.Children.Add(1, new Foo("Nr 1"));
Base.Children.Add(2, new Foo("Nr 2"));
Base.Children.Add(3, new Foo("Nr 3"));
Base.Children[1].Children.Add(1, new Foo("Nr 1-1"));
Base.Children[1].Children.Add(2, new Foo("Nr 1-2"));
Base.Children[1].Children.Add(3, new Foo("Nr 1-3"));
Base.Children[2].Children.Add(1, new Foo("Nr 2-1"));
Base.Children[2].Children.Add(2, new Foo("Nr 2-2"));
Base.Children[2].Children.Add(3, new Foo("Nr 2-3"));
Base.Children[3].Children.Add(1, new Foo("Nr 3-1"));
Base.Children[3].Children.Add(2, new Foo("Nr 3-2"));
Base.Children[3].Children.Add(3, new Foo("Nr 3-3"));
Base.Initialize();
return new List<Foo> { Base };
}
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public bool? IsChecked
{
get { return _isChecked; }
set { SetIsChecked(value, true, true); }
}
void SetIsChecked(bool? value, bool updateChildren, bool updateParent)
{
if (value == _isChecked)
return;
_isChecked = value;
if (updateChildren && _isChecked.HasValue)
{
foreach (int Key in Children.Keys)
{
Children[Key].SetIsChecked(_isChecked, true, false);
}
}
if (updateParent && _parent != null)
{
_parent.VerifyCheckState();
}
OnPropertyChanged("IsChecked");
EventHandler MyHandler = StatusChanged;
if (null != MyHandler)
{
MyHandler.Invoke(this, EventArgs.Empty);
}
}
void VerifyCheckState()
{
bool? state = null;
bool CheckedFound = false;
bool UncheckedFound = false;
bool NullFound = false;
foreach (int Key in Children.Keys)
{
if (Children[Key].IsChecked == true)
{
CheckedFound = true;
}
else if (Children[Key].IsChecked == false)
{
UncheckedFound = true;
}
else
{
NullFound = true;
}
}
if (CheckedFound && !UncheckedFound && !NullFound)
{
state = true;
}
else if (!CheckedFound && UncheckedFound && !NullFound)
{
state = false;
}
SetIsChecked(state, false, true);
}
void OnPropertyChanged(string prop)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
This is the result:
My Prolem now is, that the Event only fires when the status of the root node changes. But when I check/uncheck only Sub-Items the root-node not always changes, so I don't get this changes.
How do I have to change my code to get all changes, but also don't fire multiple events, because when I change one item multile other items (parent and children) can change also.

UserControl doesnt display collection from binding

I've been trying to create a user control, using this article as my start.
My end result would be supplying a collection and using the TextBox to filter the collection, displaying the filtered result in the ListView.
Problem is, it seems that my binding isn't working correctly. The collection isn't being passed through to the UserControl. Bear in mind, this is my first shot at creating a UserControl. Why would this happen?
SearchList.Xaml
<UserControl x:Class="CreatingControls.SearchList"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CreatingControls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Loaded="SearchList_OnLoaded">
<StackPanel>
<TextBox Name="txtFilter"
TextChanged="txtFilter_TextChanged"
Margin="5" FontSize="20" />
<TextBox IsEnabled="False" Text="Search:"
FontSize="16" BorderThickness="0" />
<ListView Name="listView"
SelectedValue="{Binding Path=SelectedItem}"
DisplayMemberPath="Value"
BorderBrush="LightGray" Margin="5" />
</StackPanel>
SearchList.xaml.cs
public partial class SearchList : UserControl
{
#region AllItems
public List<Collection> AllItems
{
get { return (List<Collection>)GetValue(AllItemsProperty); }
set { SetValue(AllItemsProperty, value); }
}
public static readonly DependencyProperty AllItemsProperty =
DependencyProperty.Register("AllItems", typeof(List<Collection>),
typeof(SearchList), new PropertyMetadata(default(List<Collection>)));
#endregion
#region SelectedItem
public Collection SelectedItem
{
get { return (Collection)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(Collection),
typeof(SearchList), new PropertyMetadata(default(Collection)));
#endregion
public SearchList()
{
InitializeComponent();
listView.ItemsSource = AllItems;
if (listView.ItemsSource != null)
{
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(listView.ItemsSource);
view.Filter = ItemsFilter;
}
}
private void SearchList_OnLoaded(object sender, RoutedEventArgs e)
{
if (listView.ItemsSource == null)
return;
CollectionViewSource.GetDefaultView(listView.ItemsSource).Filter = ItemsFilter;
}
private void txtFilter_TextChanged(object sender, TextChangedEventArgs e)
{
if (listView.ItemsSource == null)
return;
CollectionViewSource.GetDefaultView(listView.ItemsSource).Refresh();
}
private bool ItemsFilter(object item)
{
if (listView.ItemsSource == null)
return false;
if (String.IsNullOrEmpty(txtFilter.Text))
return true;
var collectionItem = (Collection)item;
return (collectionItem.Value.StartsWith(txtFilter.Text, StringComparison.OrdinalIgnoreCase) || collectionItem.Value.StartsWith(txtFilter.Text, StringComparison.OrdinalIgnoreCase));
}
}
Collection.cs
public class Collection : INotifyPropertyChanged
{
private string _id;
public string Id
{
get { return _id; }
set { SetField(ref _id, value, "Id"); }
}
private string _value;
public string Value
{
get { return _value; }
set { SetField(ref _value, value, "Value"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
MainWindow.xaml (Window that calls the UserControl created)
<Window x:Class="CreatingControls.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:uControls="clr-namespace:CreatingControls"
mc:Ignorable="d"
d:DataContext="Models."
Title="MainWindow" >
<Grid>
<uControls:SearchList x:Name="slist" />
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static List<Collection> items { get; set; }
public User SelectedUser { get; set; }
public MainWindow()
{
InitializeComponent();
items = new List<Collection>();
items.Add(new Collection { Id = "1", Value = "A" });
items.Add(new Collection { Id = "2", Value = "B" });
items.Add(new Collection { Id = "3", Value = "C" });
items.Add(new Collection { Id = "4", Value = "D" });
items.Add(new Collection { Id = "5", Value = "E" });
items.Add(new Collection { Id = "6", Value = "F" });
items.Add(new Collection { Id = "7", Value = "G" });
items.Add(new Collection { Id = "8", Value = "H" });
slist.AllItems = items;
}
}
You are assigning
listView.ItemsSource = AllItems;
in the SearchList constructor. Later, in the MainWindow constructor, you do
slist.AllItems = items;
Now you seem to be under the impression that listView.ItemsSource magically holds a reference to the items collection of your MainWindow. That is not the case.
Instead of the direct assignment, use data binding. In the SearchList XAML, write this:
<ListView ItemsSource="{Binding AllItems,
RelativeSource={RelativeSource AncestorType=UserControl}}" .../>
and remove listView.ItemsSource = AllItems from the SearchList constructor.

Why invoking command when `TreeViewItem` is expanded doesn't work?

I'm trying to invoke command when TreeViewItem is expanded as explained here, but for some reason it doesn't work. I think it's because of HierarchicalDataTemplate, but I don't know why.
Does any one have an idea what's the problem?
XAML
<Window x:Class="MyProject.MainWindow"
...
xmlns:local="clr-namespace:MyProject"
xmlns:bindTreeViewExpand="clr-namespace:MyProject"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TreeView ItemsSource="{Binding RootFolders}">
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="bindTreeViewExpand:Behaviours.ExpandingBehaviour" Value="{Binding ExpandingCommand}"/>
</Style>
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" DataType="{x:Type local:DriveFolder}">
<TreeViewItem Header="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
</Window>
BEHAVIOURS
namespace GooglePhotoPermissions
{
public static class Behaviours
{
#region ExpandingBehaviour (Attached DependencyProperty)
public static readonly DependencyProperty ExpandingBehaviourProperty =
DependencyProperty.RegisterAttached("ExpandingBehaviour", typeof(ICommand), typeof(Behaviours),
new PropertyMetadata(OnExpandingBehaviourChanged));
public static void SetExpandingBehaviour(DependencyObject o, ICommand value)
{
o.SetValue(ExpandingBehaviourProperty, value);
}
public static ICommand GetExpandingBehaviour(DependencyObject o)
{
return (ICommand)o.GetValue(ExpandingBehaviourProperty);
}
private static void OnExpandingBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeViewItem tvi = d as TreeViewItem;
if (tvi != null)
{
ICommand ic = e.NewValue as ICommand;
if (ic != null)
{
tvi.Expanded += (s, a) =>
{
if (ic.CanExecute(a))
{
ic.Execute(a);
}
a.Handled = true;
};
}
}
}
#endregion
}
}
ViewModel
namespace MyProject
{
public class DriveFile
{
public string Name { get; set; }
public string Id { get; set; }
public bool IsFolder { get; protected set; }
public DriveFile()
{
IsFolder = false;
}
}
public class DriveFolder : DriveFile
{
public List<DriveFile> Children { get; set; }
public DriveFolder()
{
IsFolder = true;
Children = new List<DriveFile>();
}
}
public class DriveViewModel
{
public IList<DriveFolder> RootFolders
{
get
{
return GetRootFolders();
}
}
private ICommand _expandingCommand;
public ICommand ExpandingCommand
{
get
{
if (_expandingCommand == null)
{
_expandingCommand = new RelayCommand(Foo);
}
return _expandingCommand;
}
}
private DriveService _driveService;
private IList<DriveFolder> GetRootFolders()
{
...
}
}
}
a
Your binding is wrong.
You define a binding in a style that applies to each TreeViewItem. In this binding, the source is the DataContext of each TreeViewItem itself. That would be a DriveFolder or a DriveFile object.
Of course, these objects have no ExpandingCommand property, so your binding just fails.
Change your binding in such a manner that the DataContext of the TreeView is used as source (to access your view model and its command). You can use ElementName or RelativeSource, e.g. like this:
<Setter Property="bindTreeViewExpand:Behaviours.ExpandingBehaviour"
Value="{Binding DataContext.ExpandingCommand, RelativeSource={RelativeSource AncestorType=TreeView}}"/>

Looking for an object graph tree-view control for WPF

I'm trying to find code or a pre-packaged control that takes an object graph and displays the public properties and values of the properties (recursively) in a TreeView. Even a naive implementation is ok, I just need something to start with.
The solution must be in WPF, not winforms or com, etc...
So I took parts from Chris Taylor's example and the structure of a codeproject article and merged them into this:
TreeView xaml:
<TreeView Name="tvObjectGraph" ItemsSource="{Binding FirstGeneration}" Margin="12,41,12,12" FontSize="13" FontFamily="Consolas">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Name}" Grid.Column="0" Grid.Row="0" Padding="2,0" />
<TextBlock Text="{Binding Type}" Grid.Column="1" Grid.Row="0" Padding="2,0" />
<TextBlock Text="{Binding Value}" Grid.Column="2" Grid.Row="0" Padding="2,0" />
</Grid>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Wire-up code
void DisplayObjectGraph(object graph)
{
var hierarchy = new ObjectViewModelHierarchy(graph);
tvObjectGraph.DataContext = hierarchy;
}
ObjectViewModel.cs:
public class ObjectViewModel : INotifyPropertyChanged
{
ReadOnlyCollection<ObjectViewModel> _children;
readonly ObjectViewModel _parent;
readonly object _object;
readonly PropertyInfo _info;
readonly Type _type;
bool _isExpanded;
bool _isSelected;
public ObjectViewModel(object obj)
: this(obj, null, null)
{
}
ObjectViewModel(object obj, PropertyInfo info, ObjectViewModel parent)
{
_object = obj;
_info = info;
if (_object != null)
{
_type = obj.GetType();
if (!IsPrintableType(_type))
{
// load the _children object with an empty collection to allow the + expander to be shown
_children = new ReadOnlyCollection<ObjectViewModel>(new ObjectViewModel[] { new ObjectViewModel(null) });
}
}
_parent = parent;
}
public void LoadChildren()
{
if (_object != null)
{
// exclude value types and strings from listing child members
if (!IsPrintableType(_type))
{
// the public properties of this object are its children
var children = _type.GetProperties()
.Where(p => !p.GetIndexParameters().Any()) // exclude indexed parameters for now
.Select(p => new ObjectViewModel(p.GetValue(_object, null), p, this))
.ToList();
// if this is a collection type, add the contained items to the children
var collection = _object as IEnumerable;
if (collection != null)
{
foreach (var item in collection)
{
children.Add(new ObjectViewModel(item, null, this)); // todo: add something to view the index value
}
}
_children = new ReadOnlyCollection<ObjectViewModel>(children);
this.OnPropertyChanged("Children");
}
}
}
/// <summary>
/// Gets a value indicating if the object graph can display this type without enumerating its children
/// </summary>
static bool IsPrintableType(Type type)
{
return type != null && (
type.IsPrimitive ||
type.IsAssignableFrom(typeof(string)) ||
type.IsEnum);
}
public ObjectViewModel Parent
{
get { return _parent; }
}
public PropertyInfo Info
{
get { return _info; }
}
public ReadOnlyCollection<ObjectViewModel> Children
{
get { return _children; }
}
public string Type
{
get
{
var type = string.Empty;
if (_object != null)
{
type = string.Format("({0})", _type.Name);
}
else
{
if (_info != null)
{
type = string.Format("({0})", _info.PropertyType.Name);
}
}
return type;
}
}
public string Name
{
get
{
var name = string.Empty;
if (_info != null)
{
name = _info.Name;
}
return name;
}
}
public string Value
{
get
{
var value = string.Empty;
if (_object != null)
{
if (IsPrintableType(_type))
{
value = _object.ToString();
}
}
else
{
value = "<null>";
}
return value;
}
}
#region Presentation Members
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (_isExpanded != value)
{
_isExpanded = value;
if (_isExpanded)
{
LoadChildren();
}
this.OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (_isExpanded && _parent != null)
{
_parent.IsExpanded = true;
}
}
}
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
this.OnPropertyChanged("IsSelected");
}
}
}
public bool NameContains(string text)
{
if (String.IsNullOrEmpty(text) || String.IsNullOrEmpty(Name))
{
return false;
}
return Name.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1;
}
public bool ValueContains(string text)
{
if (String.IsNullOrEmpty(text) || String.IsNullOrEmpty(Value))
{
return false;
}
return Value.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1;
}
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
ObjectViewModelHierarchy.cs:
public class ObjectViewModelHierarchy
{
readonly ReadOnlyCollection<ObjectViewModel> _firstGeneration;
readonly ObjectViewModel _rootObject;
public ObjectViewModelHierarchy(object rootObject)
{
_rootObject = new ObjectViewModel(rootObject);
_firstGeneration = new ReadOnlyCollection<ObjectViewModel>(new ObjectViewModel[] { _rootObject });
}
public ReadOnlyCollection<ObjectViewModel> FirstGeneration
{
get { return _firstGeneration; }
}
}
Well, this is probably a little more naive than you where hoping for, but it could possibly give you a starting point. It could do with some refactoring, but it was literally done in 15 min so take it for what it is, which is not well tested or using any WPF fancies for that matter.
First a simple UserControl which just hosts a TreeView
<UserControl x:Class="ObjectBrowser.PropertyTree"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TreeView Name="treeView1" TreeViewItem.Expanded="treeView1_Expanded" />
</Grid>
</UserControl>
The code behind for this will have just one property called ObjectGraph, this is set to the instance of the object you want to browse.
The tree only gets loaded with the first level of properties each node has the format PropertyName : Value or PropertyName : Type, if the property is a primitive type (see the IsPrimitive function), then the value is shown, otherwise an empty string is added as the child node. Adding the empty string indicates to the user that the node can ge expanded.
When the node is exanded a quick check is done to see if the first child is an empty string, if it is then the node is cleared and the properties for that node loaded into the tree.
So this basically builds the tree up as the node are expanded. This makes like easier for two reasons
1- No need to perform recursion
2- No need to detect cyclic references which will expand to eternity or some resource is depleted, which ever comes first.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Reflection;
namespace ObjectBrowser
{
public partial class PropertyTree : UserControl
{
public PropertyTree()
{
InitializeComponent();
}
private void treeView1_Expanded(object sender, RoutedEventArgs e)
{
TreeViewItem item = e.OriginalSource as TreeViewItem;
if (item.Items.Count == 1 && item.Items[0].ToString() == string.Empty)
{
LoadGraph(item.Items, item.Tag);
}
}
public object ObjectGraph
{
get { return (object)GetValue(ObjectGraphProperty); }
set { SetValue(ObjectGraphProperty, value); }
}
public static readonly DependencyProperty ObjectGraphProperty =
DependencyProperty.Register("ObjectGraph", typeof(object), typeof(PropertyTree),
new UIPropertyMetadata(0, OnObjectGraphPropertyChanged));
private static void OnObjectGraphPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
PropertyTree control = source as PropertyTree;
if (control != null)
{
control.OnObjectGraphChanged(source, EventArgs.Empty);
}
}
protected virtual void OnObjectGraphChanged(object sender, EventArgs e)
{
LoadGraph(treeView1.Items, ObjectGraph);
}
private void LoadGraph(ItemCollection nodeItems, object instance)
{
nodeItems.Clear();
if (instance == null) return;
Type instanceType = instance.GetType();
foreach (PropertyInfo pi in instanceType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
object propertyValue =pi.GetValue(instance, null);
TreeViewItem item = new TreeViewItem();
item.Header = BuildItemText(instance, pi, propertyValue);
if (!IsPrimitive(pi) && propertyValue != null)
{
item.Items.Add(string.Empty);
item.Tag = propertyValue;
}
nodeItems.Add(item);
}
}
private string BuildItemText(object instance, PropertyInfo pi, object value)
{
string s = string.Empty;
if (value == null)
{
s = "<null>";
}
else if (IsPrimitive(pi))
{
s = value.ToString();
}
else
{
s = pi.PropertyType.Name;
}
return pi.Name + " : " + s;
}
private bool IsPrimitive(PropertyInfo pi)
{
return pi.PropertyType.IsPrimitive || typeof(string) == pi.PropertyType;
}
}
}
Using the control is quite simple. Here I will just put the control on Form and then set the ObjectGraph to an instance of an object, I arbitrarily chose XmlDataProvider.
XAML
<Window x:Class="ObjectBrowser.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" xmlns:my="clr-namespace:ObjectBrowser" Loaded="Window_Loaded">
<Grid>
<my:PropertyTree x:Name="propertyTree1" />
</Grid>
</Window>
The code behind
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace ObjectBrowser
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var o = new XmlDataProvider();
o.Source = new Uri("http://www.stackoverflow.com");
propertyTree1.ObjectGraph = o;
}
}
}
Of course this would still need a lot of work, special handling for types like arrays possibly a mechanism to handle custom views to special types etc.

Categories

Resources