Я написал свой собственный "Multiselect" -Treeview. Он использует интерфейс ITreeViewItem для всех элементов данных. Это древовидное представление не будет работать без того, что связанные элементы будут реализовывать этот интерфейс.
public interface ITreeViewItem : INotifyPropertyChanged
{
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
bool IsEnabled { get; set; }
Visibility Visibility { get; set; }
ITreeViewItem GetParentTreeViewItem();
}
Это мой первый вопрос: есть ли у кого-нибудь идеи, как не форсировать реализацию этого интерфейса? Кажется, это не чисто. Основная причина, по которой я решил реализовать этот интерфейс, заключается в том, что кажется невозможным найти ItemsControl, соответствующий элементу данных, без (в худшем случае) расширения всего дерева. Что в случае с отложенной загрузкой данных приведет к загрузке всех этих данных.
Другая причина - раскраска фона выделенных предметов. Если я хочу выделить фон, мне нужно привязать некоторые свойства. Кажется, что обычный триггер на IsSelected не работает, потому что только один элемент за раз может иметь это значение (или?).
О последнем вопросе, который я уже задал здесь WPF TreeViewItem Background Проблема с данным ответом состоит в том, что шаблон по умолчанию не имеет цветов по умолчанию (при первом нажатии на элемент цвет будет светло-фиолетовым , на втором он будет синим, что является еще одной проблемой). Итак, как я могу установить цвет всех выбранных элементов на светло-серый или фактически сфокусированный элемент на синий после того, как древовидная структура потеряет фокус?
Если вам нужна дополнительная информация о моей реализации, не стесняйтесь спрашивать.
РЕДАКТИРОВАТЬ: @Snowbear JIM-компилятор:
internal static bool ExecuteOnTreeViewItem(this TreeView tree, ITreeViewItem dataItem, Action<TreeViewItem> action)
{
Stack<ITreeViewItem> pathToRoot = GetPathToRoot(dataItem);
TreeViewItem firstVisibleItem = tree.SyncTreeLevels(pathToRoot);
if (firstVisibleItem == null)
return false; // can't find any item in path which is currently selectable
if (pathToRoot.Count != 0) // expand the first item if nextLevelItems will follow
firstVisibleItem.IsExpanded = true;
ExecuteOnTreeViewItem(tree, pathToRoot, action, firstVisibleItem);
return true;
}
private static void ExecuteOnTreeViewItem(TreeView tree, Stack<ITreeViewItem> pathToRoot, Action<TreeViewItem> action, TreeViewItem treeViewItem)
{
if (pathToRoot.Count == 0)
{
action(treeViewItem);
return;
}
var nextLevelItem = pathToRoot.Pop();
if (pathToRoot.Count != 0)
nextLevelItem.IsExpanded = true; // make children visible to create the item containers
if (treeViewItem.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
EventHandler eventHandler = null;
if (!treeViewItem.IsExpanded)
treeViewItem.IsExpanded = true;
eventHandler = delegate
{
if (treeViewItem.ItemContainerGenerator.Status == GeneratorStatus.Error
|| treeViewItem.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
treeViewItem.ItemContainerGenerator.StatusChanged -= eventHandler;
if (treeViewItem.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
var nextTreeViewItem = treeViewItem.ItemContainerGenerator.ContainerFromItem(nextLevelItem) as TreeViewItem;
ExecuteOnTreeViewItem(tree, pathToRoot, action, nextTreeViewItem);
}
};
treeViewItem.ItemContainerGenerator.StatusChanged += eventHandler;
}
else
{
var nextTreeViewItem = treeViewItem.ItemContainerGenerator.ContainerFromItem(nextLevelItem) as TreeViewItem;
ExecuteOnTreeViewItem(tree, pathToRoot, action, nextTreeViewItem);
}
}
internal static Stack<ITreeViewItem> GetPathToRoot(ITreeViewItem item)
{
Stack<ITreeViewItem> items = new Stack<ITreeViewItem>();
ITreeViewItem parent = item;
while (parent != null)
{
items.Push(parent);
parent = parent.GetParentTreeViewItem();
}
return items;
}
/// <summary>
/// Returns the first item in stack which is visible in tree view. This is used
/// for the case that the first Item of ITreeViewItem is not the first bound item
/// </summary>
/// <param name="tree"></param>
/// <param name="hierachyItems"></param>
/// <returns></returns>
internal static TreeViewItem SyncTreeLevels(this TreeView tree, Stack<ITreeViewItem> hierachyItems)
{
TreeViewItem item = null;
while (item == null && hierachyItems.Count > 0)
item = tree.ItemContainerGenerator.ContainerFromItem(hierachyItems.Pop()) as TreeViewItem;
return item;
}
Плохая вещь в этом коде состоит в том, что действие выполняется только после выхода из вызывающего события в унаследованном TreeViewItem ... но оно работает.