WPF Multiselect TreeView - 几乎完全实现,但是
我写了我自己的“多选”-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-compiler:
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 中后执行...但它有效。
I wrote my own "Multiselect"-Treeview. It uses an interface ITreeViewItem for all databound items. This treeview will not work without that the bound items will implement this interface.
public interface ITreeViewItem : INotifyPropertyChanged
{
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
bool IsEnabled { get; set; }
Visibility Visibility { get; set; }
ITreeViewItem GetParentTreeViewItem();
}
This is my first question: Has someone an idea how to not force the implementation of this interface? It seems not to be clean. The main reason why i decided to implement this interface is, that it seems impossible to find the ItemsControl matching to the data item without (in worst case) expanding whole tree. Which will in the case of lazy loaded data result in loading all this data.
Another reason is the coloring of the selected items background. If i want to highlight the background I need some property to bind on. A normal trigger on IsSelected seems not to work because only one item at a time can have this value set (or?).
About the last issue I already asked here WPF TreeViewItem Background The problem with the given answer is, that the default Template have not the default colors (on first click on an item the color will be light violett, on second it will be blue; which is another issue). So how can I set the Color of all selected items to light gray or of the actually focused item to blue after the treeview looses focus?
If you need some more information on my implementation feel free to ask.
EDIT: @Snowbear JIM-compiler:
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;
}
The bad thing about this code is, that the Action is only executed after leaving the calling event in the inherited TreeViewItem... but it works.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
可能它不能回答您所有的问题,但仍然:
据我了解您的意图 ItemContainerGenerator.ContainerFromItem 应该对您有帮助。它返回给定数据项的
TreeViewItem
。Probably it won't answer all your questions, but still:
As far as I got your intention ItemContainerGenerator.ContainerFromItem should help you. It returns
TreeViewItem
for given data item.