Как получить TreeViewItem из элемента HierarchicalDataTemplate? - PullRequest
31 голосов
/ 06 марта 2009

У меня есть 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 (даже не знаю как).

Как я могу это сделать?

Ответы [ 11 ]

31 голосов
/ 05 июля 2010
TreeViewItem item = (TreeViewItem)(mainTreeList
    .ItemContainerGenerator
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition));

НЕ РАБОТАЕТ (для меня) как mainTreeList.Items.CurrentPosition в древовидной структуре с использованием HierarchicalDataTemplate всегда будет -1.

НИКОГДА НЕ ДЕЛАЕТ ниже как mainTreeList.Items.CurrentItem в древовидной структуре с использованием HierarchicalDataTemplate всегда будет нулевым.

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;

        ...
     }
20 голосов
/ 06 марта 2009

Из Bea Stollnitz В блоге об этом, попробуйте

TreeViewItem item = (TreeViewItem)(mainTreeList
    .ItemContainerGenerator
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition));
7 голосов
/ 21 сентября 2011

Я столкнулся с этой же проблемой. Мне нужно было добраться до 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 напрямую.

Надеюсь, это кому-нибудь поможет.

5 голосов
/ 09 января 2013
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)));

Это работает лучше для меня.

4 голосов
/ 26 марта 2012

Вдохновленный ответом Феанора, я попытался сделать TreeViewItem легкодоступным для каждого элемента данных, для которого был создан TreeViewItem.

Идея состоит в том, чтобы добавить поле типа TreeViewItem в модель представления, также предоставляемую через интерфейс, и сделать так, чтобы TreeView автоматически заполняло его при создании контейнера TreeViewItem.

Это делается путем создания подкласса TreeView и присоединения события к ItemContainerGenerator, который записывает TreeViewItem при каждом его создании. К ошибкам можно отнести тот факт, что 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; }
}
2 голосов
/ 07 февраля 2015

Попробуйте что-то вроде этого:

    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;
    }
1 голос
/ 11 января 2019

Я изменил рекурсивный поиск Уильяма до более компактной версии:

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

Можно было бы вызвать его, указав ItemContainerGenerator экземпляра TreeView и целевой объект данных:

TreeViewItem tvi = GetTreeViewItemFromObject(treeView.ItemContainerGenerator, targetDataObject);
1 голос
/ 08 июня 2018

Один вызов 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» не работает.

0 голосов
/ 09 октября 2014

Вот решение. rtvEsa это дерево. HierarchicalDataTemplate является шаблоном в виде дерева Тэг реально потребляет текущий предмет. Это не выбранный элемент, это текущий элемент в древовидном элементе управления, который использует HierarchicalDataTemplate.

Items.CurrentItem является частью внутренней сборки дерева. Вы не можете получить много разных данных. Например, Items.ParenItem.

  <HierarchicalDataTemplate ItemsSource="{Binding ChildItems}">

  <TextBox Tag="{Binding ElementName=rtvEsa, Path=Items.CurrentItem }" />

0 голосов
/ 06 сентября 2013

Если вам нужно найти предмет у детей, вам, возможно, придется использовать рекурсию, подобную этой

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