使用HierarchicalDataTemplates与TreeViewItem控件模板配合使用

6

我正在尝试对以下TreeView项布局进行模板化,但遇到了一些困难:

TreeView项布局模拟

我有几个项目,SearchList,其中包含一个Search集合,该集合包含一个DataSet集合(有点类似,但这不是重点)。 我遇到的问题是如何按照自己的想法为每个节点级别设置样式。我正在使用MVVM,TreeViews ItemsSource属性设置为SearchListViewModels的ObservableCollection,而SearchListViewModels又包含整个对象树。

我可以成功地将SearchList HierarchicalDataTemplate样式化以正确显示它们。 我卡在了SearchTerm节点的样式上。我希望DataSets在SearchTerm内容区域的右侧以WrapPanel或UniformGrid的形式呈现出来(我还没有决定),我已修改了TreeViewItem控件模板以实现此目的(我认为),但是如果我将其设置在Search HierarchicalDataTemplate的ItemContainerStyle属性中,则什么也不会发生。 显示的只是Search的内容。

我修改后的TreeViewItem模板

<Style TargetType="{x:Type TreeViewItem}" x:Key="AlteredTreeViewItem">
    <Setter Property="HorizontalContentAlignment"
        Value="Stretch" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TreeViewItem}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"
                            MinWidth="19" />
                        <ColumnDefinition Width="0.414*" />
                        <ColumnDefinition Width="0.586*"/>
                    </Grid.ColumnDefinitions>
                    <Border x:Name="Bd" HorizontalAlignment="Stretch"
                        Grid.Column="1" Grid.ColumnSpan="1" Background="#7F058956">
                        <ContentPresenter x:Name="PART_Header" Margin="10,0" />
                    </Border>
                    <WrapPanel x:Name="ItemsHost"
                        Grid.Column="2" IsItemsHost="True"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

我的搜索分层数据模板

    <HierarchicalDataTemplate DataType="{x:Type local:SearchViewModel}"  ItemsSource="{Binding MySearch.Custodians}" ItemContainerStyle="{StaticResource AlteredTreeViewItem}">
        <TextBlock Text="{Binding MySearch.SearchName}" Foreground="Black" FontFamily="Arial" FontSize="16"/>
    </HierarchicalDataTemplate>

当然可以同时以不同的样式呈现,同时也可以以不同的方式布局子项。如何实现这一点呢?
2个回答

7
看起来你已经接近目标了。我根据你发布的代码试图重新创建你的场景,发现了一些问题(当然,这是基于我对你发布的代码的理解)。
  • 你缺少ContentPresenterContentSource="Header"部分
  • 我认为你在错误的HierarchicalDataTemplate层次上应用了ItemContainerStyle。它应该在父级上指定,以影响子级(在你的情况下是SearchListViewModel)。
  • TreeViewItem的默认Template在一个Auto大小的ColumnDefinition中布置ContentPresenter,因此,除非你也修改父级的ItemContainerStyle,否则WrapPanel不会成功换行。我在下面的示例中将其更改为一个UniformGrid

通过以上更改和其他一些事项,我得到了一个结果,看起来与你的要求相当接近。

enter image description here

我在这里上传了示例解决方案:https://www.dropbox.com/s/4v2t8imikkagueb/TreeViewAltered.zip?dl=0

下面是它的Xaml代码(代码太多,无法全部发布)

<Window.Resources>
    <!-- DataSet-->
    <HierarchicalDataTemplate DataType="{x:Type data:DataSet}">
        <Border BorderThickness="3"
                BorderBrush="Gray"
                Background="Green">
            <TextBlock Text="{Binding Path=Tables[0].TableName}"
                       Margin="5"/>
        </Border>
    </HierarchicalDataTemplate>

    <!-- SearchViewModel -->
    <HierarchicalDataTemplate DataType="{x:Type viewModel:SearchViewModel}"
                              ItemsSource="{Binding DataSets}">
        <TextBlock Text="{Binding DisplayName}"
                   Foreground="Black"
                   FontFamily="Arial"
                   FontSize="16"/>
    </HierarchicalDataTemplate>

    <!-- SearchListViewModel -->
    <HierarchicalDataTemplate DataType="{x:Type viewModel:SearchListViewModel}"
                              ItemsSource="{Binding SearchList}">
        <HierarchicalDataTemplate.ItemContainerStyle>
            <Style TargetType="TreeViewItem">
                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type TreeViewItem}">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" MinWidth="19" />
                                    <ColumnDefinition Width="0.414*" />
                                    <ColumnDefinition Width="0.586*"/>
                                </Grid.ColumnDefinitions>
                                <Border x:Name="Bd"
                                        HorizontalAlignment="Stretch" 
                                        Grid.Column="1"
                                        Grid.ColumnSpan="1"
                                        Background="#7F058956">
                                    <ContentPresenter x:Name="PART_Header"
                                                      ContentSource="Header"
                                                      HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
                                </Border>
                                <UniformGrid x:Name="ItemsHost"
                                             Grid.Column="2"
                                             Columns="3"
                                             IsItemsHost="True"/>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </HierarchicalDataTemplate.ItemContainerStyle>
        <TextBlock Text="{Binding DisplayName}"
                   FontSize="20"/>
    </HierarchicalDataTemplate>
</Window.Resources>
<Grid>
    <TreeView ItemsSource="{Binding SearchListViewModels}" />
</Grid>

看起来很不错!我明天会仔细看一下。谢谢! - CodeWarrior
感谢Meleak提供了一个很好的解决方案。它像广告一样工作,并向我展示了我的错误之处。只需要再稍微调整一下样式,我现在就有了一种令人愉悦的方式来在更紧凑的空间中可视化大量层次数据。享受额外的声望吧! - CodeWarrior
@CodeWarrior:没问题,很高兴我能帮到你 :) - Fredrik Hedblad
@CodeWarrior:您是否需要其他信息来颁发悬赏?只是想确认一下,因为您之前的评论表明您计划颁发悬赏,并且它将在明天结束 :) - Fredrik Hedblad
抱歉,我以为将其标记为答案会授予赏金,但事实并非如此。已完成。感谢回答。 - CodeWarrior
很棒的解决方案!我会做一件事,就是将一个新的UserControl作为<ControlTemplate .../>元素的内容。 - IAbstract

3
很久以前我曾尝试创建一个类似的界面,所学到的是使用 ListBox 要比使用 TreeView 更好。为什么呢?
  1. 如果您只有一层展开(正如您的示例中所显示的),您将可以更好地控制布局,因为您只需要一个单独的 DataTemplate 进行样式设置。

  2. 自定义 ListBoxTreeView 更容易,因为您不必关注 GridViewColumnHeaderGridViewColumnPresenters 等细节。

要实现展开部分(这就是您最初选择 TreeView 的原因),只需使用两行定义的 Grid 并在第二行使用 ExpanderToggleButtonIsChecked 属性进行绑定即可。参见下面的示例,是我从我的日志查看器中获取的。

<DataTemplate>
    <Grid Margin="0,0,0,3" Grid.IsSharedSizeScope="True">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="30" SharedSizeGroup="SSG_TimeIcon"/>
            <ColumnDefinition Width="120" SharedSizeGroup="SSG_Time"/>
            <ColumnDefinition Width="30" SharedSizeGroup="SSG_LevelIcon"/>
            <ColumnDefinition Width="70" SharedSizeGroup="SSG_Level"/>
            <ColumnDefinition Width="*" SharedSizeGroup="SSG_Message"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <!-- ProgramTime -->
        <Rectangle Grid.Column="0" Grid.Row="0" Margin="0,0,0,0" Width="16" Height="16" VerticalAlignme="Top"  HorizoalAlignme="Stretch" Fill="{StaticResource Icon_Timer}"/>
        <TextBlock Grid.Column="1" Grid.Row="0" Margin="5,0,0,0" VerticalAlignme="Top" HorizoalAlignme="Stretch" Text="{Binding Path=TimeStamp, Converter={StaticResource ObjectToStringConverter}}" ToolTip="{Binding Path=ProgramTime}"/>
        <!-- Level -->
        <Rectangle Grid.Column="2" Grid.Row="0" Margin="10,0,0,0" Width="16" Height="16" VerticalAlignme="Top" HorizoalAlignme="Stretch" Fill="{Binding Path=Level, Converter={StaticResource MappingConverterNinjaLogLevelEnumToBrushResource}}"/>
        <TextBlock Grid.Column="3" Grid.Row="0" Margin="5,0,0,0" Text="{Binding Path=LevelFriendlyName}" VerticalAlignme="Top" HorizoalAlignme="Stretch"/>
        <!-- Message -->
        <StackPanel Grid.Column="4" Grid.Row="0" Margin="10,0,0,0" Orieation="Horizoal" >
            <TextBlock Margin="0,0,0,0" Text="{Binding Path=LogMessage}" TextWrapping="Wrap" VerticalAlignme="Top"  HorizoalAlignme="Stretch"/>
            <ToggleButton x:Name="ExpandExceptiooggleButton" VerticalAlignme="Top" Margin="5,0,0,0" IsChecked="False" 
                          Coe="Show Details" Tag="Hide Details" Style="{StaticResource TextButtonStyle}"
                          Foreground="{StaticResource BlueBrush}" Background="{StaticResource RedBrush}"
                          Visibility="{Binding Path=HasException, Converter={StaticResource BoolToVisibilityConverter}}" />
        </StackPanel>
        <Expander IsExpanded="{Binding Path=IsChecked, ElemeName=ExpandExceptiooggleButton}" Style="{StaticResource CoeExpanderStyle}" 
                  Margin="10,0,0,0" Grid.Column="4" Grid.Row="1">
            <Border BorderBrush="{StaticResource DarkGreyBrush}" BorderThickness="1,0,0,0">                                
                <TextBlock Text="{Binding Path=Exception}" Margin="5,0,0,0"/>
            </Border>
        </Expander>
    </Grid>
</DataTemplate>

你能看到定义标题和可展开主体变得更加容易了吗?如果你需要嵌套的数据,可以在你的视图模型中添加一个 Level 属性(你正在使用 MVVM 吗?!),然后创建一个 IValueConverter,返回一个 Margin(即 Thickness)以模拟缩进。


是的,我本来希望能够使用TreeView,因为我很快就会有一个需要在相当长的层次结构中布局项目的要求,而ListBox将不再可行。我知道它对于三级层次结构中的项目非常有效,但对于20级层次结构中的项目呢?我希望以真正的树形结构(项目从父项分支出)布置即将到来的视图,并且似乎TreeView是最好的选择,因为它专门设计用于托管像那样按层次排列的项目。 - CodeWarrior
除非我需要列,否则我仍然会继续使用 ListBox。我创建了一个文件管理器(用于在我们的游戏编辑器/工具中显示资产),使用“虚拟缩进”方法,并且必须能够显示20,000多个项目(显然使用虚拟化),这些项目可以是 n 层深度(其中 n 是NTFS驱动器上允许的最大文件夹嵌套)。它工作得非常好。 - Dennis
我基本上同意。我以前就用过ListBox这种方式。我对在这种情况下使用ListBox而不是TreeView的唯一真正问题是,TreeView(无论它目前有多么不完善)是为此目的而设计的,而ListBox可以被“欺骗”进入其中。它仍然有效。这只是尝试使用专门为此目的设计的这些项目。我已经点赞了好建议和代码示例。 - CodeWarrior

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接