Avalonia.UI ComboBox, no DisplayMemberPath or SelectedValuePath. TemplatedControl - combobox

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.

Related

WPF Update count of item when button is pushed

new to WPF and programming here I am trying to update an item in a ListView when a button is pushed if the item already exists in that ListView, for example a user inputs CAR and CAR is already in the ListView the CAR count should go from 1 to 2.
This is my InventoryItem class:
public class InventoryItem
{
public string Name { get; set; }
public int Count { get; set; }
}
Here is my ViewModel
public class ViewModel : ViewModelBase
{
private InventoryItem _item;
private int _count;
private ObservableCollection<InventoryItem> _inventoryItems;
private ICommand _addItem;
public InventoryItem Item
{
get
{
return _item;
}
set
{
_item = value;
NotifyPropertyChanged("Item");
}
}
public int Count
{
get { return _count; }
set { _count = value; NotifyPropertyChanged("Count"); }
}
public ObservableCollection<InventoryItem> InventoryItems
{
get
{
return _inventoryItems;
}
set
{
_inventoryItems = value;
NotifyPropertyChanged("Items");
}
}
public ICommand AddItem
{
get
{
if (_addItem == null)
{
_addItem = new RelayCommand(ParamArrayAttribute => this.Submit(), null);
}
return _addItem;
}
}
public ViewModel()
{
Item = new InventoryItem();
InventoryItems = new ObservableCollection<InventoryItem>();
InventoryItems.CollectionChanged += new NotifyCollectionChangedEventHandler(InventoryItems_CollectionChanged);
}
void InventoryItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("Items");
NotifyPropertyChanged("Count");
}
private void Submit()
{
if (InventoryItems.Any(p => p.Name == Item.Name))
{
InventoryItems.First(x => x.Name == Item.Name).ItemCount++;
}
else
{
InventoryItems.Add(Item);
}
}
}
Here is my View
<ListView x:Name="listBox" ItemsSource="{Binding InventoryItems}" Grid.Row="0" HorizontalAlignment="Stretch" Height="Auto" Margin="10,10,10,60" VerticalAlignment="Stretch">
<ListView.Resources>
<Style TargetType="GridViewColumnHeader">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}" Width="Auto"/>
<GridViewColumn DisplayMemberBinding="{Binding ItemCount}" Width="Auto"/>
</GridView>
</ListView.View>
</ListView>
Whenever an item that exists in the list is typed in it appears that the InventoryItems ObservableCollection is being updated however InventoryItems_CollectionChanged event is not being fired so the ListView is not being updated. Isn't the collection changing so that event should be fired to update the ListView or am I not understanding the Binding and the event?
You need to notify about property changes in the InventoryItem class to update the changes of ItemCount in the ListView.
Quick solution:
public class InventoryItem : ViewModelBase
{
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
NotifyPropertyChanged(nameof(Name));
}
}
}
private string _name;
public int ItemCount
{
get { return _itemCount; }
set { _itemCount = value; NotifyPropertyChanged(nameof(ItemCount));
}
}
private int _itemCount;
}
}
The ObservableCollection class already contains handling of INotifyCollectionChanged.
You only need this line for the ObservableCollection. You can remove everything else that has to do with "InventoryItems" and the collection in your ViewModel.
public ObservableCollection<InventoryItem> InventoryItems { get; } = new ObservableCollection<InventoryItem>();
Also: When you add an item to the collection you need to create a new item. Otherwise you are adding the same object, that will not work.
This is my reduced ViewModel to how I think you want it to work:
public class ViewModel : ViewModelBase
{
private ICommand _addItem;
public string InputName
{
get { return _inputName; }
set
{
if (_inputName != value)
{
_inputName = value;
NotifyPropertyChanged(nameof(InputName));
}
}
}
private string _inputName;
public ObservableCollection<InventoryItem> InventoryItems { get; } = new ObservableCollection<InventoryItem>();
public ICommand AddItem
{
get
{
if (_addItem == null)
{
_addItem = new RelayCommand(ParamArrayAttribute => this.Submit(), null);
}
return _addItem;
}
}
private void Submit()
{
if (InventoryItems.Any(p => p.Name == InputName))
{
InventoryItems.First(x => x.Name == InputName).ItemCount++;
}
else
{
InventoryItems.Add(new InventoryItem() { Name = InputName, ItemCount = 1 });
}
}
}
To complete the picture, I have added following XAML for test:
<TextBox Text="{Binding InputName}" MinWidth="100" Margin="5"/>
<Button Content="Add" Command="{Binding AddItem}" Margin="5"/>

Can I connect two ViewModels without DependencyProperty?

I have MainWindow (simplified for clarity):
<Window>
<!-- (...) -->
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<!-- (...) -->
<CheckBox IsChecked="{Binding ShowAdvanced}" Content="Advanced view" />
<uc:MyUserControl DataContext={Binding MyUserControlViewModel} />
</Window>
MainWindowViewModel:
public partial class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
MyUserControlVM = new MyUserControlViewModel();
}
private bool _showAdvanced;
public bool ShowAdvanced
{
get => _showAdvanced;
set { _showAdvanced = value; NotifyPropertyChanged(); }
}
private MyUserControlViewModel _myUserControlVM;
public MyUserControlViewModel MyUserControlVM
{
get => _myUserControlVM;
set { _myUserControlVM= value; NotifyPropertyChanged(); }
}
}
In my UserControl I have some controls supposed to be hidden when "Show advanced" checkbox is not checked.
<GroupBox Header="Some advanced stuff"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.(vm:MainWindowViewModel.ShowAdvanced), Converter={StaticResource BoolToVis}}">
<!-- (...) -->
</GroupBox>
This actually works, but I don't like this because UserControl relies on MainWindow.
How can I connect these viewmodels correctly without DependencyProperty?
I have tried to add this to MyUserControlViewModel:
public MyUserControlViewModel(MainWindowViewModel parent)
{
Parent = parent;
}
private MainWindowViewModel _parent;
public MainWindowViewModel Parent
{
get { return _parent; }
set { _parent = value; NotifyPropertyChanged(); }
}
and bind visibility on one of MyUserControl controls like this:
Visibility="{Binding Parent.ShowAdvanced}"
but this is not working (MyUserControl is not getting notified?).
Add the ShowAdvanced property to the control VM and assign the value to each control VM whenever a new value is assigned to the MainViewModel ShowAdvanced property.
public class MainViewModel : Base.ViewModelBase
{
private bool _showAdvanced;
public MainViewModel()
{
MyUserControl1 = new MyUserControlViewModel { Message = "Control 1" };
MyUserControl2 = new MyUserControlViewModel { Message = "Control 2" };
MyUserControl3 = new MyUserControlViewModel { Message = "Control 3" };
}
public bool ShowAdvanced
{
get => _showAdvanced;
set
{
this.RaiseAndSetIfChanged( ref _showAdvanced, value );
MyUserControl1.ShowAdvanced = value;
MyUserControl2.ShowAdvanced = value;
MyUserControl3.ShowAdvanced = value;
}
}
public MyUserControlViewModel MyUserControl1 { get; }
public MyUserControlViewModel MyUserControl2 { get; }
public MyUserControlViewModel MyUserControl3 { get; }
}
public class MyUserControlViewModel : Base.ViewModelBase
{
private bool _showAdvanced;
private string _message;
public bool ShowAdvanced { get => _showAdvanced; set => this.RaiseAndSetIfChanged( ref _showAdvanced, value ); }
public string Message { get => _message; set => this.RaiseAndSetIfChanged( ref _message, value ); }
}

Add a checkboxes to existing Treeview in 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.

SelectedItem on ComboBox

There is a ComboBox in the application which is bound to a collection of items. There are cases that user can select an item from the ComboBox but the selected item might not be ready yet so the ComboBox selected item must get back to the previous selected item (or some other item in the collection), but in the current application ComboBox always shows the selected item from the user instead of retrieving the valid item after setting it back and calling notify property change.
The flowing is a simplified code of which shows the problem.
public partial class MainWindow : Window, INotifyPropertyChanged
{
private List<Customer> _Customers = new List<Customer>();
public List<string> CustomerNames
{
get
{
var list = new List<string>();
foreach (var c in _Customers)
{
list.Add(c.Name);
}
return list; ;
}
}
public string CustomerName
{
get
{
var customer = _Customers.Where(c => c.IsReady).FirstOrDefault();
return customer.Name;
}
set
{
NotifyPropertyChanged("CustomerName");
}
}
public MainWindow()
{
SetupCustomers();
InitializeComponent();
this.DataContext = this;
}
private void SetupCustomers()
{
_Customers.Add(new Customer("c1", true));
_Customers.Add(new Customer("c2", false));
_Customers.Add(new Customer("c3", false));
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Customer
{
public Customer(string name, bool isReady)
{
this.Name = name;
this.IsReady = isReady;
}
public bool IsReady { get; set; }
public string Name { get; set; }
public override bool Equals(object obj)
{
return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
<Window x:Class="TryComboboxReset.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">
<Grid>
<ComboBox Width="100"
Height="25"
ItemsSource="{Binding Path=CustomerNames, Mode=OneWay}"
SelectedItem="{Binding Path=CustomerName, Mode=TwoWay}"/>
</Grid>
The problem was UI thread, I used dispatcher to fix this problem
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Customer> _Customers =
new ObservableCollection<Customer>();
public ObservableCollection<Customer> CustomerNames
{
get
{
return _Customers;
}
}
public Customer CustomerName
{
get
{
return _Customers.Where(c => c.IsReady == true).FirstOrDefault();
}
set
{
// Delay the revert
Application.Current.Dispatcher.BeginInvoke(
new Action(() => NotifyPropertyChanged("CustomerName")), DispatcherPriority.ContextIdle, null);
}
}
public MainWindow()
{
SetupCustomers();
InitializeComponent();
this.DataContext = this;
}
private void SetupCustomers()
{
_Customers.Add(new Customer("c1", true));
_Customers.Add(new Customer("c2", false));
_Customers.Add(new Customer("c3", false));
CustomerName = _Customers.Where(c => c.IsReady == true).FirstOrDefault();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Customer
{
public Customer(string name, bool isReady)
{
this.Name = name;
this.IsReady = isReady;
}
public bool IsReady { get; set; }
public string Name { get; set; }
}
<ComboBox Width="400"
Height="25"
ItemsSource="{Binding Path=CustomerNames}"
SelectedValue="{Binding CustomerName,Mode=TwoWay}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You aren't actually setting the value of the selected customer name in your setter, and your getter is always going to return the first "ready" customer name it finds...
If I'm understanding the problem correctly, you need to be doing something more along these lines:
private string _customerName = null;
public string CustomerName
{
get
{
if(_customerName == null)
{
_customerName = _Customers.Where(c => c.IsReady).FirstOrDefault().Name;
}
return _customerName;
}
set
{
_customerName = value;
NotifyPropertyChanged("CustomerName");
}
}

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