【问题标题】:How to select TreeView item from code如何从代码中选择 TreeView 项目
【发布时间】:2010-10-30 17:27:27
【问题描述】:

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

这是我用来选择 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 { }
}

【问题讨论】:

  • WPF 的 TreeView 牺牲了每一点的可用性来实现大多数人似乎大部分时间都不需要的功能...

标签: wpf treeview


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

    • ContainerFromItem 仅当 item 是元素的直接子元素时才会返回容器。在 TreeView 中,这意味着只会返回第一级子容器,您必须在子 TreeViewItem 上调用 ContainerFromItem 才能从下一级获取容器
    • 为使 ContainerFromItem 工作,应创建 TreeViewItem 可视子项,并且仅在展开 TreeViewItem 时才会发生这种情况。这意味着要选择 TreeViewItem 必须展开所需项目之前的所有项目。在实践中,这意味着我们必须提供要选择的项目的路径,而不仅仅是项目。

    这是我最终得到的代码

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

    【讨论】:

    • 看来我必须拥有我要选择的项目。然而,通常那个对象是不可用的;你只是有那个对象的一些ID,对吧?我不知道如何在没有实际项目实例的情况下调用 ContainerFromItem
    【解决方案3】:

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

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

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

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

    【讨论】:

    • 是的,我知道这一点。但看起来它引入了代码耦合。无论如何,你在这里有这个答案很好。访问此页面的人可能更喜欢你的方式而不是我的方式
    • @Andy:我如何在 Silverlight 中做到这一点?我尝试这段代码得到错误Cannot set read-only property ''.
    • @Navid 我不确定你是否可以。我对 Silverlight 做的不多,但我不认为 TreeViewItem.IsSelected 是 Silverlight 中的 DependencyProperty。您不能对不是 DependencyProperty 的属性使用绑定。
    • 似乎对我不起作用;程序选择总是自动重置为之前选择的内容,除非之前没有选择任何内容。我用示例代码创建了一个related question
    • @Andy-谢谢。你救了我。
    【解决方案4】:

    在我的情况下(我遇到了同样的问题),但使用绑定到 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;
        }
    

    【讨论】:

      【解决方案5】:

      您可以使用以下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;
         }
      }
      

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

      【讨论】:

      • 工作,不需要改变数据结构,但根本没有效率。
      • 效果很好,谢谢伙计!
      【解决方案6】:

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

      为此,主 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); }
          }
      }
      

      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>
      

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-05
        • 1970-01-01
        • 2010-09-29
        相关资源
        最近更新 更多