Reputation: 55
I've got a ListBox binded to an ObservableCollection.
<ListBox x:Name="HorizontalListBox"
ItemsSource="{Binding DataModels}" ...
public class DataModel
{
public string TextValue { get; set; }
public DataModel(string textValue)
{
this.TextValue = textValue;
}
}
I insert some data inside my collection:
int idx = this.DataModels.IndexOf(currentDataModel);
DataModel newDataModel = new DataModel($"Item{this.DataModels.Count}");
this.DataModels.Insert(idx, newDataModel);
I would like to get the ListBoxItem corresponding to this newDataModel (because I retrieve its position by example and I need to update some of my interface).
I tried:
int nidx = HorizontalListBox.Items.IndexOf(newDataModel);
//var v = HorizontalListBox.Items.GetItemAt(nidx); //ne marche pas on récupère le DataModel
var lbi = HorizontalListBox.ItemContainerGenerator.ContainerFromIndex(nidx) as ListBoxItem;
but lbi is null (not bull for the other indexes). I think that it's because the ListBoxItem is not instantly created.
So, how to get the ListBoxItem corresponding to this new DataModel, please? Do I have to catch an event?
Any suggestions? Thank you in advance.
EDIT
<ListBox x:Name="HorizontalListBox"
ItemsSource="{Binding DataModels}"
MouseLeave="HorizontalListBox_MouseLeave">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" VirtualizingPanel.IsVirtualizing="False" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="myElement"
MouseEnter="myElement_MouseEnter"
MouseLeave="myElement_MouseLeave">
<TextBlock x:Name="myText"
Margin="10"
Text="{Binding TextValue}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
TextAlignment="Center" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Upvotes: 0
Views: 78
Reputation: 28958
ListBox
uses UI virtualization by default. It's not possible to get the container of an item that is outside the viewport respective virtualization cache length, as that container is not realized yet. You can force realization by scrolling the relevant item into view using the ListBox.ScrollIntoView
method:
<ListBox x:Name="HorizontalListBox" />
private void OnSelectedItemChanged(object, EventArgs e)
{
var listBox = sender as ListBox;
if (TryGetItemContainerOf(listBox.SelectedItem, out ContentControl itemContainer)
&& TryGetVisualChildOfItemContainerByName(itemContainer, "myElement", out Grid grid))
{
// TODO::Handle named element
}
}
private bool TryGetItemContainerOf(object item, out ContentControl itemContainer)
{
this.HorizontalListBox.ScrollIntoView(item);
itemContainer = this.HorizontalListBox.ItemContainerGenerator.ContainerFromItem(item) as ContentControl;
return itemContainer != null;
}
private bool TryGetVisualChildOfItemContainerByName<TChild>(ContentControl container, string elementName, out TChild resultElement)
where TChild : FrameworkElement
{
resultElement = default;
// Item container is only generated (by calling 'GetItemContainerOf()'),
// but not rendered yet. This means templates are not applied.
// Therefore, we must force the container to apply the DataTemplate on its Content.
// This is only necessary because of the circumstances introduced UI virtualization.
container.ApplyTemplate();
var contentPresenter = FindVisualChild<ContentPresenter>(container);
if (contentPresenter != null)
{
// Item container is only generated but not rendered yet.
// Therefore we must force the container to apply the ControlTemplate.
// This is only necessary because of the circumstances introduced UI virtualization.
contentPresenter.ApplyTemplate();
resultElement = contentPresenter.ContentTemplate.FindName(elementName, contentPresenter) as TChild;
}
return resultElement != default;
}
Remarks
You can find an example implementation of FindVisualChild
when visiting Microsoft Docs: How to: Find DataTemplate-Generated Elements
Upvotes: 1
Reputation: 7908
I complement the answer from @BionicCode. If the count of items in the collection is small, then you can disable virtualization:
<ListBox x:Name="HorizontalListBox"
ItemsSource="{Binding DataModels}"
VirtualizingStackPanel.IsVirtualizing="False"
....
Upvotes: 1