Как программно выбрать элемент в WPF TreeView? - PullRequest
45 голосов
/ 05 января 2009

Как можно программно выбрать элемент в WPF TreeView? Модель ItemsControl, кажется, предотвращает это.

Ответы [ 14 ]

0 голосов
/ 15 декабря 2015

Да .. Я знаю много лет назад с тех пор, как был задан вопрос, но .. все еще нет быстрого решения этой проблемы .. и так:

Следующее будет делать то, что просил OP.

Что я в основном сделал, так это прочитал все ответы на этой странице и перешел по всем соответствующим ссылкам, чтобы создать раз и навсегда решение этой раздражающей проблемы.

Преимущества:

  • Также поддерживается виртуализация TreeView.
  • Он использует технику поведения, поэтому XAML очень прост.
  • Добавляет свойство зависимости, чтобы разрешить привязку к выбранному элементу TreeView.


Эта часть является единственным кодом, который вам нужно скопировать, остальные части просто для того, чтобы помочь завершить пример.

public static class TreeViewSelectedItemExBehavior
{
    private static List<TreeView> isRegisteredToSelectionChanged = new List<TreeView>();

    public static readonly DependencyProperty SelectedItemExProperty =
        DependencyProperty.RegisterAttached("SelectedItemEx",
            typeof(object),
            typeof(TreeViewSelectedItemExBehavior),
            new FrameworkPropertyMetadata(new object(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemExChanged, null));

    #region SelectedItemEx

    public static object GetSelectedItemEx(TreeView target)
    {
        return target.GetValue(SelectedItemExProperty);
    }

    public static void SetSelectedItemEx(TreeView target, object value)
    {
        target.SetValue(SelectedItemExProperty, value);
        var treeViewItemToSelect = GetTreeViewItem(target, value);
        if (treeViewItemToSelect == null)
        {
            if (target.SelectedItem == null)
                return;
            var treeViewItemToUnSelect = GetTreeViewItem(target, target.SelectedItem);
            treeViewItemToUnSelect.IsSelected = false;
        }
        else
            treeViewItemToSelect.IsSelected = true;
    }

    public static void OnSelectedItemExChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        var treeView = depObj as TreeView;
        if (treeView == null)
            return;
        if (!isRegisteredToSelectionChanged.Contains(treeView))
        {
            treeView.SelectedItemChanged += TreeView_SelectedItemChanged;
            isRegisteredToSelectionChanged.Add(treeView);
        }
    }

    #endregion

    private static void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        var treeView = (TreeView)sender;
        SetSelectedItemEx(treeView, e.NewValue);
    }

    #region Helper Structures & Methods

    public class MyVirtualizingStackPanel : VirtualizingStackPanel
    {
        /// <summary>
        /// Publically expose BringIndexIntoView.
        /// </summary>
        public void BringIntoView(int index)
        {
            BringIndexIntoView(index);
        }
    }

    /// <summary>Recursively search for an item in this subtree.</summary>
    /// <param name="container">The parent ItemsControl. This can be a TreeView or a TreeViewItem.</param>
    /// <param name="item">The item to search for.</param>
    /// <returns>The TreeViewItem that contains the specified item.</returns>
    private static TreeViewItem GetTreeViewItem(ItemsControl container, object item)
    {
        if (container != null)
        {
            if (container.DataContext == item)
            {
                return container as TreeViewItem;
            }

            // Expand the current container
            if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
            {
                container.SetValue(TreeViewItem.IsExpandedProperty, true);
            }

            // Try to generate the ItemsPresenter and the ItemsPanel.
            // by calling ApplyTemplate.  Note that in the 
            // virtualizing case even if the item is marked 
            // expanded we still need to do this step in order to 
            // regenerate the visuals because they may have been virtualized away.

            container.ApplyTemplate();
            ItemsPresenter itemsPresenter =
                (ItemsPresenter)container.Template.FindName("ItemsHost", container);
            if (itemsPresenter != null)
            {
                itemsPresenter.ApplyTemplate();
            }
            else
            {
                // The Tree template has not named the ItemsPresenter, 
                // so walk the descendents and find the child.
                itemsPresenter = FindVisualChild<ItemsPresenter>(container);
                if (itemsPresenter == null)
                {
                    container.UpdateLayout();

                    itemsPresenter = FindVisualChild<ItemsPresenter>(container);
                }
            }

            Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);


            // Ensure that the generator for this panel has been created.
            UIElementCollection children = itemsHostPanel.Children;

            MyVirtualizingStackPanel virtualizingPanel =
                itemsHostPanel as MyVirtualizingStackPanel;

            for (int i = 0, count = container.Items.Count; i < count; i++)
            {
                TreeViewItem subContainer;
                if (virtualizingPanel != null)
                {
                    // Bring the item into view so 
                    // that the container will be generated.
                    virtualizingPanel.BringIntoView(i);

                    subContainer =
                        (TreeViewItem)container.ItemContainerGenerator.
                        ContainerFromIndex(i);
                }
                else
                {
                    subContainer =
                        (TreeViewItem)container.ItemContainerGenerator.
                        ContainerFromIndex(i);

                    // Bring the item into view to maintain the 
                    // same behavior as with a virtualizing panel.
                    subContainer.BringIntoView();
                }

                if (subContainer != null)
                {
                    // Search the next level for the object.
                    TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
                    if (resultContainer != null)
                    {
                        return resultContainer;
                    }
                    else
                    {
                        // The object is not under this TreeViewItem
                        // so collapse it.
                        subContainer.IsExpanded = false;
                    }
                }
            }
        }

        return null;
    }

    /// <summary>Search for an element of a certain type in the visual tree.</summary>
    /// <typeparam name="T">The type of element to find.</typeparam>
    /// <param name="visual">The parent element.</param>
    /// <returns></returns>
    private static T FindVisualChild<T>(Visual visual) where T : Visual
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
        {
            Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
            if (child != null)
            {
                T correctlyTyped = child as T;
                if (correctlyTyped != null)
                {
                    return correctlyTyped;
                }

                T descendent = FindVisualChild<T>(child);
                if (descendent != null)
                {
                    return descendent;
                }
            }
        }
        return null;
    }

    #endregion
}

И это пример того, как выглядит строка TreeView в XAML:

<TreeView x:Name="trvwSs"
          Grid.Column="2" Grid.Row="1" Margin="4" ItemsSource="{Binding ItemsTreeViewSs}"
          behaviors:TreeViewSelectedItemExBehavior.SelectedItemEx="{Binding SelectedItemTreeViewSs}" />

Единственное, о чем нужно беспокоиться, это убедиться, что ваше свойство модели представления, которое вы собираетесь связать с SelectedItemEx, не равно нулю. Но это не особый случай. Просто упомянул об этом на случай, если люди запутаются.

public class VmMainContainer : INotifyPropertyChanged
{
    private object selectedItemTreeViewSs = new object();
    private ObservableCollection<object> selectedItemsTreeViewSs = new ObservableCollection<object>();
    private ObservableCollection<VmItem> itemsTreeViewSs = new ObservableCollection<VmItem>();

 public object SelectedItemTreeViewSs
        {
            get
            {
                return selectedItemTreeViewSs;
            }
            set
            {
                selectedItemTreeViewSs = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItemTreeViewSs)));
            }
        }

public ObservableCollection<object> SelectedItemsTreeViewSs
        {
            get
            {
                return selectedItemsTreeViewSs;
            }
            set
            {
                selectedItemsTreeViewSs = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItemsTreeViewSs)));
            }
        }

 public ObservableCollection<VmItem> ItemsTreeViewSs
        {
            get { return itemsTreeViewSs; }
            set
            {
                itemsTreeViewSs = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ItemsTreeViewSs)));
            }
        }
    }

И последнее ... пример программного выбора: Я создал кнопку в своем MainWindow.xaml и из его обработчика ..

private void Button_Click(object sender, RoutedEventArgs e)
{
    TreeViewSelectedItemExBehavior.SetSelectedItemEx(trvwSs, trvwSs.Items[3]);
    //TreeViewSelectedItemExBehavior.SetSelectedItemEx(trvwSs, null);
}

Надеюсь, это кому-нибудь поможет:)

0 голосов
/ 14 августа 2014

Я создал метод VisualTreeExt.GetDescendants<T>, который возвращает перечисляемую коллекцию элементов, соответствующих указанному типу:

public static class VisualTreeExt
{
  public static IEnumerable<T> GetDescendants<T>(DependencyObject parent) where T : DependencyObject
  {
    var count = VisualTreeHelper.GetChildrenCount(parent);
    for (var i = 0; i < count; ++i)
    {
       // Obtain the child
       var child = VisualTreeHelper.GetChild(parent, i);
       if (child is T)
         yield return (T)child;

       // Return all the descendant children
       foreach (var subItem in GetDescendants<T>(child))
         yield return subItem;
    }
  }
}

Когда вы попросите VisualTreeHelperExt.GetDescendants<TreeViewItem>(MyAmazingTreeView), вы получите всех TreeViewItem детей. Вы можете выбрать конкретное значение, используя следующий фрагмент кода:

var treeViewItem = VisualTreeExt.GetDescendants<TreeViewItem>(MyTreeView).FirstOrDefault(tvi => tvi.DataContext == newValue);
if (treeViewItem != null)
  treeViewItem.IsSelected = true;

Это немного грязное решение (и, вероятно, не самое эффективное), и оно не будет работать, если вы используете виртуализированный TreeView, поскольку оно зависит от наличия реальных визуальных элементов. Но это работает для моей ситуации ...

0 голосов
/ 08 января 2013

Просто подумал, что я согласен с решением, с которым я пошел, на случай, если это может кому-нибудь помочь. Обратите внимание, что лучший способ сделать это - использовать связанное свойство, такое как IsSelected, согласно ответу kuninl, но в моем случае это было устаревшее приложение, которое не следовало MVVM, поэтому я в итоге получил следующее.

private void ChangeSessionSelection()
{
    foreach (SessionContainer item in this.treeActiveSessions.Items)
    {
        var treeviewItem = this.treeActiveSessions.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;

        if (item.Session == this.selectedSession.Session)
        {
            treeviewItem.IsSelected = true;
            treeviewItem.IsExpanded = true;
        }
        else
        {
            treeviewItem.IsSelected = false;
            treeviewItem.IsExpanded = false;
        }
    }            
}

Для этого выберите и разверните элемент дерева в пользовательском интерфейсе, который представляет выбранный элемент данных в коде. Целью этого было изменение выбора в древовидной структуре, когда выбор пользователя изменился в элементе управления в том же окне.

0 голосов
/ 01 июня 2011

Попробуйте с этим

    /// <summary>
    /// Selects the tree view item.
    /// </summary>
    /// <param name="Collection">The collection.</param>
    /// <param name="Value">The value.</param>
    /// <returns></returns>
    private TreeViewItem SelectTreeViewItem(ItemCollection Collection, String Value)
    {
        if (Collection == null) return null;
        foreach(TreeViewItem Item in Collection)
        {
            /// Find in current
            if (Item.Header.Equals(Value))
            {
                Item.IsSelected = true;
                return Item;
            }
            /// Find in Childs
            if (Item.Items != null)
            {
                TreeViewItem childItem = this.SelectTreeViewItem(Item.Items, Value);
                if (childItem != null)
                {
                    Item.IsExpanded = true;
                    return childItem;
                }
            }
        }
        return null;
    }

Ссылка: http://amastaneh.blogspot.com/2011/06/wpf-selectedvalue-for-treeview.html

...