Add a checkboxes to existing Treeview in WPF - wpf

I have an existing treeview in WPF in which I would like to add checkboxes
Here the code
I have a class Person which contains all the structure
Person.cs
public class Person
{
readonly List<Person> _children = new List<Person>();
public IList<Person> Children
{
get { return _children; }
}
public string Name { get; set; }
}
As I read in some other posts, I use ViewModel
PersonViewModel.cs
public class PersonViewModel : INotifyPropertyChanged
{
#region Data
readonly ReadOnlyCollection<PersonViewModel> _children;
readonly PersonViewModel _parent;
readonly Person _person;
bool _isExpanded=true;
bool _isSelected;
#endregion Data
#region Constructors
public PersonViewModel(Person person): this(person, null)
{
}
private PersonViewModel(Person person, PersonViewModel parent)
{
_person = person;
_parent = parent;
_children = new ReadOnlyCollection<PersonViewModel>(
(from child in _person.Children
select new PersonViewModel(child, this))
.ToList<PersonViewModel>());
}
#endregion Constructors
#region Person Properties
public ReadOnlyCollection<PersonViewModel> Children
{
get { return _children; }
}
public string Name
{
get { return _person.Name; }
}
#endregion Person Properties
#region Presentation Members
#region IsExpanded
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is expanded.
/// </summary>
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (value != _isExpanded)
{
_isExpanded = value;
OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (_isExpanded && _parent != null)
_parent.IsExpanded = true;
}
}
#endregion IsExpanded
#region IsSelected
/// <summary>
/// Gets/sets whether the TreeViewItem
/// associated with this object is selected.
/// </summary>
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
#endregion IsSelected
#region NameContainsText
public bool NameContainsText(string text)
{
if (String.IsNullOrEmpty(text) || String.IsNullOrEmpty(this.Name))
return false;
return Name.IndexOf(text, StringComparison.InvariantCultureIgnoreCase) > -1;
}
#endregion NameContainsText
#region Parent
public PersonViewModel Parent
{
get { return _parent; }
}
#endregion Parent
#endregion Presentation Members
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged Members
}
The family tree ViewModel
FamilyTreeViewModel.cs
public class FamilyTreeViewModel
{
#region Data
readonly PersonViewModel _rootPerson;
#endregion Data
#region Constructor
public FamilyTreeViewModel(Person rootPerson)
{
_rootPerson = new PersonViewModel(rootPerson);
FirstGeneration = new ReadOnlyCollection<PersonViewModel>(
new PersonViewModel[]
{
_rootPerson
});
}
#endregion Constructor
#region Properties
#region FirstGeneration
/// <summary>
/// Returns a read-only collection containing the first person
/// in the family tree, to which the TreeView can bind.
/// </summary>
public ReadOnlyCollection<PersonViewModel> FirstGeneration { get; }
#endregion FirstGeneration
#endregion Properties
}
The xaml code
MainWindow.xaml
<TreeView ItemsSource="{Binding FirstGeneration}">
<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}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
readonly FamilyTreeViewModel _familyTree;
public MainWindow()
{
InitializeComponent();
Person rootPerson = new Person
{
Name="Application Architect Right",
Children =
{
new Person
{
Name="Generate"
},
new Person
{
Name="Instances rights",
Children =
{
new Person
{
Name = "Create"
},
new Person
{
Name = "Modify"
},
new Person
{
Name = "Delete"
},
new Person
{
Name = "Exceptions Management"
}
}
},
new Person
{
Name="Templates rights",
Children =
{
new Person
{
Name = "Create"
},
new Person
{
Name = "Modify"
},
new Person
{
Name = "Delete"
}
}
},
new Person
{
Name="Parameters rights",
Children =
{
new Person
{
Name = "Create"
},
new Person
{
Name = "Modify"
},
new Person
{
Name = "Delete"
}
}
},
}
};
// Create UI-friendly wrappers around the
// raw data objects (i.e. the view-model).
_familyTree = new FamilyTreeViewModel(rootPerson);
// Let the UI bind to the view-model.
DataContext = _familyTree;
}
}
Can someone can help me?
Thanks in advance

I'm not sure I fully understand your question, but have you tried editing your XAML like this?
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation=Horizontal>
<TextBlock Text="{Binding Name}" />
<Checkbox IsChecked="{Binding IsSelected} />
</StackPanel>
</HierarchicalDataTemplate>
Since there is no checkbox by default in a treeview in wpf, editing the template of the items to add a checkbox is the way to go.
Since the checkbox is binded to the IsSelected property of your PersonViewModel, you could do something like this if you want to update the selection of the childs
public bool IsSelected
{
...
set
{
if (value != _isSelected)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
this.UpdateChildSelection();
}
}
}
private void UpdateChildSelection()
{
foreach(var child in Children)
{
child.IsSelected = this.IsSelected;
}
}

This is my base ViewModel class for TreeView items, which includes cascading checking.
public class perTreeViewItemViewModelBase : perViewModelBase
{
// a dummy item used in lazy loading mode, ensuring that each node has at least one child so that the expand button is shown
private static perTreeViewItemViewModelBase LoadingDataItem { get; }
static perTreeViewItemViewModelBase()
{
LoadingDataItem = new perTreeViewItemViewModelBase { Caption = "Loading Data ..." };
}
private readonly perObservableCollection<perTreeViewItemViewModelBase> _childrenList = new perObservableCollection<perTreeViewItemViewModelBase>();
public perTreeViewItemViewModelBase(bool addLoadingDataItem = false)
{
if (addLoadingDataItem)
_childrenList.Add(LoadingDataItem);
}
private string _caption;
public string Caption
{
get { return _caption; }
set { Set(nameof(Caption), ref _caption, value); }
}
public void ClearChildren()
{
_childrenList.Clear();
}
public void AddChild(perTreeViewItemViewModelBase child)
{
if (_childrenList.Any() && _childrenList.First() == LoadingDataItem)
ClearChildren();
_childrenList.Add(child);
SetChildPropertiesFromParent(child);
}
protected void SetChildPropertiesFromParent(perTreeViewItemViewModelBase child)
{
child.Parent = this;
if (IsChecked.GetValueOrDefault())
child.IsChecked = true;
}
public void AddChildren(IEnumerable<perTreeViewItemViewModelBase> children)
{
foreach (var child in children)
AddChild(child);
}
protected perTreeViewItemViewModelBase Parent { get; private set; }
private bool? _isChecked = false;
public bool? IsChecked
{
get { return _isChecked; }
set
{
if (Set(nameof(IsChecked), ref _isChecked, value))
{
foreach (var child in Children)
if (child.IsEnabled)
child.SetIsCheckedIncludingChildren(value);
SetParentIsChecked();
}
}
}
private bool _isExpanded;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
if (!Set(nameof(IsExpanded), ref _isExpanded, value) || IsInitialised || IsInitialising)
return;
var unused = InitialiseAsync();
}
}
private bool _isEnabled = true;
public bool IsEnabled
{
get { return _isEnabled; }
set { Set(nameof(IsEnabled), ref _isEnabled, value); }
}
public bool IsInitialising { get; private set; }
public bool IsInitialised { get; private set; }
public async Task InitialiseAsync()
{
if (IsInitialised || IsInitialising)
return;
IsInitialising = true;
await InitialiseChildrenAsync().ConfigureAwait(false);
foreach (var child in InitialisedChildren)
SetChildPropertiesFromParent(child);
IsInitialised = true;
RaisePropertyChanged(nameof(Children));
}
protected virtual Task InitialiseChildrenAsync()
{
return Task.CompletedTask;
}
public IEnumerable<perTreeViewItemViewModelBase> Children => IsInitialised
? InitialisedChildren
: _childrenList;
// override this as required in descendent classes
// e.g. if Children is a union of multiple child item collections which are populated in InitialiseChildrenAsync()
protected virtual IEnumerable<perTreeViewItemViewModelBase> InitialisedChildren => _childrenList;
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
// ensure that all ancestor items are expanded, so this item will be visible
if (value)
{
var parent = Parent;
while (parent != null)
{
parent.IsExpanded = true;
parent = parent.Parent;
}
}
if (_isSelected == value)
return;
// use DispatcherPriority.ContextIdle so that we wait for any children of newly expanded items to be fully created in the
// parent TreeView, before setting IsSelected for this item (which will scroll it into view - see perTreeViewItemHelper)
perDispatcherHelper.CheckBeginInvokeOnUI(() => Set(nameof(IsSelected), ref _isSelected, value), DispatcherPriority.ContextIdle);
// note that by rule, a TreeView can only have one selected item, but this is handled automatically by
// the control - we aren't required to manually unselect the previously selected item.
}
}
private void SetIsCheckedIncludingChildren(bool? value)
{
_isChecked = value;
RaisePropertyChanged(nameof(IsChecked));
foreach (var child in Children)
if (child.IsEnabled)
child.SetIsCheckedIncludingChildren(value);
}
private void SetIsCheckedThisItemOnly(bool? value)
{
_isChecked = value;
RaisePropertyChanged(nameof(IsChecked));
}
private void SetParentIsChecked()
{
var parent = Parent;
while (parent != null)
{
var hasIndeterminateChild = parent.Children.Any(c => c.IsEnabled && !c.IsChecked.HasValue);
if (hasIndeterminateChild)
parent.SetIsCheckedThisItemOnly(null);
else
{
var hasSelectedChild = parent.Children.Any(c => c.IsEnabled && c.IsChecked.GetValueOrDefault());
var hasUnselectedChild = parent.Children.Any(c => c.IsEnabled && !c.IsChecked.GetValueOrDefault());
if (hasUnselectedChild && hasSelectedChild)
parent.SetIsCheckedThisItemOnly(null);
else
parent.SetIsCheckedThisItemOnly(hasSelectedChild);
}
parent = parent.Parent;
}
}
public override string ToString()
{
return Caption;
}
}
For more details and an example of its usage, see my recent blog post.

Related

Avalonia.UI ComboBox, no DisplayMemberPath or SelectedValuePath. TemplatedControl

Avalonia.UI ComboBox doesn't have SelectedValuePath & DisplayMemberPath and I'm not skilled enough to implement them.
I have experimented with a TemplatedControl as a work-around because I'm using a TemplatedControl anyway and I don't want to have extra Properties on the ViewModel to handle it.
What I'm trying to archive is to show the items in the ComboBox by DisplayMember but loading/saving by Id. In this test case by Project.ManagerId. It's working but it would be nice not to have to hard-code the Type in that TemplatedControl code behind, like I do right now with "LookupItem". It should be possible to get around that by setting x:DataType="viewModels:LookupItem" or something.
Any ideas?
I have removed most not relevant code.
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:ChangeTrackingHeaderControls.Controls">
<Design.PreviewWith>
<controls:ChangeTrackingHeaderComboBox />
</Design.PreviewWith>
<Style Selector="controls|ChangeTrackingHeaderComboBox">
<!-- Set Defaults -->
<Setter Property="Template">
<ControlTemplate>
<ComboBox Items="{TemplateBinding Items, Mode=OneWay}"
SelectedItem="{TemplateBinding SelectedItem, Mode=TwoWay}"
SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}"
ItemTemplate="{TemplateBinding ItemTemplate}"
HorizontalAlignment="Stretch" />
</ControlTemplate>
</Setter>
</Style>
</Styles>
public class ChangeTrackingHeaderComboBox : TemplatedControl
{
private object? _selectedItem;
private int? _selectedValue;
public static readonly DirectProperty<ChangeTrackingHeaderComboBox, object?> SelectedItemProperty =
AvaloniaProperty.RegisterDirect<ChangeTrackingHeaderComboBox, object?>(nameof(SelectedItem),
o => o.SelectedItem,
(o, v) => o.SelectedItem = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
public static readonly DirectProperty<ChangeTrackingHeaderComboBox, int?> SelectedValueProperty =
AvaloniaProperty.RegisterDirect<ChangeTrackingHeaderComboBox, int?>(nameof(SelectedValue),
o => o.SelectedValue,
(o, v) => o.SelectedValue = v,
defaultBindingMode: BindingMode.TwoWay,
enableDataValidation: true);
public static readonly StyledProperty<IEnumerable> ItemsProperty = AvaloniaProperty
.Register<ChangeTrackingHeaderComboBox, IEnumerable>(nameof(Items));
public static readonly StyledProperty<int> SelectedIndexProperty = AvaloniaProperty
.Register<ChangeTrackingHeaderComboBox, int>(nameof(SelectedIndex));
public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty = AvaloniaProperty
.Register<ChangeTrackingHeaderComboBox, IDataTemplate>(nameof(ItemTemplate));
public object? SelectedItem
{
get => _selectedItem;
set
{
SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
OnSelectedItemChanged();
}
}
public int? SelectedValue
{
get => _selectedValue;
set
{
SetAndRaise(SelectedValueProperty, ref _selectedValue, value);
OnSelectedValueChanged();
}
}
public IEnumerable Items
{
get => GetValue(ItemsProperty);
set => SetValue(ItemsProperty, value);
}
public int SelectedIndex
{
get => GetValue(SelectedIndexProperty);
set => SetValue(SelectedIndexProperty, value);
}
public IDataTemplate ItemTemplate
{
get => GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
{
base.UpdateDataValidation(property, state, error);
if (property == SelectedItemProperty)
{
DataValidationErrors.SetError(this, error);
}
if (property == SelectedValueProperty)
{
DataValidationErrors.SetError(this, error);
}
}
private void OnSelectedItemChanged()
{
// LookupItem should not be hard coded
if (SelectedItem is LookupItem selectedItem)
{
if (SelectedValue == selectedItem.Id) return;
if (Items is IEnumerable<LookupItem> items)
{
var selectedItemDisplayMember = selectedItem.DisplayMember;
SelectedValue = items.SingleOrDefault(x => x.DisplayMember == selectedItemDisplayMember).Id;
}
}
}
private void OnSelectedValueChanged()
{
// LookupItem should not be hard coded
if (Items is IEnumerable<LookupItem> items)
{
if (SelectedItem == null)
{
SelectedItem = items.SingleOrDefault(x => x.Id == SelectedValue);
return;
}
if (SelectedItem is LookupItem selectedItem)
{
if (selectedItem.Id == SelectedValue) return;
SelectedItem = items.SingleOrDefault(x => x.Id == SelectedValue);
}
}
}
}
// App.axaml
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:ChangeTrackingHeaderControls.Controls"
xmlns:viewModels="clr-namespace:ChangeTrackingHeaderControls.ViewModels"
x:Class="ChangeTrackingHeaderControls.App">
<Application.Resources>
<DataTemplate x:Key="LookupItemItemTemplate" x:DataType="viewModels:LookupItem">
<ComboBoxItem Content="{CompiledBinding DisplayMember}"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center" />
</DataTemplate>
</Application.Resources>
<Application.Styles>
<FluentTheme Mode="Light" />
<StyleInclude Source="/Controls/ChangeTrackingHeaderComboBox.axaml" />
</Application.Styles>
</Application>`
// MainWindow
<controls:ChangeTrackingHeaderComboBox Items="{CompiledBinding Managers}"
SelectedValue="{CompiledBinding Project.ManagerId}"
ItemTemplate="{StaticResource LookupItemItemTemplate}" />
// ViewModel
public sealed class MainViewModel : ViewModelBase
{
private Project _project;
public MainViewModel()
{
Managers = new ObservableCollection<LookupItem>();
// Simulating loading Managers from database
for (var i = 1; i < 12; i++)
{
Managers.Add(new LookupItem
{
Id = i,
DisplayMember = $"Manager {i}"
});
}
// One single Project loaded from database
Project = new Project
{
Id = 1,
Name = "Project 1",
ManagerId = Managers.LastOrDefault()?.Id
};
}
public ObservableCollection<LookupItem> Managers { get; }
public Project Project
{
get => _project;
private set
{
if (value == _project) return;
_project = value;
OnPropertyChanged();
}
}
}
// Models
public sealed class Project
{
public int Id { get; set; }
public string Name { get; set; }
public int? ManagerId { get; set; }
}
public sealed class LookupItem
{
public int Id { get; init; }
public string DisplayMember { get; init; }
}```
I have tried to use x:DataType="viewModels:LookupItem" and also implemented StyledProperty<Type> but no succcess.
On the ViewModel instances bound to the ComboBox's Items property, override ToString() and return the value you want displayed in the open drop down.

WPF DataGrid : how to bind an object to reflect the item whose row is checked

I have a datagrid populated with elements and a checkbox for each element.
I'm looking for a way to have an object in my ViewModel be whichever element currently has its checkbox checked.
Here is my XAML so far :
<Window x:Class="fun_with_DataGridCheckBoxColumns.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:fun_with_DataGridCheckBoxColumns"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top">
<Label Content="Chosen One : " />
<Label Content="{Binding ChosenOne.Name, Mode=OneWay}" />
</StackPanel>
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID, Mode=OneWay}" IsReadOnly="True"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name, Mode=OneWay}" IsReadOnly="True"/>
<DataGridCheckBoxColumn Header="Is Chosen"/>
</DataGrid.Columns>
</DataGrid>
</DockPanel>
and my CS :
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace fun_with_DataGridCheckBoxColumns
{
public partial class MainWindow : Window
{
public Person ChosenOne { get; set; }
public MainWindow()
{
InitializeComponent();
DataContext = new Viewmodel();
}
}
public class Viewmodel : INotifyPropertyChanged
{
public ObservableCollection<Person> People { get; private set; }
private Person _chosenOne = null;
public Person ChosenOne
{
get
{
if (_chosenOne == null) { return new Person { Name = "Does Not Exist" }; }
else return _chosenOne;
}
set
{
_chosenOne = value;
NotifyPropertyChanged("ChosenOne");
}
}
public Viewmodel()
{
People = new ObservableCollection<Person>
{
new Person { Name = "John" },
new Person { Name = "Marie" },
new Person { Name = "Bob" },
new Person { Name = "Sarah" }
};
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Person
{
private static int person_quantity = 0;
private int _id = ++person_quantity;
public int ID { get { return _id; } }
public string Name { get; set; }
}
}
Here is the behavior I am looking for :
ChosenOne in ViewModel becomes whichever Person has its checkbox checked
When a checkbox is checked, all others are unchecked
If no checkboxes are checked, sets ChosenOne to null
Basically it is the same behavior as if I had put this in the DataGrid (XAML) :
SelectedItem="{Binding ChosenOne, Mode=TwoWay}"
But in my case ChosenOne can not be the SelectedItem of the datagrid since I need SelectedItem for something else, and I have to use checkboxes for Company reasons.
I have not found how to simulate this "SelectedItem" logic with checkboxes.
I know I could put a "bool IsChosen" property in my Person class and bind the checkbox to it, but I would really rather avoid this. It will be my solution if all else fails.
Thank you.
An alternative would be to wrap your object with something that supports the checking.
Source
using System.ComponentModel;
namespace Jarloo
{
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
public CheckedListItem()
{}
public CheckedListItem(T item, bool isChecked=false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
}
Add an IsChecked property to the Person class and implement the INotifyPropertyChanged interface:
public class Person : INotifyPropertyChanged
{
private static int person_quantity = 0;
private int _id = ++person_quantity;
public int ID { get { return _id; } }
public string Name { get; set; }
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set { _isChecked = value; NotifyPropertyChanged("IsChecked"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Bind the DataGridCheckBoxColumn to this property:
<DataGridCheckBoxColumn Header="Is Chosen" Binding="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged}"/>
You can then handle the logic in your view model class. This should make sure that only a single Person is selected at a time:
public class Viewmodel : INotifyPropertyChanged
{
public ObservableCollection<Person> People { get; private set; }
private Person _chosenOne = null;
public Person ChosenOne
{
get
{
if (_chosenOne == null) { return new Person { Name = "Does Not Exist" }; }
else return _chosenOne;
}
set
{
_chosenOne = value;
NotifyPropertyChanged("ChosenOne");
}
}
public Viewmodel()
{
People = new ObservableCollection<Person>
{
new Person { Name = "John" },
new Person { Name = "Marie" },
new Person { Name = "Bob" },
new Person { Name = "Sarah" }
};
foreach(Person p in People)
p.PropertyChanged += P_PropertyChanged;
}
private bool handle = true;
private void P_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(handle && e.PropertyName == "IsChecked")
{
handle = false;
//uncheck all other persons
foreach (Person p in People)
if(p != sender)
p.IsChecked = false;
ChosenOne = sender as Person;
handle = true;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
If you plan to add new Person objects to the People collection dynamically at runtime you should also make sure that you handle the PropertyChanged event for these as well:
public Viewmodel()
{
People = new ObservableCollection<Person>
{
new Person { Name = "John" },
new Person { Name = "Marie" },
new Person { Name = "Bob" },
new Person { Name = "Sarah" }
};
foreach (Person p in People)
p.PropertyChanged += P_PropertyChanged;
People.CollectionChanged += (s, e) =>
{
if (e.NewItems != null)
{
foreach (object person in e.NewItems)
{
(person as INotifyPropertyChanged).PropertyChanged
+= new PropertyChangedEventHandler(P_PropertyChanged);
}
}
if (e.OldItems != null)
{
foreach (object person in e.OldItems)
{
(person as INotifyPropertyChanged).PropertyChanged
-= new PropertyChangedEventHandler(P_PropertyChanged);
}
}
};
People.Add(new Person() { Name = "New..." });
}

Silverlight MVVM:Updating parent view model based on status of child View model

I am new to Silverlight MVVM.
I have one requirement to show checkbox in a parent child hierarchy.
While loading the page if the child is checked then parent checkbox should also get checked.
I have created a ViewModel as below
public class TestViewModel : INotifyPropertyChanged
{
private string name;
private string percent;
private bool isChecked;
internal event EventHandler CheckboxStateChanged = delegate { };
private List<TestViewModel> testViewModel;
public List<TestViewModel> TestViewModel1
{
get { return testViewModel; }
set
{
testViewModel = value;
NotifyPropertyChanged("TestViewModel1");
}
}
public TestViewModel()
{
//IsChecked = true;
//Name = "Hello";
//Percent = "10";
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
NotifyPropertyChanged("IsChecked");
CheckboxStateChanged(this, new EventArgs());
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
In my main.xaml.cs I have created recursive method which will create the parent child hierarchy of checkboxes.
On clicking the child checkbox, parent checkbox is getting checked as I have added eventhandler in my VM (CheckboxStateChanged ) for that.But while on page load if child is checked then parent also get checked,I am unable to do that..Pls help.
Note I can not make parents checked until I get the status of child and once I get child status m not sure how to go back to parent.
Parent VM contains list of same VM as children(i.e public List TestViewModel1)
If I understand your question correct you are looking for a way to bouble up checkbox values from the children to its parent checkbox.
I've done a similar solution for a tree view. This code works but needs some event detaching if the collection changes.
The following is the set of classes that is used to run the ViewModel part of this solution.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class StructureViewModel : ViewModelBase
{
private bool? _isChecked = false;
public bool? IsChecked
{
get { return _isChecked; }
set
{
if (_isChecked != value)
{
_isChecked = value;
RaisePropertyChanged("IsChecked");
}
}
}
public string Name { get; set; }
}
public class ChildViewModel : StructureViewModel
{
}
public class ParentViewModel : StructureViewModel
{
public ParentViewModel()
{
Children = new List<ChildViewModel>();
}
public ICollection<ChildViewModel> Children { get; set; }
}
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
Parents = new List<ParentViewModel>();
var parent = new ParentViewModel { Name = "Parent" };
parent.Children.Add(new ChildViewModel
{
Name = "Child1"
});
parent.Children.Add(new ChildViewModel
{
Name = "Child2"
});
Parents.Add(parent);
}
public ICollection<ParentViewModel> Parents { get; set; }
}
To display this I use the following markup:
<TreeView ItemsSource="{Binding Parents}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}" >
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Content="{Binding Name}">
<i:Interaction.Behaviors>
<local:CheckParentBehavior Children="{Binding Children}" />
</i:Interaction.Behaviors>
</CheckBox>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The magic that fixes the checkboxes are the CheckParentBehavior:
public class CheckParentBehavior : Behavior<CheckBox>
{
public IEnumerable<StructureViewModel> Children
{
get { return (IEnumerable<StructureViewModel>)GetValue(ChildrenProperty); }
set { SetValue(ChildrenProperty, value); }
}
public static readonly DependencyProperty ChildrenProperty =
DependencyProperty.Register("Children", typeof(IEnumerable<StructureViewModel>), typeof(CheckParentBehavior), new PropertyMetadata(OnChildrenChanged));
protected override void OnAttached()
{
if (Children != null)
AssociatedObject.IsChecked = GetCheck(Children);
}
private static void OnChildrenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
foreach (var child in e.NewValue as IEnumerable<StructureViewModel>)
child.PropertyChanged += (_, args) => OnChildPropertyChanged(d as CheckParentBehavior, args);
}
}
private static void OnChildPropertyChanged(CheckParentBehavior behavior, PropertyChangedEventArgs args)
{
if (args.PropertyName == "IsChecked")
behavior.AssociatedObject.IsChecked = GetCheck(behavior.Children);
}
public static bool? GetCheck(IEnumerable<StructureViewModel> children)
{
if (children.All(c => c.IsChecked.GetValueOrDefault()))
return true;
else if (children.Any(c => c.IsChecked.GetValueOrDefault()))
return null;
else
return false;
}
}
What happens is that it listens to each childs propertychanged event and if it changes the ischecked property it will change the parents accordingly.
Hopefully you can use some of this code to solve your problem.

Need a simple Example of cascading combo boxes using MVVM

Need a simple Example of cascading combo boxes using MVVM
Wpf / Silverlight
If I understand your question you want to have the next combobox to fill with data based on the previous value.
I have a generic ViewModel that you can have to capture the list of items and the selected item
class ItemListViewModel<T> : INotifyPropertyChanged where T : class
{
private T _item;
private ObservableCollection<T> _items;
public ItemListViewModel()
{
_items = new ObservableCollection<T>();
_item = null;
}
public void SetItems(IEnumerable<T> items)
{
Items = new ObservableCollection<T>(items);
SelectedItem = null;
}
public ObservableCollection<T> Items
{
get { return _items; }
private set
{
_items = value;
RaisePropertyChanged("Items");
}
}
public T SelectedItem
{
get { return _item; }
set
{
_item = value;
RaisePropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then have the main viewmodel that will be bound to the DataContext of the view. Have the Load methods do what you want
class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
First = new ItemListViewModel<string>();
Second = new ItemListViewModel<string>();
Third = new ItemListViewModel<string>();
First.PropertyChanged += (s, e) => Update(e.PropertyName, First, Second, LoadSecond);
Second.PropertyChanged += (s, e) => Update(e.PropertyName, Second, Third, LoadThird);
LoadFirst();
}
public ItemListViewModel<string> First { get; set; }
public ItemListViewModel<string> Second { get; set; }
public ItemListViewModel<string> Third { get; set; }
private void LoadFirst()
{
First.SetItems(new List<string> { "One", "Two", "Three" });
}
private void LoadSecond()
{
Second.SetItems(new List<string> { "First", "Second", "Third" });
}
private void LoadThird()
{
Third.SetItems(new List<string> { "Firsty", "Secondly", "Thirdly" });
}
private void Update<T0, T1>(string propertyName, ItemListViewModel<T0> parent, ItemListViewModel<T1> child, Action loadAction)
where T0 : class
where T1 : class
{
if (propertyName == "SelectedItem")
{
if (parent.SelectedItem == null)
{
child.SetItems(Enumerable.Empty<T1>());
}
else
{
loadAction();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
And in your view have this code somewhere.
<ComboBox ItemsSource="{Binding First.Items}" SelectedItem="{Binding First.SelectedItem}" />
<ComboBox ItemsSource="{Binding Second.Items}" SelectedItem="{Binding Second.SelectedItem}" />
<ComboBox ItemsSource="{Binding Third.Items}" SelectedItem="{Binding Third.SelectedItem}" />
You can refactor to make it nicer, use MVVM frameworks or derive the ItemListViewModel specifically for the list of items and have the load in there for better encapsulation. Its up to you.
If any parent combobox value gets changed then all child lists will get cleared.
HTH

WPF MVVM User Control binding problem

I have an wpf mvvm application. I try to write checkbox list control.
I can bind the checkbox list elements.
Added to this issue, I want to get sum of the selected checkbox list elements values.
I added DependencyProperty and bind it to view model property.
But, they dont fire each other.
CheckBoxList User Control Xaml
<ListBox x:Name="ItemsControl" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding IsSelected, Mode=TwoWay}"
Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
CheckBoxList Code Behind
public partial class CheckBoxList : UserControl
{
public CheckBoxList()
{
InitializeComponent();
}
public static readonly DependencyProperty SelectedCheckBoxItemsValueProperty =
DependencyProperty.Register("SelectedCheckBoxItemsValue", typeof(int), typeof(CheckBoxList),
new FrameworkPropertyMetadata(
0,
new FrameworkPropertyMetadata(0, OnSelectedItemsChanged));
public int SelectedCheckBoxItemsValue
{
get { return (int)GetValue(SelectedCheckBoxItemsValueProperty); }
set { SetValue(SelectedCheckBoxItemsValueProperty, value); }
}
private static int GetSelectedCheckBoxItemsValue(DependencyObject obj)
{
return (int)obj.GetValue(SelectedCheckBoxItemsValueProperty);
}
private static void OnSelectedItemsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
CheckBoxList checkboxList = obj as CheckBoxList;
ObservableCollection<ISelectableItem> items = checkboxList.DataContext as ObservableCollection<ISelectableItem>;
foreach (var item in items)
{
item.IsSelected = (GetSelectedCheckBoxItemsValue(obj) & item.Value) != 0;
}
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBoxList checkboxList = sender as CheckBoxList;
ObservableCollection<ISelectableItem> coll = ItemsControl.DataContext as ObservableCollection<ISelectableItem>;
if (coll == null) return;
int count = 0;
foreach (var item in coll)
{
if (item.IsSelected)
{
count += item.Value;
}
}
SelectedCheckBoxItemsValue = count;
}
}
SelectableItem Class
public interface ISelectableItem : INotifyPropertyChanged
{
bool IsSelected { get; set; }
string Text { get; set; }
int Value { get; set; }
string GroupName { get; set; }
}
public class SelectableItem : ISelectableItem
{ ....
ViewModel Property
public int SelectedCheckBoxEnumItemsValue
{
get
{
return _selectedCheckBoxEnumItemsValue;
}
set
{
_selectedCheckBoxEnumItemsValue = value;
NotifyOfPropertyChange("SelectedCheckBoxEnumItemsValue");
}
}
At Binder Class
string selectedItemPropertyName = "Selected" + viewModelProperty.Name + "Value";
var property = viewModelProperties.FirstOrDefault(p => p.Name.Contains(selectedItemPropertyName));
if (property != null)
{
var selectedItemOrValueBinding = new Binding(property.Name)
{
Mode = property.CanWrite ? BindingMode.TwoWay : BindingMode.OneWay,
ValidatesOnDataErrors = Attribute.GetCustomAttributes(property, typeof(ValidationAttribute), true).Any()
};
BindingOperations.SetBinding(control, CheckBoxList.SelectedCheckBoxItemsValueProperty, selectedItemOrValueBinding);
}
Below code solves your problem..
Please Note the segrgation of view models.
<StackPanel>
<TextBlock Text="{Binding Count}"></TextBlock>
<ListBox x:Name="ItemsControl" ItemsSource="{Binding CheckList}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Name="item" Content="{Binding Text}" IsChecked="{Binding IsSelected, Mode=TwoWay}" Command="{Binding CheckboxCheckedCommand}" CommandParameter="{Binding IsChecked, ElementName=item}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MasterViewModel();
}
}
public class MasterViewModel : INotifyPropertyChanged
{
private List<CheckBoxItem> checkList;
private int count;
public int Count
{
get
{
return count;
}
set
{
count = value;
OnPropertyChanged("Count");
}
}
public List<CheckBoxItem> CheckList
{
get
{
return checkList;
}
set
{
checkList = value;
OnPropertyChanged("CheckList");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MasterViewModel()
{
checkList = new List<CheckBoxItem>();
for (int i = 0; i < 5; i++)
{
CheckBoxItem item = new CheckBoxItem();
item.Text = i.ToString();
item.IsSelected = false;
item.CheckboxCheckedCommand = new RelayCommand(new Action<object>(ExecuteCheckCommand));
checkList.Add(item);
}
}
private void ExecuteCheckCommand(object parameter)
{
if (parameter.GetType() == typeof(bool))
{
bool value = bool.Parse(parameter.ToString());
int val = count;
if (value)
{
val++;
}
else
{
val--;
}
Count = val;
}
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
public class CheckBoxItem : INotifyPropertyChanged
{
private bool isSelected;
private string text;
public string Text
{
get
{
return text;
}
set
{
text = value;
OnPropertyChanged("Text");
}
}
public bool IsSelected
{
get
{
return isSelected;
}
set
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
public ICommand CheckboxCheckedCommand
{
get;
set;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
public class RelayCommand : ICommand
{
private Action<object> executeCommand;
public RelayCommand(Action<object> executeCommand)
{
this.executeCommand = executeCommand;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
executeCommand(parameter);
}
}

Resources