Как выбрать элемент TreeView из кода - PullRequest
25 голосов
/ 02 июня 2009

У меня есть три уровня дерева. Как выбрать любой элемент третьего уровня из кода? Я попробовал метод, упомянутый во многих блогах и в 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 { }
}

Ответы [ 6 ]

31 голосов
/ 03 июня 2009

Другим вариантом будет использование привязки. Если у вас есть объект, с которым вы используете привязку для получения текста каждого TreeViewItem (например), вы можете создать стиль, который также связывает свойство IsSelected:

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

Предполагается, что связанный объект имеет свойство IsSelected типа bool. Затем вы можете выбрать TreeViewItem, установив IsSelected в true для соответствующего объекта.

Тот же подход можно использовать со свойством IsExpanded, чтобы управлять расширением или свертыванием TreeViewItem.

7 голосов
/ 03 марта 2015

Вы можете использовать следующее расширение 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/

4 голосов
/ 03 июня 2009

Попробовав различные решения, я пришел на этот сайт. Чжоу Юн показывает, как программно расширить все узлы TreeView. В его методе есть две основные идеи:

  • ContainerFromItem будет возвращать контейнер, только если элемент является прямым потомком элемента. В TreeView это означает, что будет возвращен только дочерний контейнер первого уровня, и вам нужно вызвать ContainerFromItem для дочернего TreeViewItem, чтобы получить контейнер со следующего уровня
  • Для работы 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);
        }
    }
}
1 голос
/ 20 декабря 2013

В моем случае (у меня была та же проблема), но было неуместно использовать привязку к свойству IsSelected объекта Data, а также я не мог легко получить путь к элементу дерева, поэтому следующий код отлично справился с задачей:

  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;
    }
0 голосов
/ 07 августа 2015

Очень поздно для участника с моим ответом, но для тех, кто хочет чисто MVVM-решение, это можно сделать с помощью Event Trigger (для обновления привязки, когда пользователь выбирает новый элемент) и Data Trigger (для обновления выбранного элемента когда значение привязки изменяется).

Чтобы это работало, основной 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 при изменении выбора и DataTrigger в стиле TreeViewItem, чтобы элементы управления выбирались при программном изменении значения 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();
    }
}
0 голосов
/ 03 июня 2009

Да, метод ContainerFromItem ничего не возвращает, даже если вы вызываете его из прямого родительского TreeViewItem.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...