wpf - ItemContainerGenerator.ContainerFromItem() devuelve nulo?
listbox containers (10)
Aunque la desactivación de la virtualización de XAML funciona, creo que es mejor desactivarlo desde el archivo .cs que usa ContainerFromItem
VirtualizingStackPanel.SetIsVirtualizing(listBox, false);
De esa forma, reduces el acoplamiento entre el XAML y el código; para evitar el riesgo de que alguien rompa el código al tocar el XAML.
Estoy teniendo un comportamiento extraño que parece que no puedo resolver. Cuando repito los elementos en mi propiedad ListBox.ItemsSource, parece que no puedo obtener el contenedor? Espero ver un ListBoxItem devuelto, pero solo obtengo un nulo.
¿Algunas ideas?
Aquí está el bit de código que estoy usando:
this.lstResults.ItemsSource.ForEach(t =>
{
ListBoxItem lbi = this.lstResults.ItemContainerGenerator.ContainerFromItem(t) as ListBoxItem;
if (lbi != null)
{
this.AddToolTip(lbi);
}
});
ItemsSource está configurado actualmente en un diccionario y contiene una cantidad de KVP.
Encontré algo que funcionó mejor para mi caso en esta pregunta de :
Obtener fila en la cuadrícula de datos
Al poner UpdateLayout y las llamadas ScrollIntoView antes de llamar a ContainerFromItem o ContainerFromIndex, hace que esa parte del DataGrid se realice, lo que hace posible que devuelva un valor para ContainerFromItem / ContainerFromIndex:
dataGrid.UpdateLayout();
dataGrid.ScrollIntoView(dataGrid.Items[index]);
var row = (DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromIndex(index);
Si no desea que la ubicación actual en DataGrid cambie, probablemente esta no sea una buena solución para usted, pero si está bien, funciona sin tener que apagar la virtualización.
Examine el código con el depurador y vea si realmente no hay nada retured o si el diferido es incorrecto y por lo tanto lo convierte en null
(puede usar un cast normal para obtener una excepción).
Un problema que ocurre con frecuencia es que cuando un ItemsControl
se virtualiza para la mayoría de los elementos, ningún contenedor existirá en ningún momento.
Además, no recomendaría tratar directamente con los contenedores de elementos sino más bien propiedades de enlace y suscribirse a eventos (a través de ItemsControl.ItemContainerStyle
).
Finalmente resuelto el problema ... Al agregar VirtualizingStackPanel.IsVirtualizing="False"
en mi XAML, todo funciona ahora como se esperaba.
En el lado negativo, pierdo todos los beneficios de rendimiento de la virtualización, así que cambié mi enrutamiento de carga a asincrónico y agregué un "spinner" a mi listbox mientras cargaba ...
Llego un poco tarde a la fiesta, pero aquí hay otra solución que es a prueba de fallas en mi caso,
Después de probar muchas soluciones que sugieren agregar IsExpanded
e IsSelected
al objeto subyacente y vincularlas en el estilo TreeViewItem
, aunque esto en su mayoría funciona en algunos casos, aún falla ...
Nota: mi objetivo era escribir una vista mini / personalizada tipo Explorador en la que cuando hago clic en una carpeta en el panel derecho, se selecciona en TreeView
, como en el Explorador.
private void ListViewItem_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var item = sender as ListViewItem;
var node = item?.Content as DirectoryNode;
if (node == null) return;
var nodes = (IEnumerable<DirectoryNode>)TreeView.ItemsSource;
if (nodes == null) return;
var queue = new Stack<Node>();
queue.Push(node);
var parent = node.Parent;
while (parent != null)
{
queue.Push(parent);
parent = parent.Parent;
}
var generator = TreeView.ItemContainerGenerator;
while (queue.Count > 0)
{
var dequeue = queue.Pop();
TreeView.UpdateLayout();
var treeViewItem = (TreeViewItem)generator.ContainerFromItem(dequeue);
if (queue.Count > 0) treeViewItem.IsExpanded = true;
else treeViewItem.IsSelected = true;
generator = treeViewItem.ItemContainerGenerator;
}
}
Múltiples trucos utilizados aquí:
- una pila para expandir cada elemento de arriba a abajo
- asegúrese de usar el generador de nivel actual para encontrar el artículo (realmente importante)
- el hecho de que el generador para artículos de nivel superior nunca devuelve
null
Hasta ahora funciona muy bien,
- no hay necesidad de contaminar sus tipos con nuevas propiedades
- no es necesario deshabilitar la virtualización en absoluto .
Lo más probable es que se ListBoxItem
problema relacionado con la virtualización, por lo que los contenedores de ListBoxItem
se generan solo para los elementos visibles actualmente (consulte https://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingstackpanel(v=vs.110).aspx#Anchor_9 )
Si está utilizando ListBox
, le sugiero cambiar a ListView
lugar: hereda de ListBox
y es compatible con el método ScrollIntoView()
que puede utilizar para controlar la virtualización;
targetListView.ScrollIntoView(itemVM);
DoEvents();
ListViewItem itemContainer = targetListView.ItemContainerGenerator.ContainerFromItem(itemVM) as ListViewItem;
(El ejemplo anterior también utiliza el método estático DoEvents()
explicado con más detalle aquí; WPF ¿cómo esperar a que ocurra la actualización de enlace antes de procesar más código? )
Hay algunas otras diferencias menores entre los controles ListBox
y ListView
( Cuál es la diferencia entre ListBox y ListView ), lo que no debería afectar esencialmente a su caso de uso.
Para cualquiera que todavía tenga problemas con esto, pude solucionar este problema ignorando el evento de la primera selección modificada y usando un hilo para básicamente repetir la llamada. Esto es lo que terminé haciendo:
private int _hackyfix = 0;
private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e)
{
//HACKYFIX:Hacky workaround for an api issue
//Microsoft''s api for getting item controls for the flipview item fail on the very first media selection change for some reason. Basically we ignore the
//first media selection changed event but spawn a thread to redo the ignored selection changed, hopefully allowing time for whatever is going on
//with the api to get things sorted out so we can call the "ContainerFromItem" function and actually get the control we need I ignore the event twice just in case but I think you can get away with ignoring only the first one.
if (_hackyfix == 0 || _hackyfix == 1)
{
_hackyfix++;
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
OnMediaSelectionChanged(sender, e);
});
}
//END OF HACKY FIX//Actual code you need to run goes here}
EDIT 10/29/2014: en realidad ni siquiera necesita el código del distribuidor de hilos. Puedes configurar lo que necesites para anular el evento de la primera selección modificada y luego regresar al evento para que los eventos futuros funcionen como se espera.
private int _hackyfix = 0;
private void OnMediaSelectionChanged(object sender, SelectionChangedEventArgs e)
{
//HACKYFIX: Daniel note: Very hacky workaround for an api issue
//Microsoft''s api for getting item controls for the flipview item fail on the very first media selection change for some reason. Basically we ignore the
//first media selection changed event but spawn a thread to redo the ignored selection changed, hopefully allowing time for whatever is going on
//with the api to get things sorted out so we can call the "ContainerFromItem" function and actually get the control we need
if (_hackyfix == 0)
{
_hackyfix++;
/*
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
OnMediaSelectionChanged(sender, e);
});*/
return;
}
//END OF HACKY FIX
//Your selection_changed code here
}
Use esta suscripción:
TheListBox.ItemContainerGenerator.StatusChanged += (sender, e) =>
{
TheListBox.Dispatcher.Invoke(() =>
{
var TheOne = TheListBox.ItemContainerGenerator.ContainerFromIndex(0);
if (TheOne != null)
// Use The One
});
};
VirtualizingStackPanel.IsVirtualizing = "False" Hace que el control sea borroso. Ver la implementación a continuación. Lo cual me ayuda a evitar el mismo problema. Configure su aplicación VirtualizingStackPanel.IsVirtualizing = "True" siempre.
Ver el link para obtener información detallada
/// <summary>
/// Recursively search for an item in this subtree.
/// </summary>
/// <param name="container">
/// The parent ItemsControl. This can be a TreeView or a TreeViewItem.
/// </param>
/// <param name="item">
/// The item to search for.
/// </param>
/// <returns>
/// The TreeViewItem that contains the specified item.
/// </returns>
private TreeViewItem GetTreeViewItem(ItemsControl container, object item)
{
if (container != null)
{
if (container.DataContext == item)
{
return container as TreeViewItem;
}
// Expand the current container
if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
{
container.SetValue(TreeViewItem.IsExpandedProperty, true);
}
// Try to generate the ItemsPresenter and the ItemsPanel.
// by calling ApplyTemplate. Note that in the
// virtualizing case even if the item is marked
// expanded we still need to do this step in order to
// regenerate the visuals because they may have been virtualized away.
container.ApplyTemplate();
ItemsPresenter itemsPresenter =
(ItemsPresenter)container.Template.FindName("ItemsHost", container);
if (itemsPresenter != null)
{
itemsPresenter.ApplyTemplate();
}
else
{
// The Tree template has not named the ItemsPresenter,
// so walk the descendents and find the child.
itemsPresenter = FindVisualChild<ItemsPresenter>(container);
if (itemsPresenter == null)
{
container.UpdateLayout();
itemsPresenter = FindVisualChild<ItemsPresenter>(container);
}
}
Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);
// Ensure that the generator for this panel has been created.
UIElementCollection children = itemsHostPanel.Children;
MyVirtualizingStackPanel virtualizingPanel =
itemsHostPanel as MyVirtualizingStackPanel;
for (int i = 0, count = container.Items.Count; i < count; i++)
{
TreeViewItem subContainer;
if (virtualizingPanel != null)
{
// Bring the item into view so
// that the container will be generated.
virtualizingPanel.BringIntoView(i);
subContainer =
(TreeViewItem)container.ItemContainerGenerator.
ContainerFromIndex(i);
}
else
{
subContainer =
(TreeViewItem)container.ItemContainerGenerator.
ContainerFromIndex(i);
// Bring the item into view to maintain the
// same behavior as with a virtualizing panel.
subContainer.BringIntoView();
}
if (subContainer != null)
{
// Search the next level for the object.
TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
if (resultContainer != null)
{
return resultContainer;
}
else
{
// The object is not under this TreeViewItem
// so collapse it.
subContainer.IsExpanded = false;
}
}
}
}
return null;
}
object viewItem = list.ItemContainerGenerator.ContainerFromItem(item);
if (viewItem == null)
{
list.UpdateLayout();
viewItem = list.ItemContainerGenerator.ContainerFromItem(item);
Debug.Assert(viewItem != null, "list.ItemContainerGenerator.ContainerFromItem(item) is null, even after UpdateLayout");
}