Wpf 如何从代码中选择TreeView项

Wpf 如何从代码中选择TreeView项,wpf,treeview,Wpf,Treeview,我有一个三层的树景。如何从代码中选择第三级中的任何项目?我尝试了许多博客和stackoverflow中提到的一种方法,但它似乎只适用于第一级(对于低于第一级的项目,dbObject为null) 下面是我用来选择TreeViewItem的代码。我错过什么了吗 public static void SetSelectedItem(this TreeView control, object item) { try { var dObject = control.Item

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

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

是的,ContainerFromItem方法不会返回任何东西,即使您从直接父树项调用它


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

在尝试了不同的解决方案后,我来到了该站点。周勇展示了如何通过编程扩展TreeView的所有节点。他的方法有两个主要观点:

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

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);
        }
    }
}
public static void SelectItem(此项控制父容器,列表路径)
{
var head=path.First();
var tail=path.GetRange(1,path.Count-1);
var itemContainer=parentContainer.ItemContainerGenerator.ContainerFromItem(head)作为TreeViewItem;
if(itemContainer!=null&&itemContainer.Items.Count==0)
{
itemContainer.IsSelected=true;
var selectMethod=typeof(treevieItem).GetMethod(“选择”,BindingFlags.NonPublic | BindingFlags.Instance);
调用(itemContainer,新对象[]{true});
}
else if(itemContainer!=null)
{
itemContainer.IsExpanded=true;
if(itemContainer.ItemContainerGenerator.Status!=GeneratorStatus.ContainerGenerated)
{
itemContainer.ItemContainerGenerator.StatusChanged+=委托
{
选择Item(itemContainer,tail);
};
}
其他的
{
选择Item(itemContainer,tail);
}
}
}

另一种选择是使用绑定。如果您有一个对象正在使用绑定来获取每个
treevieItem
(例如)的文本,您可以创建一个样式来绑定
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
何时展开或折叠。

在我的情况下(我也有同样的问题),但是使用绑定到数据对象的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;
    }
private void SelectTreeViewItem(对象项)
{
尝试
{
var tvi=GetContainerFromItem(this.MainRegion,item);
tvi.Focus();
tvi.IsSelected=true;
变量选择法=
typeof(TreeViewItem).GetMethod(“选择”,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
调用(tvi,新对象[]{true});
}
捕获{}
}
私有TreeViewItem GetContainerFromItem(ItemsControl父项,对象项)
{
var found=parent.ItemContainerGenerator.ContainerFromItem(item);
if(found==null)
{
对于(int i=0;i
{
childFound=GetContainerFromItem(childContainer,item);
};
}
其他的
{
childFound=GetContainerFromItem(childContainer,item);
}
if(childFound!=null)
返回找到的孩子;
}
}
返回被发现为TreeViewItem;
}

您可以使用以下
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;
   }
}
欲了解更多信息,请阅读此博客文章;

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

为此,主视图模型需要项目、当前选定项目的属性以及当前选定项目更改时将调用的命令属性:

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

是的,我知道这一点。但它似乎引入了代码耦合。不管怎样,你在这里有这个答案是很好的。浏览此页面的人可能更喜欢您的方式mine@Andy当前位置在Silverlight中如何执行此操作?我尝试此代码获取错误
无法设置只读属性“”。
@Navid我不确定是否可以。我
<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>
    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();
    }
}