wpf 如何从代码中选择 TreeView 项目

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/940243/
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:30:20  来源:igfitidea点击:

How to select TreeView item from code

wpftreeview

提问by Sergej Andrejev

I have a three level treeview. How do I select any item in third level from code? I tried a method mentioned in many blogs and on stackoverflow but it seems to work only for first level (dbObject is null for items on below first level).

我有一个三级树视图。如何从代码中选择第三级的任何项目?我尝试了许多博客和 stackoverflow 上提到的方法,但它似乎只适用于第一级(dbObject 对于第一级以下的项目为 null)。

Here is the code I'm using to select TreeViewItem. Do I miss something?

这是我用来选择 TreeViewItem 的代码。我错过了什么吗?

public static void SetSelectedItem(this TreeView control, object item)
{
    try
    {
        var dObject = control.ItemContainerGenerator.ContainerFromItem(item);

        //uncomment the following line if UI updates are unnecessary
        ((TreeViewItem)dObject).IsSelected = true;

        MethodInfo selectMethod = typeof(TreeViewItem).GetMethod("Select",
            BindingFlags.NonPublic | BindingFlags.Instance);

        selectMethod.Invoke(dObject, new object[] { true });
    }
    catch { }
}

回答by Andy

Another option would be to use binding. If you have an object that you are using binding with to get the text of each TreeViewItem(for example), you can create a style that also binds the IsSelectedproperty:

另一种选择是使用绑定。如果您有一个对象使用绑定来获取每个对象的文本TreeViewItem(例如),您可以创建一个也绑定IsSelected属性的样式:

<TreeView>
    <TreeView.Resources>
        <Style TargetType="TreeViewItem">
            <Setter Property="IsSelected"
                    Value="{Binding Path=IsSelected, Mode=TwoWay}" />
        </Style>
    </TreeView.Resources>
</TreeView>

This assumes that the bound object has an IsSelectedproperty of type bool. You can then select a TreeViewItemby setting IsSelectedto truefor its corresponding object.

这假定绑定对象具有IsSelected类型的属性bool。然后,您可以TreeViewItem通过为其相应的对象设置IsSelectedtrue来选择一个。

The same approach can be used with the IsExpandedproperty to control when a TreeViewItemis expanded or collapsed.

相同的方法可以与IsExpanded属性一起使用来控制 a 何时TreeViewItem展开或折叠。

回答by kbisang

You can use the following TreeViewextension, which I find is a simpler solution:

您可以使用以下TreeView扩展名,我发现这是一个更简单的解决方案:

public static class TreeViewExtension
{
    public static bool SetSelectedItem(this TreeView treeView, object item)
    {
        return SetSelected(treeView, item);
    }

    private static bool SetSelected(ItemsControl parent, object child)
    {
       if (parent == null || child == null)
          return false;

       TreeViewItem childNode = parent.ItemContainerGenerator
       .ContainerFromItem(child) as TreeViewItem;

       if (childNode != null)
       {
          childNode.Focus();
          return childNode.IsSelected = true;
       }

       if (parent.Items.Count > 0) 
       {
          foreach (object childItem in parent.Items)
          {
             ItemsControl childControl = parent
               .ItemContainerGenerator
               .ContainerFromItem(childItem) 
               as ItemsControl;

             if (SetSelected(childControl, child))
               return true;
          }
       }

      return false;
   }
}

For more info, read this blog article; http://decompile.it/blog/2008/12/11/selecting-an-item-in-a-treeview-in-wpf/

有关更多信息,请阅读此博客文章; http://decompile.it/blog/2008/12/11/selecting-an-item-in-a-treeview-in-wpf/

回答by Sergej Andrejev

After trying different sollutions I came to thissite. Zhou Yong shows how to programatically expand all nodes of TreeView. There are two main ideas in his method:

在尝试了不同的解决方案后,我来到了这个网站。周勇展示了如何以编程方式扩展 TreeView 的所有节点。他的方法有两个主要思想:

  • ContainerFromItem will return container only if item is direct child of the element. In TreeView that means that only first level child container will be returned and you have to call ContainerFromItem on child TreeViewItem to get container from next level
  • For ContainerFromItem to work TreeViewItem visual children should be created and this happens only when TreeViewItem is expanded. That means that to select TreeViewItem all items preceding required item must be expanded. In practice that means that we will have to provide path to the item we want to select instead of just the item.
  • 只有当 item 是元素的直接子元素时,ContainerFromItem 才会返回容器。在 TreeView 中,这意味着只会返回第一级子容器,您必须在子 TreeViewItem 上调用 ContainerFromItem 才能从下一级获取容器
  • 要使 ContainerFromItem 工作,应创建 TreeViewItem 可视子项,并且仅在展开 TreeViewItem 时才会发生这种情况。这意味着要选择 TreeViewItem,必须展开所需项目之前的所有项目。在实践中,这意味着我们必须提供我们想要选择的项目的路径,而不仅仅是项目。

Here is the code I ended up with

这是我最终得到的代码

public static void SelectItem(this ItemsControl parentContainer, List<object> path)
{
    var head = path.First();
    var tail = path.GetRange(1, path.Count - 1);
    var itemContainer = parentContainer.ItemContainerGenerator.ContainerFromItem(head) as TreeViewItem;

    if (itemContainer != null && itemContainer.Items.Count == 0)
    {
        itemContainer.IsSelected = true;

        var selectMethod = typeof(TreeViewItem).GetMethod("Select", BindingFlags.NonPublic | BindingFlags.Instance);
        selectMethod.Invoke(itemContainer, new object[] { true });
    }
    else if (itemContainer != null)
    {
        itemContainer.IsExpanded = true;

        if (itemContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
        {
            itemContainer.ItemContainerGenerator.StatusChanged += delegate
            {
                SelectItem(itemContainer, tail);
            };
        }
        else
        {
            SelectItem(itemContainer, tail);
        }
    }
}

回答by Mladen Nikolov

In my case (I had the same problem) but it was unappropriate to use binding to IsSelected property of the Data object and also I couldnt easily get the path to the tree item, so the following code did the job perfectly:

在我的情况下(我遇到了同样的问题),但是使用绑定到 Data 对象的 IsSelected 属性是不合适的,而且我无法轻松获取树项的路径,因此以下代码完美地完成了这项工作:

  private void SelectTreeViewItem(object item)
    {
        try
        {
            var tvi = GetContainerFromItem(this.MainRegion, item);

            tvi.Focus();
            tvi.IsSelected = true;

            var selectMethod =
                typeof(TreeViewItem).GetMethod("Select",
                System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);

            selectMethod.Invoke(tvi, new object[] { true });
        }
        catch { }
    }

  private TreeViewItem GetContainerFromItem(ItemsControl parent, object item)
    {
        var found = parent.ItemContainerGenerator.ContainerFromItem(item);
        if (found == null)
        {
            for (int i = 0; i < parent.Items.Count; i++)
            {
                var childContainer = parent.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl;
                TreeViewItem childFound = null;
                if (childContainer != null && childContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
                {
                    childContainer.ItemContainerGenerator.StatusChanged += (o, e) =>
                        {
                             childFound = GetContainerFromItem(childContainer, item);
                        };
                }
                else
                {
                     childFound = GetContainerFromItem(childContainer, item);                            
                }
                if (childFound != null)
                    return childFound;                 
            }
        }
        return found as TreeViewItem;
    }

回答by RandomEngy

Yeah, the ContainerFromItem method isn't giving back anything, even when you call it from the direct parent TreeViewItem.

是的,ContainerFromItem 方法不会返回任何东西,即使您从直接父 TreeViewItem 调用它也是如此。

You may need to do a bit of redesign. If you create everything as an explicit TreeViewItem you should be able to keep a reference to it and set IsSelected on it.

您可能需要进行一些重新设计。如果您将所有内容创建为显式 TreeViewItem,您应该能够保留对它的引用并在其上设置 IsSelected。

回答by Mark Feldman

Very late to the party with my answer but for those wanting a pure MVVM solution this can be done with an Event Trigger (to update the binding when the user selects a new item) and a Data Trigger (to update the selected item when the value of the binding changes).

我的回答迟到了,但对于那些想要纯 MVVM 解决方案的人来说,这可以通过事件触发器(在用户选择新项目时更新绑定)和数据触发器(在值发生时更新所选项目)来完成绑定更改)。

For this to work the main ViewModel needs the items, a property for the currently selected item and a command property that will be called when the currently selected item changes:

为此,主 ViewModel 需要项目、当前选定项目的属性和当前选定项目更改时将调用的命令属性:

public class MainViewModel : ViewModelBase
{
    // the currently selected node, can be changed programmatically
    private Node _CurrentNode;
    public Node CurrentNode
    {
        get { return this._CurrentNode; }
        set { this._CurrentNode = value; RaisePropertyChanged(() => this.CurrentNode); }
    }

    // called when the user selects a new node in the tree view
    public ICommand SelectedNodeChangedCommand { get { return new RelayCommand<Node>(OnSelectedNodeChanged); } }
    private void OnSelectedNodeChanged(Node node)
    {
        this.CurrentNode = node;
    }

    // list of items to display in the tree view
    private ObservableCollection<Node> _Items;
    public ObservableCollection<Node> Items
    {
        get { return this._Items; }
        set { this._Items = value; RaisePropertyChanged(() => this.Items); }
    }
}

The TreeView needs an event trigger to call SelectedNodeChangedCommand when the selection changes, and a DataTrigger in the TreeViewItem style so that the control items get selected when the value of CurrentNode is changed programatically in the code:

TreeView 需要一个事件触发器在选择更改时调用 SelectedNodeChangedCommand 和 TreeViewItem 样式中的 DataTrigger 以便在代码中以编程方式更改 CurrentNode 的值时控件项被选中:

<TreeView x:Name="treeView" ItemsSource="{Binding Items}"
            xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
            xmlns:cmd ="http://www.galasoft.ch/mvvmlight">
        <TreeView.Resources>

            <conv:EqualityConverter x:Key="EqualityConverter" />

            <Style TargetType="TreeViewItem">
                <Setter Property="IsExpanded" Value="True" />
                <Setter Property="IsSelected" Value="False" />
                <Style.Triggers>
                    <!-- DataTrigger updates TreeViewItem selection when vm code changes CurrentNode -->
                    <DataTrigger Value="True">
                        <DataTrigger.Binding>
                            <MultiBinding Converter="{StaticResource EqualityConverter}">
                                <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type TreeView}}" Path="DataContext.CurrentNode" />
                                <Binding />
                            </MultiBinding>
                        </DataTrigger.Binding>
                        <Setter Property="IsSelected" Value="True" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>


            <!-- *** HierarchicalDataTemplates go here ***  -->

        </TreeView.Resources>

        <!-- EventTrigger invokes SelectedNodeChangedCommand when selection is changed by user interaction -->
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectedItemChanged">
                <cmd:EventToCommand Command="{Binding SelectedNodeChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=SelectedItem}"  />
            </i:EventTrigger>
        </i:Interaction.Triggers>

    </TreeView>

The DataTrigger works by detecting when the value of CurrentNode matches the Node for the current list item. Unfortunately DataTriggers can't bind their Value, so it has to test with an EqualityConverter instead which just does a simple comparison:

DataTrigger 的工作原理是检测 CurrentNode 的值何时与当前列表项的 Node 匹配。不幸的是,DataTriggers 不能绑定它们的值,所以它必须使用 EqualityConverter 进行测试,而这只是一个简单的比较:

    public class EqualityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        return values[0] == values[1];
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}