Подтвердить что ты не робот

Как выбрать элемент TreeView из кода

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

Ответ 1

Другой вариант - использовать привязку. Если у вас есть объект, с которым вы используете привязку, чтобы получить текст каждого 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 расширяется или сбрасывается.

Ответ 2

После нескольких попыток я пришел в этот сайт. Чжоу Юн показывает, как программно расширить все узлы 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);
        }
    }
}

Ответ 3

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

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

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

Ответ 5

В моем случае (у меня была такая же проблема), но было неприемлемо использовать привязку к свойству 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;
    }

Ответ 6

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

Для этого основным 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();
    }
}