检索对应于 ObservableCollection 的 object 的 ListBoxItem

Retrieve the ListBoxItem corresponding to an object of an ObservableCollection

我有一个绑定到 ObservableCollection 的 ListBox。

       <ListBox x:Name="HorizontalListBox" 
                     ItemsSource="{Binding DataModels}" ...
    public class DataModel
    {
        public string TextValue { get; set; }

        public DataModel(string textValue)
        {
            this.TextValue = textValue;
        }
    }

我在 collection 中插入了一些数据:

   int idx = this.DataModels.IndexOf(currentDataModel);
            
   DataModel newDataModel = new DataModel($"Item{this.DataModels.Count}");
   this.DataModels.Insert(idx, newDataModel);

我想获取这个newDataModel对应的ListBoxItem(因为我通过实例获取了它的位置,我需要更新我的一些界面)。

我试过了:

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;

但 lbi 为空(其他索引不牛)。我认为这是因为 ListBoxItem 不是立即创建的。

那么,请问如何获取这个新DataModel对应的ListBoxItem呢?我必须赶上活动吗?

有什么建议吗?提前谢谢你。

编辑

<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>

ListBox 默认使用 UI 虚拟化。无法获取视口相应虚拟化缓存长度之外的项目的容器,因为该容器尚未实现。您可以使用 ListBox.ScrollIntoView 方法通过将相关项目滚动到视图中来强制实现:

<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;
}

备注
您可以在访问 Microsoft Docs 时找到 FindVisualChild 的示例实现:How to: Find DataTemplate-Generated Elements

我补充@BionicCode 的答案。 如果集合中的项目数较少,则可以禁用虚拟化:

    <ListBox x:Name="HorizontalListBox" 
             ItemsSource="{Binding DataModels}"
             VirtualizingStackPanel.IsVirtualizing="False"
             ....