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 设置
基本思想:使用具有两个级别的
模板进行模板化。第二层(叶子)和共享标题项的相同实例(见下文)。因此,选定的标题项目引用了完全相同的项目标题- 无需搜索。TreeView
HierarchicalDataTemplate
TreeView
ListView
IHeaederData
TreeView
ListView
实施概述:
您需要两个
ItemsControl
元素:带有节根节点(例如“文本编辑器”)
以及该部分的设置类别标题子节点(叶节点)(例如“字体”、“格式”)
TreeView
左侧导航窗格有 两层一个
ListView
用于实际设置及其类别标题。
然后设计数据类型来表示设置、设置标头和节根节点
让它们都实现一个
IData
具有共享属性的共同点(例如标头)让设置头数据类型实现一个额外的
IHeaderData
让设置数据类型实现一个额外的
ISettingData
让父节节点数据类型(根节点)用于实现具有子节点类型的
TreeView
附加节点ISectionData
IHeaderData
创建项目源集合(所有类型
IEnumerable<IData>
)TreeView
一个用于(仅保存类别)的每个父节节点,aSectionCollection
类型ISectionData
每个类别一个,一个
CategoryCollection
类型IHeaderData
单个用于设置数据和共享类别(标题数据),a
SettingCollection
类型IData
逐节填充已排序的源集合
将类型的节数据实例添加到的
ISectionData
源集合中SectionCollection
TreeView
将类型的共享类别数据头实例添加
IHeaderData
到两个源集合中CategoryCollection
,并且SettingCollection
将 type 的设置实例添加
ISettingData
到唯一SettingCollection
的对当前部分的所有类别重复最后两个步骤
将 分配给根节点
CategoryCollection
的子集合ISectionData
对所有部分重复这些步骤(及其类别和相应的设置)
将 绑定
SectionCollection
到TreeView
将 绑定
SettingsCollection
到LIstView
HierarchicalDataTemplate
为TreeView
数据创建一个ISectionData
类型为根的数据创建两个
DataTemplate
用于ListView
一个目标
IHeaderData
一个目标
ISettingData
逻辑:
当选择
IHeaderData
其中的一项时TreeView
ListView
使用获取此数据项的项目容器var container = ItemsContainerGenerator.GetContainerFromItem(selectedTreeViewCategoryItem)
将容器滚动到视图中
container.BringIntoView()
(实现视图外的虚拟化项目)将容器滚动到视图顶部
因为TreeView
和ListView
共享相同的类别标题数据 ( IHeaderData
),所以所选项目很容易跟踪和查找。您不必搜索设置组。您可以使用参考直接跳转到该组。这意味着数据的结构是解决方案的关键。
- 1 回答
- 0 关注
- 188 浏览
添加回答
举报