Search code examples
c#wpfxamldatatemplateitemscontrol

How to send an ItemControl Item's location for nested collections


I have an ItemControl that is bound to an ObservableCollection<ObservableCollection<BookModel>>. Each item at the deeppest ItemControl has a button. I implemented a command that fires every time the button is clicked.
My problem is that I don't know which parameter should I pass to the execution method in order to know which Item fired the command. Meaning, if the button functions as an "Add Book" button, I want to know where should I insert a BookModel object

<UserControl.Resources>
<DataTemplate x:Key="BooksViewTemplate">
    <StackPanel>
        <TextBlock>
            <!-- This is where I bind data to the BookModel -->
        </TextBlock>
        <Button x:Name="AddNewBook"
                Command="{Binding ElementName=LayoutRoot, Path = DataContext.AddBookCommand}"
                <!-- CommandParameter=  -->
        />
    </StackPanel>
</<DataTemplate>

<DataTemplate x:Key="DataTemplate_level1">
    <StackPanel>
        <ItemsControl ItemsSource="{Binding}" ItemTemplate="{DynamicResource BooksViewTemplate}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <StackPanel Orientation="Vertical">
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </StackPanel>
</DataTemplate>
</UserControl.Resources>

<Grid x:Name="LayoutRoot">
<ItemsControl ItemsSource="{Binding Books}" ItemTemplate="{DynamicResource DataTemplate_level1}" DataContext="{Binding}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

public ICommand AddBookCommand
{
    get
    {
        return _addBookCommnad ?? (_addBookCommand = new CmandHandler<object>((par) => AddBookAction(par), _canExecute));
    }
}

public void AddBookAction(object obj)
{
    //This is where I want to add a new BookModel to the 
    IObservableCollection<IObservableCollection<BookModel>> at the location 
    given by the pressed button
}

Solution

  • You could use an AlternationIndex property to get the index. Just set AlternationCount in ItemsControl to int.MaxValue:

    <ItemsControl ItemsSource="{Binding yourcollection}" AlternationCount="2147483647">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Path=(ItemsControl.AlternationIndex), RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    If you pass an item as CommandParameter and search in your collection, then it will work, if you have no reference duplicates in the collection, otherwise it fails (you will not know which instance occurrence you should take).

    For nested collection you can access index such a way:

    <ItemsControl ItemsSource="{Binding ColOfCol}" AlternationCount="2147483647">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <ItemsControl ItemsSource="{Binding }" AlternationCount="2147483647">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <TextBlock>
                                <TextBlock.Text>
                                    <MultiBinding Converter="{StaticResource multivalcnv}">
                                        <Binding Path='(ItemsControl.AlternationIndex)' RelativeSource="{RelativeSource AncestorType=ContentPresenter, AncestorLevel=2}"></Binding>
                                        <Binding Path='(ItemsControl.AlternationIndex)' RelativeSource="{RelativeSource AncestorType=ContentPresenter, AncestorLevel=1}"></Binding>
                                    </MultiBinding>
                                </TextBlock.Text>
                            </TextBlock>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    public class MultValConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (values.Length == 2)
            {
                //return (values[0], values[1]); //For the ViewModel
                return (values[0], values[1]).ToString(); //For the UI example
                }
            else
                return Binding.DoNothing;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException("It's a one way converter.");
        }
    }