检索对应于 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"
....
我有一个绑定到 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"
....