WPF binding to child collection
I have the following objects:
public class User
{
public User()
{
Roles = new ObservableCollection<Role>();
}
public int UserId { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public string Name { get; set; }
public virtual User User { get; set; }
}
With this ViewModel:
public class UserManagerViewModel : ObservableObject
{
public ObservableCollection<Role> AllRoles { get; set; }
public UserViewModel()
{
AllRoles = new ObservableCollection<Role>(RoleRepository.GetAll());
}
private User _selectedUser;
public User SelectedUser
{
get { return _selectedUser; }
set
{
if (_selectedUser != value)
{
_selectedUser = value;
RaisePropertyChanged();
}
}
}
...
}
I want to show roles SelectedUser
like this (or something similar):
<Window.DataContext>
<vm:UserManagerViewModel/>
</Window.DataContext>
<ListBox ItemsSource="{Binding AllRoles}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding ???}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Why do I need to set a property IsChecked
to CheckBox
represent roles SelectedUser
?
Assuming you want to "check" the roles it SelectedUser
has.
First we answer the question: "What data does it depend on?" The answer is quite simple, it depends on the role itself, so we write:
<CheckBox Content="{Binding Name}" IsChecked="{Binding .}"/>
It is now clear that this is not a bool; so we will need to write a converter for it that checks the collection. We could do a MultiValueConverter here (as in @Moji's answer), but it's probably easier to expose the collection via the Dependency Property and bind when creating the converter.
<Window.Resources>
<local:CollectionContainsConverter Collection="{Binding SelectedUser.Roles}"/>
</Window.Resources>
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=., Converter={StaticResource CollectionContainsConverter}"/>
And the converter:
public class CollectionContainsConverter : IValueConverter
{
public IEnumerable<object> Collection { get; set; } //This is actually a DP
public object Convert(...)
{
return Collection.Contains(value);
// or possibly, to allow for the Object.Equals override
return Collection.Any(o => o.Equals(value));
}
public object ConvertBack(...)
{
return Binding.DoNothing;
}
}
Without testing this, you might need to use a second return so that it doesn't compare references and use Object.Equals
(or another comparator of your choice) to figure out if the item is in the list.
you can use MultipleBinding and MultipleValueVonverter as below
<Grid>
<Grid.Resources>
<local:RoleValueConverter x:Key="converter"></local:RoleValueConverter>
</Grid.Resources>
<ListBox ItemsSource="{Binding AllRoles}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" >
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="."></Binding>
<Binding RelativeSource="{RelativeSource AncestorType={x:Type ListBox}}" Path="DataContext"></Binding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
public class RoleValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//values[0] is Role object
//value[1] is UserManagerViewModel
// then you can see if Selected user had Role object return true else return false
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}