ContentPresenter from ItemsControl returns null

283 Views Asked by At

I want to get all the UI-items from an ItemsControl.

From this post How do I access the children of an ItemsControl? I copied an answer and it works so far.

However, if I DIRECTLY execute the code in the for-loop after setting the ItemsSource (like in the bottom example), the contentpresenter is null and I cannot work with it.
If I run the for-loop quite a while later (maybe when I hit a button), everything works out fine.

How can I access all Children of a ItemsControl, DIRECTLY after setting the ItemsSource?

itemscontrol.ItemsSource = items; // items is a list
itemscontrol.ApplyTemplate(); // might refresh the itemscontrol

for (int i = 0; i < itemscontrol.Items.Count; i++)
{
    //                      ↓ this is null
    ContentPresenter contentpresenter = (ContentPresenter)itemscontrol.ItemContainerGenerator.ContainerFromItem(itemscontrol.Items[i]);
    //                      ↑ this is null

    contentpresenter.ApplyTemplate();

    StackPanel stackPanel = (StackPanel)contentpresenter.ContentTemplate.FindName("selectedStackpanel", contentpresenter);
    // do some stuff with the stackpanel
}
2

There are 2 best solutions below

9
BionicCode On BEST ANSWER

The better solution is to add related attributes to your item model e.g. a IsUserSelected property. Then create a Style, which you assign to ItemsControl.ItemContainerStyle. Inside this Style you define a trigger that triggers on IsUserSelected.

That's how it is done. Don't deal with the generator and check if each item is generated. Let the framework do this work for you.

 <ListBox ItemsSource="{Binding Items}">
   <ListBox.ItemContainerStyle>
     <Style TargetType="ListBoxItem">

       <Style.Triggers>
         <DataTrigger Binding="{Binding IsUserSelected}"
                      Value="True">
           <Setter Property="Background" Value="Red" />
         </DataTrigger>
       </Style.Triggers>
     </Style>
   </ListBox.ItemContainerStyle>`enter code here`
 </ListBox>

Since you already have a property HighlightId in your code-behind file, you can use a IMultiValueConverter together with a MultiBinding to define a color based on the value:

MainWindow.xaml.cs

partial class MainWindow
{
  public static readonly DependencyProperty HighlightIdProperty = DependencyProperty.Register(
    "HighlightId",
    typeof(int),
    typeof(MainWindow),
    new PropertyMetadata(default(int)));

  public int HighlightId
  {
    get => (int) GetValue(MainWindow.HighlightIdProperty);
    set => SetValue(MainWindow.HighlightIdProperty, value);
  }
}

HighlightIdToBrushConverter.cs

public class HighlightIdToBrushConverter : IMultiValueConverter
{
  public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
  {
    if (!(values[0] is MyModelType currentItem 
      && values[1] is int highlightId))
    {
      return Binding.DoNothing;
    }
    
    var highlightBrush = highlightId == currentItem.Id
      ? new SolidColorBrush(Colors.Red)
      : new SolidColorBrush(Colors.Transparent);
      
     highlightBrush.Freeze();
     return highlightBrush; 
  }

  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
    throw new NotSupportedException();
}

MainWindow.xaml

<ListBox ItemsSource="{Binding Items}">
  <ListBox.Resources>
    <HighlightIdToBrushConverter x:Key="HighlightIdToBrushConverter" />
  </ListBox.Resources>

  <ListBox.ItemContainerStyle>
    <Style TargetType="ListBoxItem">
      <Setter Property="Background">
        <Setter.Value>
          <MultiBinding Converter="{StaticResource HighlightIdToBrushConverter}">
            <Binding />
            <Binding RelativeSource="{RelativeSource AncestorType=Window}" 
                     Path="HighlightId" />
          </MultiBinding>
        </Setter.Value>
      </Setter>
    </Style>
  </ListBox.ItemContainerStyle>
</ListBox>
0
Pixel_95 On

Maybe not the best coding style, but it works reliably.

public ClassConstructor()
{
    InitializeComponent();
    itemscontrol.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}

...

void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
    if (itemscontrol.ItemContainerGenerator.Status == System.Windows.Controls.Primitives.GeneratorStatus.ContainersGenerated)
    {
        for (int i = 0; i < itemscontrol.Items.Count; i++)
        {
            ContentPresenter contentpresenter = (ContentPresenter)itemscontrol.ItemContainerGenerator.ContainerFromItem(itemscontrol.Items[i]);
            contentpresenter.ApplyTemplate();
            StackPanel stackPanel = (StackPanel)contentpresenter.ContentTemplate.FindName("selectedStackpanel", contentpresenter);
            int ID = FindIDofSelectedItemFromStackpanel(stackPanel);

            if (highlightID == ID)
            {
                Border border = (Border)contentpresenter.ContentTemplate.FindName("border_models", contentpresenter);
                border.Background = (Brush)FindResource("brushAccent3");
            }
            else
            {
                Border border = (Border)contentpresenter.ContentTemplate.FindName("border_models", contentpresenter);
                border.Background = Brushes.White;
            }
        }
    }
}