Search code examples
c#wpfxamlbindingdatacontext

Get Ancestor Source in Wpf


I am trying to get the ancestor ItemsControl.ItemTemplate source as my DataGrid source but i could not handle the case. I tried the following case in my xaml. But it did not work and datagrid is empty.

Here is my xaml:

<ItemsControl ItemsSource="{Binding Accounts}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Expander ExpandDirection="Down" FlowDirection=" RightToLeft">
                <Expander.Header>
                    <DataGrid FlowDirection="LeftToRight"
                            ItemsSource="{RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemTemplate}}}">
                        <DataGrid.Columns>
                            <DataGridTextColumn Binding="{Binding UserName}" />
                            <DataGridTextColumn Binding="{Binding Password}" />
                        </DataGrid.Columns>
                    </DataGrid>
                </Expander.Header>
           </Expander>
      </DataTemplate>
   </ItemsControl.ItemTemplate>

Accounts property implementation is like:

public List<User> Accounts = new List<User>();
Accounts.Add(new User("userName1","password1"));
Accounts.Add(new User("userName2","password2"));

And my User.cs is like:

public class User : INotifyPropertyChanged
{
        public string UserName{get; set;}
        public string Password{get; set;}


       public UserAccount(string userName, string password)
       {
          UserName = userName;
          Password = password;
       }
 }

Solution

  • Basically, this code doesn't create a binding

    ItemsSource="{RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemTemplate}}}"
    

    also ancestor type is incorrect.

    try this Binding:

    ItemsSource="{Binding Path=DataContext.Accounts, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}"
    

    or use ElementName:

    <ItemsControl Name="LstAccounts" ItemsSource="{Binding Accounts}">
    

    and

    ItemsSource="{Binding Path=DataContext.Accounts, ElementName=LstAccounts}"
    

    code above fixes binding, but produces unexpected results. here is a solution to show a single object in a DataGrid

    for ItemSource binding I created a converter (my own older answer)

    public class ItemsSourceConverter: IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            // doesn't allow to add new rows in DataGrid
            return Enumerable.Repeat(value, 1).ToArray();            
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    and here is modified ItemsControl. Converter is stored in resources and used in ItemsSource binding. DataGrid autogenerates columns

    <ItemsControl ItemsSource="{Binding Accounts}">
        <ItemsControl.Resources>
            <local:ItemsSourceConverter x:Key="Enumerator"/>
        </ItemsControl.Resources>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Expander ExpandDirection="Down" FlowDirection=" RightToLeft">
                    <Expander.Header>
                        <DataGrid FlowDirection="LeftToRight" 
                                  ItemsSource="{Binding ., Converter={StaticResource Enumerator}}"/>
                    </Expander.Header>
                </Expander>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    here is a screenshot

    it looks like this