1 回答
TA贡献1805条经验 获得超9个赞
左侧的树(目录)具有根节点(例如“TextEditor”部分)。每个部分都包含设置类别(例如“格式”)。右侧ListView(设置视图)的项目具有组标题,其类别名称与目录名称相匹配(例如,格式)。
1. 编辑以解决使用PropertyGroupDescription
假设:
在 a 内部存在一个
CollectionViewSource定义ResourceDictionary并命名为CollectionViewSource。设置数据项具有属性
SettingsCategoryName(例如格式)。的
SettingsCategoryName的SelectedItem绑定TreeView到一个属性SelectedSettingsCategoryName
查看.xaml:
<ResourceDictionary>
<CollectionViewSource x:Key="CollectionViewSource" Source="{Binding Settings}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="SettingsCategoryName"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</ResourceDictionary>
<ListView x:Name="ListView" ItemsSource="{Binding Source={StaticResource CollectionViewSource}}">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold"
FontSize="14"
Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
View.xaml.cs:
找到选定的类别并将其滚动到视口的顶部。
// Scroll the selected section to top when the selected item has changed
private void ScrollToSection()
{
CollectionViewSource viewSource = FindResource("CollectionViewSource") as CollectionViewSource;
CollectionViewGroup selectedGroupItemData = viewSource
.View
.Groups
.OfType<CollectionViewGroup>()
.FirstOrDefault(group => group.Name.Equals(this.SelectedSettingsCategoryName));
GroupItem selectedroupItemContainer = this.ListView.ItemContainerGenerator.ContainerFromItem(selectedGroupItemData) as GroupItem;
ScrollViewer scrollViewer;
if (!TryFindCildElement(this.ListView, out scrollViewer))
{
return;
}
// Subscribe to scrollChanged event
// because the scroll executed by `BringIntoView` is deferred.
scrollViewer.ScrollChanged += ScrollSelectedGroupToTop;
selectedGroupItemContainer?.BringIntoView();
}
private void ScrollSelectedGroupToTop(object sender, ScrollChangedEventArgs e)
{
ScrollViewer scrollViewer;
if (!TryFindCildElement(this.ListView, out scrollViewer))
{
return;
}
scrollViewer.ScrollChanged -= ScrollGroupToTop;
var viewSource = FindResource("CollectionViewSource") as CollectionViewSource;
CollectionViewGroup selectedGroupItemData = viewSource
.View
.Groups
.OfType<CollectionViewGroup>()
.FirstOrDefault(group => group.Name.Equals(this.SelectedSettingsCategoryName));
var groupIndex = viewSource
.View
.Groups.IndexOf(selectedGroupItemData);
var absoluteVerticalScrollOffset = viewSource
.View
.Groups
.OfType<CollectionViewGroup>()
.TakeWhile((group, index) => index < groupIndex)
.Sum(group =>
(this.ListView.ItemContainerGenerator.ContainerFromItem(group) as GroupItem)?.ActualHeight
?? 0
);
scrollViewer.ScrollToVerticalOffset(absoluteVerticalScrollOffset);
}
// Generic method to find any `DependencyObject` in the visual tree of a parent element
private bool TryFindCildElement<TElement>(DependencyObject parent, out TElement resultElement) where TElement : DependencyObject
{
resultElement = null;
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is Popup popup)
{
childElement = popup.Child;
}
if (childElement is TElement)
{
resultElement = childElement as TElement;
return true;
}
if (TryFindCildElement(childElement, out resultElement))
{
return true;
}
}
return false;
}
您可以将此方法移至ListView派生类型中。然后将 a 添加到处理路由命令的CommandBindings新自定义中,例如。将 模板化为 a并让它们发出命令以将节名称传递给自定义.ListViewScrollToSectionRoutedCommandTreeViewItemsButtonCommandParameterListView
备注
由于使用PropertyGroupDescription结果会产生混合数据类型的项目源(GroupItemData对于组标头以及实际数据项目),因此托管的 UI 虚拟化ItemsControl已禁用且不可能(。在这种情况下,附加属性ScrollViewer.CanContentScroll会自动设置为False(强制)。对于大列表来说,这可能是一个巨大的缺点,也是采用替代方法的原因。
2.替代解决方案(支持UI虚拟化)
当涉及到实际设置结构的设计时,存在多种可能的变化。它可以是一棵树,其中每个类别标题节点都有自己的子节点,这些子节点表示类别的设置,也可以是一个平面列表结构,其中类别标题和设置都是同级的。为了使示例简单起见,我选择第二个选项:平面列表数据结构。
2.1 设置
基本思想:使用具有两个级别的
模板进行模板化。第二层(叶子)和共享标题项的相同实例(见下文)。因此,选定的标题项目引用了完全相同的项目标题- 无需搜索。TreeViewHierarchicalDataTemplateTreeViewListViewIHeaederDataTreeViewListView
实施概述:
您需要两个
ItemsControl元素:带有节根节点(例如“文本编辑器”)
以及该部分的设置类别标题子节点(叶节点)(例如“字体”、“格式”)
TreeView左侧导航窗格有 两层一个
ListView用于实际设置及其类别标题。
然后设计数据类型来表示设置、设置标头和节根节点
让它们都实现一个
IData具有共享属性的共同点(例如标头)让设置头数据类型实现一个额外的
IHeaderData让设置数据类型实现一个额外的
ISettingData让父节节点数据类型(根节点)用于实现具有子节点类型的
TreeView附加节点ISectionDataIHeaderData
创建项目源集合(所有类型
IEnumerable<IData>)TreeView一个用于(仅保存类别)的每个父节节点,aSectionCollection类型ISectionData每个类别一个,一个
CategoryCollection类型IHeaderData单个用于设置数据和共享类别(标题数据),a
SettingCollection类型IData
逐节填充已排序的源集合
将类型的节数据实例添加到的
ISectionData源集合中SectionCollectionTreeView将类型的共享类别数据头实例添加
IHeaderData到两个源集合中CategoryCollection,并且SettingCollection将 type 的设置实例添加
ISettingData到唯一SettingCollection的对当前部分的所有类别重复最后两个步骤
将 分配给根节点
CategoryCollection的子集合ISectionData对所有部分重复这些步骤(及其类别和相应的设置)
将 绑定
SectionCollection到TreeView将 绑定
SettingsCollection到LIstViewHierarchicalDataTemplate为TreeView数据创建一个ISectionData类型为根的数据创建两个
DataTemplate用于ListView一个目标
IHeaderData一个目标
ISettingData
逻辑:
当选择
IHeaderData其中的一项时TreeViewListView使用获取此数据项的项目容器var container = ItemsContainerGenerator.GetContainerFromItem(selectedTreeViewCategoryItem)将容器滚动到视图中
container.BringIntoView()(实现视图外的虚拟化项目)将容器滚动到视图顶部
因为TreeView和ListView共享相同的类别标题数据 ( IHeaderData),所以所选项目很容易跟踪和查找。您不必搜索设置组。您可以使用参考直接跳转到该组。这意味着数据的结构是解决方案的关键。
- 1 回答
- 0 关注
- 327 浏览
添加回答
举报
