Max Mazur
Max Mazur

Reputation: 1316

Get Listboxitem from listbox

Hi this should be faily simple, however I don't know what I am doing wrong. I've been looking all over the internet seeing people make this work, even followed the tutorial on MSDN still nothing has worked for me.

I want to Iterate over a ListBox, and get the ListBoxItems so I can find the DataTemplate that I have added to it.

This is my code behind.

private void SetListBoxDataTemplate(ListBox MyListBox)
{
  try
  {
    foreach (CustomDataTemplateObject dataobject in MyListBox.Items)
    {
      ListBoxItem lbi = (ListBoxItem)(MyListBox.ItemContainerGenerator.ContainerFromItem(dataobject));
      ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(lbi);
      DataTemplate dt = myContentPresenter.ContentTemplate;
      TextBlock tb = (TextBlock)dt.FindName("ListBoxItemTextBlock1", myContentPresenter);
      ComboBox cb = (ComboBox)dt.FindName("ListBoxItemComboBox1", myContentPresenter);

      tb.Text = dataobject.Text;
      cb.ItemsSource = dataobject.ListColors;
    }
  }
  catch (Exception ex)
  {
    MessageBox.Show(""+ex);
  }
}

XAML looks like this:

 <DataTemplate x:Key="ListBoxItemDataTemplate1">
        <StackPanel Orientation="Horizontal">
            <Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
                <TextBlock Name="ListBoxItemTextBlock1" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
                </TextBlock>
            </Border>
            <ComboBox Name="ListBoxItemComboBox1" />
        </StackPanel>
    </DataTemplate>*

 <StackPanel>
    <ListBox Name="ListBoxTest1" ItemTemplate="{DynamicResource ListBoxItemDataTemplate1}" />
</StackPanel>

I have tried with setting my itemtemplate to static to see if it works, and the method i'm calling from code behind, is called after I have populated my ListBoxs

My dataobject is NOT null, however when i call the line in my code behind, my lbi, ends up being null.

Any suggestions? thanks in advance!

FIRST UPDATE

This problem only occurs if i call the method in my constructor, so perhaps it's because it hasn't initialized the full group element section yet. However I want to do this as soon as possible. Am I perhaps forced to do it in a WindowLoaded event?

SECOND UPDATE

Code updated, Rachel's answer worked for iterating over my ListBoxItems, however the Listbox Has not fully rendered since i'm unable to reach the Datatemplate at this time. So MyListBox_GeneratorStatusChanged is not working for this problem, but it does get the ListBoxItems.

Upvotes: 0

Views: 3617

Answers (2)

Rachel
Rachel

Reputation: 132618

WPF's main thread runs items at different priority levels. Code that runs in the Constructor all gets run at Normal priority, while things like rendering the ListBox and it's items run at the Render priority level, which occurs after all Normal priority operations have finished.

This means that your entire Constructor gets run (including SetListBoxDataTemplate()) before your ListBox is even rendered and the items get generated.

If you want to run some code after the items are generated, use the ItemsContainerGenerator.StatusChanged event

// Constructor
MyListBox.ItemContainerGenerator.StatusChanged += MyListBox_GeneratorStatusChanged;

...

void MyListBox_GeneratorStatusChanged(object sender, EventArgs e)
{
    // return if containers have not been generated yet
    if (MyListBox.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
        return;

    // remove event
    MyListBox.ItemContainerGenerator.StatusChanged -= MyListBox_GeneratorStatusChanged;

    // your items are now generated
    SetListBoxDataTemplate(MyListBox);
}

What are you trying to accomplish with this method anyways? It is a bit unusual for WPF, and there may be a much better WPF way of accomplishing your task.

Updated based on new code added to Question

A much better method of setting your Text and ItemsSource properties is to make use of WPF's data bindings.

Your DataTemplate should look like this:

<DataTemplate x:Key="ListBoxItemDataTemplate1">
    <StackPanel Orientation="Horizontal">
        <Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
            <TextBlock Text="{Binding Text}" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
            </TextBlock>
        </Border>
        <ComboBox ItemsSource="{Binding ListColors}" />
    </StackPanel>
</DataTemplate>*

A DataTemplate is like a cookie cutter. It's used to make the UI objects, but is not part of the UI object itself. All it does is tell WPF that "When you go to render this object, render it using this XAML". So the way your XAML gets rendered is

<ListBoxItem>
    <StackPanel>
        <Border>
            <TextBlock Text="{Binding Text}" />
        </Border>
        <ComboBox ItemsSource="{Binding ListColors}">
    </StackPanel> 
</ListBoxItem>

In addition, the DataContext behind your ListBoxItem is the item from the collection bound to ListBox.ItemsSource, which based on your code should be CustomDataTemplateObject. That allows the bindings from the DataTemplate to work

If you're new to WPF and struggling to understand how exact the DataContext works, I'd recommend reading this article of mine: What is this "DataContext" you speak of?.

To summarize, WPF has two layers to an application: the UI layer and the Data Layer (DataContext). When you perform a basic binding like above, you are pulling data from the data layer into the UI layer.

So your ListBoxItem has a data layer of CustomDataTemplateObject, and the TextBlock.Text and ComboBox.ItemsSource bindings are pulling data from the data layer for use in the UI layer.

I'd also highly recommend using a utility like Snoop which lets you view the entire Visual Tree of a running WPF application to see how items get rendered. Its very useful for debugging or learning more about how WPF works.

Upvotes: 3

Sheridan
Sheridan

Reputation: 69987

You're confusing two jobs and mixing them into one. First, get access to the ListBoxItem:

private void SetListBoxDataTemplate(ListBox MyListBox)
{
    foreach (ListBoxItem listBoxItem in MyListBox.Items)
    {
    }
}

Now you can get the DataTemplate from the ListBoxItem:

foreach (ListBoxItem listBoxItem in MyListBox.Items)
{
    ContentPresenter presenter = FindVisualChild<ContentPresenter>(listBoxItem);
    DataTemplate dataTemplate = presenter.ContentTemplate;
    if (dataTemplate != null)
    {
        // Do something with dataTemplate here
    }
}

The FindVisualChild method can be found in the How to: Find DataTemplate-Generated Elements page on MSDN.


UPDATE >>>

To answer your edit, yes, the constructor will be too early to try to access these DataTemplates because the Framework won't have applied them to all of the objects by then. It is best to use the FrameworkElement.Loaded Event to do these kinds of things, as that is the first event that can be called after the controls have all been initialised.

Upvotes: 2

Related Questions