У меня есть TreeView
, который использует HierarchicalDataTemplate
для привязки своих данных.
Он выглядит следующим образом:
<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>
Теперь, из кода, скажем, главного окна, я хочу получить текущий выбранный TreeViewItem
. Однако, если я использую:
this.mainTreeList.SelectedItem;
Выбранный элемент имеет тип MyTreeViewItemViewModel
. Но я хочу получить "родительский" или "связанный" TreeViewItem
. Я не передаю это моему объекту TreeViewItemModel
(даже не знаю, как это сделать).
Как я могу это сделать?
Ответ 1
Из Bea Stollnitz в блоге об этом, попробуйте
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition));
Ответ 2
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition));
НЕ РАБОТАЕТ (для меня) как mainTreeList.Items.CurrentPosition в древовидной структуре с использованием HierarchicalDataTemplate всегда будет -1.
НИ ПРИ КАКИХ ОБСТОЯТЕЛЬСТВАХ ниже как mainTreeList.Items.CurrentItem в древовидном представлении с использованием HierarchicalDataTemplate всегда будет null.
TreeViewItem item = (TreeViewItem)mainTreeList
.ItemContainerGenerator
.ContainerFromItem(mainTreeList.Items.CurrentItem);
INSTEAD Мне нужно было установить последний выбранный TreeViewItem в маршрутизированном событии TreeViewItem.Selected, который пузырится до древовидного представления (сам TreeViewItem не существует во время разработки, поскольку мы используем HierarchicalDataTemplate).
Событие может быть записано в XAML так:
<TreeView TreeViewItem.Selected="TreeViewItemSelected" .../>
Затем последний выбранный TreeViewItem может быть установлен в событии так:
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;
...
}
Ответ 3
Я столкнулся с этой проблемой.
Мне нужно было добраться до TreeViewItem, чтобы я мог выбрать его.
Затем я понял, что могу просто добавить свойство IsSelected в мой ViewModel, который затем привязал к TreeViewItems IsSelectedProperty.
Это может быть достигнуто с помощью 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>
Теперь, если я хочу, чтобы элемент в выбранном дереве был выбран, я просто вызываю IsSelected непосредственно в моем классе ViewModel.
Надеюсь, что это поможет кому-то.
Ответ 4
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition)); gives first item in the TreeView because CurrentPosition is always 0.
Как насчет
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromItem(mainTreeList.SelectedItem)));
Это работает лучше для меня.
Ответ 5
Вдохновленный F'anors ответ, Ive попытался сделать TreeViewItem
легко доступным для каждого элемента данных, для которого был создан TreeViewItem
.
Идея состоит в том, чтобы добавить к модели представления TreeViewItem
-типное поле, также отображаемое через интерфейс, и TreeView
автоматически заполнять его всякий раз, когда создается контейнер TreeViewItem
.
Это выполняется путем подкласса TreeView
и привязки события к ItemContainerGenerator
, который записывает TreeViewItem
всякий раз, когда он создан. Gotchas включает в себя тот факт, что TreeViewItem
создаются лениво, поэтому в определенное время действительно невозможно быть доступным.
После публикации этого ответа я разработал это и использовал его в течение длительного времени в одном проекте. Пока нет проблем, кроме того, что это нарушает MVVM (но и экономит вам тонну шаблона для простых случаев). Источник здесь.
Использование
Выберите родительский элемент выбранного элемента и сверните его, указав его в представлении:
...
var selected = myTreeView.SelectedItem as MyItem;
selected.Parent.TreeViewItem.IsSelected = true;
selected.Parent.TreeViewItem.IsExpanded = false;
selected.Parent.TreeViewItem.BringIntoView();
...
Объявления:
<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; }
...
}
код
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; }
}
Ответ 6
Попробуйте что-то вроде этого:
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;
}
Ответ 7
Один вызов ItemContainerGenerator.ContainerFromItem или ItemContainerGenerator.ItemContainerGenerator не может найти TreeViewItem, связанный с объектом представления дерева, поскольку происходит разделение контроллера элемента представления дерева и данных элемента представления дерева. Необходимо создать рекурсивную функцию для использования ItemContainerGenerator.ContainerFromItem и ItemContainerGenerator.ItemContainerGenerator, чтобы найти TreeViewItem в виде дерева. Пример рекурсивной функции может выглядеть так:
private TreeViewItem GetTreeViewItemFromObject(ItemContainerGenerator container, object tvio)
{
var item = container.ContainerFromItem(tvio) as TreeViewItem;
if (item != null)
{
return item;
}
for (int i = 0; i < container.Items.Count; i++)
{
var subContainer = (TreeViewItem)container.ContainerFromIndex(i);
if (subContainer != null)
{
item = GetTreeViewItemFromObject(subContainer.ItemContainerGenerator, tvio);
if (item != null)
{
return item;
}
}
}
return null;
}
вызывается с помощью var target = GetTreeViewItemFromObject (treeView.ItemContainerGenerator, item);
Рекурсивная функция работает только после того, как древовидное представление ItemContainerGenerator.Status имеет значение "ContainersGenerated". Таким образом, в течение периода просмотра инициализации "GetTreeViewItemFromObject" не работает.
Ответ 8
Я изменил рекурсивный поиск Уильяма до более компактной версии:
public TreeViewItem GetTreeViewItemFromObject(ItemContainerGenerator container, object targetObject) {
if (container.ContainerFromItem(targetObject) is TreeViewItem target) return target;
for (int i = 0; i < container.Items.Count; i++)
if ((container.ContainerFromIndex(i) as TreeViewItem)?.ItemContainerGenerator is ItemContainerGenerator childContainer)
if (GetTreeViewItemFromObject(childContainer, targetObject) is TreeViewItem childTarget) return childTarget;
return null;
}
Можно было бы вызвать его, предоставив экземпляр TreeView ItemContainerGenerator и целевой объект данных:
TreeViewItem tvi = GetTreeViewItemFromObject(treeView.ItemContainerGenerator, targetDataObject);
Ответ 9
Вам нужен TreeViewItem, потому что вы собираетесь изменить отображаемое? Если это так, я бы рекомендовал использовать стиль, чтобы изменить способ отображения элемента вместо использования кода вместо прямого изменения TreeViewItem. Он должен, мы надеемся, быть более чистым.
Ответ 10
Если вам нужно найти элемент у детей детей, вам, возможно, придется использовать рекурсию, как это
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;
}
Ответ 11
Вот решение.
rtvEsa - это дерево.
HierarchicalDataTemplate - шаблон дерева.
Тег делает реальное потребление текущего элемента. Это не выбранный элемент, это текущий элемент в дереве управления, который использует HierarchicalDataTemplate.
Items.CurrentItem является частью внутреннего древовидного объединения.
Вы не можете получить много и разных данных. Например, Items.ParenItem тоже.
<HierarchicalDataTemplate ItemsSource="{Binding ChildItems}">
<TextBox Tag="{Binding ElementName=rtvEsa, Path=Items.CurrentItem }" />