2
votes

I'm creating an MVVM application which has two view models, RegisterUserViewModel and UserViewModel. The "RegisterUserViewModel" has a public property which is a collection of "UserViewModel", like the code shown below

public class RegisterUserViewModel : ViewModelBase
{
    public ObservableCollection<UserViewModel> Users { get; }
    public ObservableCollection<string> UsersFromAD { get; }
}

public class UserViewModel : ViewModelBase
{
    public string Name { get; set; }
}

The "RegisterUserViewModel" class also has a "UsersFromAD" property which is populated with the names of users obtained from Active Directory.

My problem lies in data binding from within my views.

I have a MainWindow view which separates its window in two areas (left and right): The area on the left has a list of available commands, and the area on the right has a tab control. The commands on the left are used to open new tabs on the tab control. The TabControl's ItemsSource property is bound to a collection of "ViewModelBase" classes. Each opened tab in the TabControl is automatically bound to a specific view due to DataTemplate bindings, depending on the type of class that is being opened.

Let me be more clear:

1) I have a command on the left that adds a new instance of "RegisterUserViewModel" to the "ObservableCollection" to which the TabControl's ItemsSource property is bound.

2) The MainWindow.xaml file has a section which specifies a DataTemplate that binds a specific view to RegisterUserViewModel, as shown in the code below:

<Window.Resources>
    <ResourceDictionary>
        <DataTemplate DataType={x:Type ViewModel:RegisterUserViewModel}">
            <View:RegisterUserView />
        </DataTemplate>
    <ResourceDictionary>
<Window.Resources>

3) By doing this, WPF automatically sets the DataContext of my RegisterUserView to my RegisterUserViewModel.

Now, I have added a DataGrid control to my RegisterUserView with its ItemsSource set to my UserViewModel collection (Users). Everything works fine, and users added to the collection show up on the DataGrid.

However, what I want is for the "Name" column on the datagrid to use a DataGridComboBoxColumn, with its ItemsSource property set to the "UsersFromAD" property which lies on my "RegisterUserViewModel" class.

For that, I used the following xaml:

<DataGridComboBoxColumn ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ViewModel:RegisterUserViewModel}}, Path=UsersFromAD}"
                        SelectedValueBinding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ViewModel:UserViewModel}}, Path=Name}"
                        />

However, that does not work. Whenever I run the application, I get the following from the Output Window:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='TestApplication.ViewModel.RegisterUserViewModel', AncestorLevel='1''. BindingExpression:Path=UsersFromAD; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=24004376); target property is 'ItemsSource' (type 'IEnumerable')
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='TestApplication.ViewModel.UserViewModel', AncestorLevel='1''. BindingExpression:Path=Name; DataItem=null; target element is 'TextBlockComboBox' (Name=''); target property is 'SelectedValue' (type 'Object')

Can anyone help me with setting these bindings correctly?

2

2 Answers

2
votes

I solved this problem by adding a CollectionViewSource to my "UserControl.Resources" section of my RegisterUserView and binding the CollectionViewSource to my UsersFromAD property, like below:

<UserControl.Resources>
    <CollectionViewSource x:Key="cvsUsersFromAD" Source="{Binding UsersFromAD}" />
</UserControl.Resources>

And then, by binding my DataGridComboBoxColumn to my CollectionViewSource, like below:

<DataGridComboBoxColumn ItemsSource="{Binding Source={StaticResource cvsUsersFromAD}}"
                        />
1
votes

FindAncestor is seaching for type in element tree not in DataContext of elements in tree. So for binding ItemSource of column you can try folowing (as i understand your object graph)

<DataGridComboBoxColumn ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type View:RegisterUserView}}, Path=DataContext.UsersFromAD}"
                        />