wpf 如何从 HierarchicalDataTemplate 项中获取 TreeViewItem?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/616948/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 20:06:12  来源:igfitidea点击:

How to get TreeViewItem from HierarchicalDataTemplate item?

wpftreeviewhierarchicaldatatemplate

提问by Razzie

I have a TreeViewwhich uses a HierarchicalDataTemplateto bind its data.

我有一个TreeView使用 aHierarchicalDataTemplate来绑定它的数据。

It looks like this:

它看起来像这样:

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

Now, from the code-behind of say the main window, I want to get the current selected TreeViewItem. However, if I use:

现在,从主窗口的代码隐藏中,我想获取当前选定的TreeViewItem. 但是,如果我使用:

this.mainTreeList.SelectedItem;

The selectedItem is of type MyTreeViewItemViewModel. But I want to get the 'parent' or 'bound' TreeViewItem. I do not pass that to my TreeViewItemModelobject (wouldn't even know how).

selectedItem 的类型为MyTreeViewItemViewModel。但我想得到 'parent' 或 'bound' TreeViewItem。我不会将它传递给我的TreeViewItemModel对象(甚至不知道如何传递)。

How can I do this?

我怎样才能做到这一点?

采纳答案by Cameron MacFarland

From Bea Stollnitz's blog entry about this, try

Bea Stollnitz的关于此的博客条目中,尝试

TreeViewItem item = (TreeViewItem)(mainTreeList
    .ItemContainerGenerator
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition));

回答by markmnl

TreeViewItem item = (TreeViewItem)(mainTreeList
    .ItemContainerGenerator
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition));

DOES NOT WORK(for me) as mainTreeList.Items.CurrentPosition in a treeview using a HierarchicalDataTemplate will always be -1.

不起作用使用HierarchicalDataTemplate(对我来说)作为mainTreeList.Items.CurrentPosition TreeView中的永远是-1。

NEITHER DOESbelow as as mainTreeList.Items.CurrentItem in a treeview using a HierarchicalDataTemplate will always be null.

同样没有下文如为mainTreeList.Items.CurrentItem在使用HierarchicalDataTemplate永远是空树视图。

TreeViewItem item = (TreeViewItem)mainTreeList
    .ItemContainerGenerator
    .ContainerFromItem(mainTreeList.Items.CurrentItem);

INSTEADI had to set a the last selected TreeViewItem in the routed TreeViewItem.Selected event which bubbles up to the tree view (the TreeViewItem's themselves do not exist at design time as we are using a HierarchicalDataTemplate).

相反,我必须在路由的 TreeViewItem.Selected 事件中设置最后一个选定的 TreeViewItem,该事件冒泡到树视图(TreeViewItem 本身在设计时不存在,因为我们使用的是 HierarchicalDataTemplate)。

The event can be captured in XAML as so:

可以在 XAML 中捕获事件,如下所示:

<TreeView TreeViewItem.Selected="TreeViewItemSelected" .../> 

Then the last TreeViewItem selected can be set in the event as so:

然后可以在事件中设置最后选择的 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;

        ...
     }

回答by santiagoIT

I ran into this same problem. I needed to get to the TreeViewItem so that I could have it be selected. I then realized that I could just add a property IsSelected to my ViewModel, which I then bound to the TreeViewItems IsSelectedProperty. This can be achieved with the ItemContainerStyle:

我遇到了同样的问题。我需要进入 TreeViewItem 以便我可以选择它。然后我意识到我可以向我的 ViewModel 添加一个属性 IsSelected,然后我将它绑定到 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>

Now if I want to have an item in the treeview selected, I just call IsSelected on my ViewModel class directly.

现在,如果我想在树视图中选择一个项目,我只需直接在我的 ViewModel 类上调用 IsSelected。

Hope it helps someone.

希望它可以帮助某人。

回答by kitndirangu

TreeViewItem item = (TreeViewItem)(mainTreeList
    .ItemContainerGenerator
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition)); gives first item in the TreeView because CurrentPosition is always 0.

How about

怎么样

TreeViewItem item = (TreeViewItem)(mainTreeList
    .ItemContainerGenerator
    .ContainerFromItem(mainTreeList.SelectedItem)));

This works better for me.

这对我来说效果更好。

回答by Roman Starkov

Inspired by F?anor's answer, I've attempted to make the TreeViewItemeasily accessible for every data item that a TreeViewItemhas been created for.

受 F?anor 的回答的启发,我试图让 a 的TreeViewItem每个数据项都可以轻松访问TreeViewItem

The idea is to add a TreeViewItem-typed field to the view model, also exposed via an interface, and have the TreeViewautomatically populate it whenever the TreeViewItemcontainer is created.

这个想法是向TreeViewItem视图模型添加一个类型的字段,也通过接口公开,并在创建容器TreeView时自动填充它TreeViewItem

This is done by subclassing TreeViewand attaching an event to the ItemContainerGenerator, which records the TreeViewItemwhenever it's created. Gotchas include the fact that TreeViewItems are created lazily, so there might genuinely not be one available at certain times.

这是通过子类化TreeView并将事件附加到 来完成的ItemContainerGenerator,它TreeViewItem会在创建时记录。陷阱包括一个事实,即TreeViewItems 是懒惰地创建的,因此在某些时候可能真的没有可用的。

Since posting this answer, I've developed this further and used it for a long time in one project. No issues so far, other than the fact that this violates MVVM (but also saves you a ton of boilerplate for the simple cases). Source here.

自从发布这个答案后,我进一步开发了它,并在一个项目中使用了很长时间。到目前为止没有问题,除了这违反了 MVVM 的事实(但也为简单的情况节省了大量的样板文件)。来源在这里

Usage

用法

Select the parent of the selected item and collapse it, ensuring that it's in the view:

选择所选项目的父项并折叠它,确保它在视图中:

...
var selected = myTreeView.SelectedItem as MyItem;
selected.Parent.TreeViewItem.IsSelected = true;
selected.Parent.TreeViewItem.IsExpanded = false;
selected.Parent.TreeViewItem.BringIntoView();
...

Declarations:

声明:

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

Code

代码

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

回答by Kevin

Try something like this:

尝试这样的事情:

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

回答by William

A single 'ItemContainerGenerator.ContainerFromItem' or 'ItemContainerGenerator.ItemContainerGenerator' call cannot find the TreeViewItem associated by tree view object, since the separation of tree view item controller and tree view item data. It is necessary to create a recursive function to use 'ItemContainerGenerator.ContainerFromItem' and 'ItemContainerGenerator.ItemContainerGenerator', to locate TreeViewItem in tree view. Example recursive function could look like:

由于树视图项控制器和树视图项数据分离,因此单个“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;
}

called by var target = GetTreeViewItemFromObject(treeView.ItemContainerGenerator, item);

由 var target = GetTreeViewItemFromObject(treeView.ItemContainerGenerator, item) 调用;

The recursive function only works after tree view's 'ItemContainerGenerator.Status' is 'ContainersGenerated'. So during the initialization view period, 'GetTreeViewItemFromObject' does not work.

递归函数仅在树视图的 'ItemContainerGenerator.Status' 为 'ContainersGenerated' 后才起作用。所以在初始化视图期间,'GetTreeViewItemFromObject' 不起作用。

回答by Mike DeMauro

I modified William's recursive search to a more compact version:

我将威廉的递归搜索修改为更紧凑的版本:

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

One would call it by providing the TreeView instance's ItemContainerGenerator and the target data object:

可以通过提供 TreeView 实例的 ItemContainerGenerator 和目标数据对象来调用它:

TreeViewItem tvi = GetTreeViewItemFromObject(treeView.ItemContainerGenerator, targetDataObject);

回答by purvin

If you have to find item in children of children you may have to use recursion like this

如果您必须在孩子的孩子中找到项目,您可能必须使用这样的递归

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

回答by Petar

Here is solution. rtvEsa is treeview. HierarchicalDataTemplate is treeview template Tag make real consumation of current item. This is not selected item it is current item in tree control that use HierarchicalDataTemplate.

这是解决方案。rtvEsa 是树视图。HierarchicalDataTemplate 是树视图模板标签,使当前项目真正消耗。这不是选定的项目,它是使用 HierarchicalDataTemplate 的树控件中的当前项目。

Items.CurrentItem is part of internal tree colection. You cant get many and diferent data. For example Items.ParenItem too.

Items.CurrentItem 是内部树集合的一部分。您无法获得许多不同的数据。例如 Items.ParenItem 也是。

  <HierarchicalDataTemplate ItemsSource="{Binding ChildItems}">

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