WPF: выберите TreeViewItem, сломанный за корневым уровнем - PullRequest
1 голос
/ 15 декабря 2009

Я пытаюсь выбрать TreeViewItem по идентификатору, но у меня не получается заставить его работать после первого (корневого) уровня. Я так много читал об этом и использую метод ниже.

private static bool SetSelected(ItemsControl parent, INestable itemToSelect) {
    if(parent == null || itemToSelect == null) {
        return false;
    }
    foreach(INestable item in parent.Items) {
        if(item.ID == itemToSelect.ID) { // just comparing instances failed
            TreeViewItem container = parent.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
            if(container != null) {
                container.IsSelected = true;
                container.Focus();
                return true;
            }
        }
        ItemsControl childControl = parent.ItemContainerGenerator.ContainerFromItem(item) as ItemsControl;
        if(SetSelected(childControl, itemToSelect))
            return true;
    }
    return false;
}

INestable - интерфейс базового уровня, реализованный IGroup и IAccount:

public interface INestable {
        string ID { get; set; }
    ...
}
public interface IAccount : INestable { 
    ...
}
public interface IGroup : INestable { 
    public IList<INestable> Children
    ...
}

Я думаю, что это как-то связано с табличками данных (возможно):

<HierarchicalDataTemplate DataType="{x:Type loc:IGroup}" ItemsSource="{Binding Children}" x:Key="TreeViewGroupTemplate">
<HierarchicalDataTemplate DataType="{x:Type loc:IAccount}" x:Key="TreeViewAccountTemplate">

The Template selector for the treeview returns thr group template for IGroups and the account template for IAccounts:
<conv:TreeTemplateSelector x:Key="TreeTemplateSelector" AccountTemplate="{StaticResource TreeViewAccountTemplate}" GroupTemplate="{StaticResource TreeViewGroupTemplate}"/>
<TreeView ItemTemplateSelector="{StaticResource TreeTemplateSelector}">

Работает для всех элементов верхнего уровня, только ничего ниже, и отладка подтверждает parent.ItemContainerGenerator действительно содержит элементы для всех уровней.

Я знаю, что кода много, но я сжигаю часы, пытаясь заставить его работать. Спасибо за любую помощь. :)

Ответы [ 3 ]

1 голос
/ 18 декабря 2009

Проблема в том, что вложенные ItemContainerGenerators создаются не в начале, а по запросу. И даже более того, они генерируются в отдельном потоке, поэтому вы должны прослушать StatusChanged в генераторе, чтобы убедиться, что он готов = (

Некоторые люди предлагают поиграть с Dispatcher (, как в посте этого Беа ). Я пытался реализовать решение Dispatcher, но по какой-то причине оно не сработало ... генераторы по-прежнему пусты = (

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

    private static bool SetSelected(TreeView treeView, ItemsControl parentControl, INestable itemToSelect)
    {
        if (parentControl == null || itemToSelect == null)
        {
            return false;
        }
        foreach (INestable item in parentControl.Items)
        {
            TreeViewItem container = parentControl.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;

            if (item.ID == itemToSelect.ID)
            { // just comparing instances failed
                    container.IsSelected = true;
                    container.Focus();
                    return true;
            }
            container.IsExpanded = true;
            treeView.UpdateLayout();
            WaitForPriority(DispatcherPriority.Background);
            if (SetSelected(treeView, container, itemToSelect))
                return true;
            else
                container.IsExpanded = false;
        }
        return false;
    }
0 голосов
/ 16 октября 2012

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

Как упоминалось ранее, проблема заключается в том, что TreeViewItem создается в другом потоке, отличном от диспетчерского.

Я лично считаю, что правильное решение является более сложным. Я знаю, что плохо слышать, но я действительно думаю, что это так. Мой код должен работать при виртуализации или нет. Также вы можете удалить любую ссылку, которая не нужна (я не проверял).

Мое решение основано на модели данных, в которой каждый узел наследует один и тот же корень: объект MultiSimBase, но это не является обязательным требованием.

Все начинается с SetSelectedTreeViewItem (), который активирует (+ устанавливает фокус и отображает) вновь добавленный элемент.

Надеюсь, это могло бы помочь или вдохновить ... Счастливое кодирование !!!

Код формы:

    //  ******************************************************************
    private List<MultiSimBase> SetPathListFromRootToNode(MultiSimBase multiSimBase, List<MultiSimBase> listTopToNode = null)
    {
        if (listTopToNode == null)
        {
            listTopToNode = new List<MultiSimBase>();
        }

        listTopToNode.Insert(0, multiSimBase);
        if (multiSimBase.Parent != null)
        {
            SetPathListFromRootToNode(multiSimBase.Parent, listTopToNode);
        }

        return listTopToNode;
    }

    // ******************************************************************
    private void SetSelectedTreeViewItem(MultiSimBase multiSimBase)
    {
        List<MultiSimBase> listOfMultiSimBasePathFromRootToNode = SetPathListFromRootToNode(multiSimBase);

        TreeViewStudy.SetItemHierarchyVisible(listOfMultiSimBasePathFromRootToNode, (tvi) =>
                                                                                    {
                                                                                        tvi.IsSelected = true;
                                                                                        tvi.Focus();
                                                                                        tvi.BringIntoView();
                                                                                    });
    }

А теперь общий код:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;

namespace HQ.Util.Wpf.WpfUtil
{
    public static class TreeViewExtensions
    {
        public delegate void OnTreeViewVisible(TreeViewItem tvi);

        private static void SetItemHierarchyVisible(ItemContainerGenerator icg, IList listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
        {
            Debug.Assert(icg != null);

            if (icg != null)
            {
                if (listOfRootToNodePath.Count == 0) // nothing to do
                    return;

                TreeViewItem tvi = icg.ContainerFromItem(listOfRootToNodePath[0]) as TreeViewItem;
                if (tvi != null) // Due to threading, always better to verify
                {
                    listOfRootToNodePath.RemoveAt(0);

                    if (listOfRootToNodePath.Count == 0)
                    {
                        if (onTreeViewVisible != null)
                            onTreeViewVisible(tvi);
                    }
                    else
                    {
                        if (!tvi.IsExpanded)
                            tvi.IsExpanded = true;

                        SetItemHierarchyVisible(tvi.ItemContainerGenerator, listOfRootToNodePath, onTreeViewVisible);
                    }
                }
                else
                {
                    ActionHolder actionHolder = new ActionHolder();
                    EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
                                    {
                                        var icgSender = sender as ItemContainerGenerator;
                                        tvi = icgSender.ContainerFromItem(listOfRootToNodePath[0]) as TreeViewItem;
                                        if (tvi != null) // Due to threading, it is always better to verify
                                        {
                                            SetItemHierarchyVisible(icg, listOfRootToNodePath, onTreeViewVisible);

                                            actionHolder.Execute();
                                        }
                                    };

                    actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
                    icg.StatusChanged += itemCreated;
                    return;
                }
            }
        }

        // ******************************************************************
        /// <summary>
        /// You cannot rely on this method to be synchronous. If you have any action that depend on the TreeViewItem 
        /// (last item of collectionOfRootToNodePath) to be visible, you should set it in the 'onTreeViewItemVisible' method.
        /// This method should work for Virtualized and non virtualized tree.
        /// </summary>
        /// <param name="treeView">TreeView where  an item has to be set visible</param>
        /// <param name="collectionOfRootToNodePath">Any of collection that implement ICollection like a generic List.
        /// The collection should have every objet of the path to the targeted item from the top to the target.
        /// For example for an apple tree: AppleTree (index 0), Branch4, SubBranch3, Leaf2 (index 3)</param>
        /// <param name="onTreeViewVisible">Optionnal</param>
        public static void SetItemHierarchyVisible(this TreeView treeView, IList listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
        {
            ItemContainerGenerator icg = treeView.ItemContainerGenerator;
            if (icg == null)
                return; // Is tree loaded and initialized ???

            SetItemHierarchyVisible(icg, listOfRootToNodePath, onTreeViewVisible);
        }

И

    using System;

namespace HQ.Util.Wpf.WpfUtil
{
    // Requested to unsubscribe into an anonymous method that is a delegate used for a one time execution
    // http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/df2773eb-0cc1-4f3a-a674-e32f2ef2c3f1/
    public class ActionHolder
    {
        public void Execute()
        {
            if (Action != null)
            {
                Action();
            }
        }

        public Action Action { get; set; }
    }
}
0 голосов
/ 18 декабря 2009

Я думаю, что это не работает, так как элемент свернут, а его контейнер не создан. Таким образом, попытка прямого выбора TreeViewItem - определенно не лучший путь.

Вместо этого мы используем подход MVVM. Каждый объект viewmodel должен иметь свойство IsSelected. Затем вы привязываете TreeViewItem.IsSelected к нему.

В вашем случае это будет выглядеть так

CS:

public interface INestable : INotifyPropertyChanged
{
  string ID { get; set; }

  // Make sure you invoke PropertyChanged in setter
  bool IsSelected { get; set; } 

  event PropertyChangedEventHandler PropertyChanged;
  ...
}

XAML:

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

Теперь вы можете просмотреть вашу модель и установить там свойство IsSelected.

Вы также можете отслеживать IsExpanded свойство таким же образом ...

Чтобы получить больше информации о TreeView, прочитайте эту замечательную статью Джоша Смита: Упрощение WPF TreeView с помощью шаблона ViewModel

Надеюсь, это поможет.

...