wpf - ¿Cómo obtener TreeViewItem del elemento HierarchicalDataTemplate?
(9)
¿Necesita TreeViewItem porque va a modificar lo que se muestra? Si ese es el caso, recomendaría usar un Estilo para cambiar la forma en que se muestra el elemento en lugar de usar código subyacente en lugar de modificar directamente el TreeViewItem. Debería ser más limpio.
Tengo un TreeView
que utiliza una HierarchicalDataTemplate
para vincular sus datos.
Se parece a esto:
<TreeView x:Name="mainTreeList" ItemsSource="{Binding MyCollection}>
<TreeView.Resources>
<HierarchicalDataTemplate
DataType="{x:Type local:MyTreeViewItemViewModel}"
ItemsSource="{Binding Children}">
<!-- code code code -->
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Ahora, desde el código subyacente de, digamos, la ventana principal, quiero obtener el TreeViewItem
seleccionado actualmente. Sin embargo, si uso:
this.mainTreeList.SelectedItem;
El elemento seleccionado es de tipo MyTreeViewItemViewModel
. Pero quiero obtener TreeViewItem
''padre'' o ''encuadernado''. No paso eso a mi objeto TreeViewItemModel
(ni siquiera sabría cómo).
¿Cómo puedo hacer esto?
Aquí hay una solución. rtvEsa es treeview. HierarchicalDataTemplate es una plantilla treeview Tag make real consumation of current item. Este no es un elemento seleccionado, es un elemento actual en el control de árbol que usa HierarchicalDataTemplate.
Items.CurrentItem es parte de la recolección interna de árboles. No puedes obtener muchos y diferentes datos. Por ejemplo Items.ParenItem también.
<HierarchicalDataTemplate ItemsSource="{Binding ChildItems}">
<TextBox Tag="{Binding ElementName=rtvEsa, Path=Items.CurrentItem }" />
Desde la entrada del blog de Bea Stollnitz sobre esto, prueba
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition));
Inspirado por la respuesta de Fëanor, he intentado hacer que TreeViewItem
fácilmente accesible para cada elemento de datos para el que se ha creado TreeViewItem
.
La idea es agregar un campo TreeViewItem
type al modelo de vista, también expuesto a través de una interfaz, y hacer que TreeView
lo TreeView
automáticamente siempre que se cree el contenedor TreeViewItem
.
Esto se hace subclasificando TreeView
y adjuntando un evento al ItemContainerGenerator
, que registra TreeViewItem
cada vez que se crea. Las capturas incluyen el hecho de que TreeViewItem
s se crean de forma perezosa, por lo que es posible que realmente no haya uno disponible en determinados momentos.
Desde que publiqué esta respuesta, la desarrollé más y la utilicé durante mucho tiempo en un proyecto. No hay problemas hasta el momento, aparte del hecho de que esto viola MVVM (pero también le ahorra una tonelada de repetitivo para los casos simples). Fuente aquí .
Uso
Seleccione el elemento primario del elemento seleccionado y córtelo, asegurándose de que esté en la vista:
...
var selected = myTreeView.SelectedItem as MyItem;
selected.Parent.TreeViewItem.IsSelected = true;
selected.Parent.TreeViewItem.IsExpanded = false;
selected.Parent.TreeViewItem.BringIntoView();
...
Declaraciones:
<Window ...
xmlns:tvi="clr-namespace:TreeViewItems"
...>
...
<tvi:TreeViewWithItem x:Name="myTreeView">
<HierarchicalDataTemplate DataType = "{x:Type src:MyItem}"
ItemsSource = "{Binding Children}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</tvi:TreeViewWithItem>
...
</Window>
class MyItem : IHasTreeViewItem
{
public string Name { get; set; }
public ObservableCollection<MyItem> Children { get; set; }
public MyItem Parent;
public TreeViewItem TreeViewItem { get; set; }
...
}
Código
public class TreeViewWithItem : TreeView
{
public TreeViewWithItem()
{
ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
var generator = sender as ItemContainerGenerator;
if (generator.Status == GeneratorStatus.ContainersGenerated)
{
int i = 0;
while (true)
{
var container = generator.ContainerFromIndex(i);
if (container == null)
break;
var tvi = container as TreeViewItem;
if (tvi != null)
tvi.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
var item = generator.ItemFromContainer(container) as IHasTreeViewItem;
if (item != null)
item.TreeViewItem = tvi;
i++;
}
}
}
}
interface IHasTreeViewItem
{
TreeViewItem TreeViewItem { get; set; }
}
Me encontré con este mismo problema. Necesitaba acceder al TreeViewItem para poder seleccionarlo. Luego me di cuenta de que solo podía agregar una propiedad IsSelected a mi ViewModel, que luego vinculé a TreeViewItems IsSelectedProperty. Esto se puede lograr con ItemContainerStyle:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Ahora, si quiero tener un elemento seleccionado en la vista de árbol, simplemente llamo a IsSelected en mi clase de ViewModel directamente.
Espero que ayude a alguien.
Pruebe algo como esto:
public bool UpdateSelectedTreeViewItem(PropertyNode dateItem, ItemsControl itemsControl)
{
if (itemsControl == null || itemsControl.Items == null || itemsControl.Items.Count == 0)
{
return false;
}
foreach (var item in itemsControl.Items.Cast<PropertyNode>())
{
var treeViewItem = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (treeViewItem == null)
{
continue;
}
if (item == dateItem)
{
treeViewItem.IsSelected = true;
return true;
}
if (treeViewItem.Items.Count > 0 && UpdateSelectedTreeViewItem(dateItem, treeViewItem))
{
return true;
}
}
return false;
}
Si tiene que encontrar un artículo en niños de niños, puede que tenga que recurrir a la recursión de esta manera.
public bool Select(TreeViewItem item, object select) // recursive function to set item selection in treeview
{
if (item == null)
return false;
TreeViewItem child = item.ItemContainerGenerator.ContainerFromItem(select) as TreeViewItem;
if (child != null)
{
child.IsSelected = true;
return true;
}
foreach (object c in item.Items)
{
bool result = Select(item.ItemContainerGenerator.ContainerFromItem(c) as TreeViewItem, select);
if (result == true)
return true;
}
return false;
}
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition));
NO FUNCIONA (para mí) como mainTreeList.Items.CurrentPosition en una vista de árbol utilizando una HierarchicalDataTemplate siempre será -1.
NI HACE A continuación, ya que mainTreeList.Items.CurrentItem en una vista de árbol utilizando una HierarchicalDataTemplate siempre será nula.
TreeViewItem item = (TreeViewItem)mainTreeList
.ItemContainerGenerator
.ContainerFromItem(mainTreeList.Items.CurrentItem);
EN LUGAR , tuve que establecer el último elemento TreeViewItem seleccionado en el evento TreeViewItem.Selected enrutado que fluye hasta la vista en árbol (los mismos TreeViewItem no existen en el momento del diseño, ya que estamos utilizando una HierarchicalDataTemplate).
El evento se puede capturar en XAML así:
<TreeView TreeViewItem.Selected="TreeViewItemSelected" .../>
Entonces, el último TreeViewItem seleccionado se puede configurar en el evento como sigue:
private void TreeViewItemSelected(object sender, RoutedEventArgs e)
{
TreeViewItem tvi = e.OriginalSource as TreeViewItem;
// set the last tree view item selected variable which may be used elsewhere as there is no other way I have found to obtain the TreeViewItem container (may be null)
this.lastSelectedTreeViewItem = tvi;
...
}
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition)); gives first item in the TreeView because CurrentPosition is always 0.
Qué tal si
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromItem(mainTreeList.SelectedItem)));
Esto funciona mejor para mí.