Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

25 Jan 2021

UWP的XAML控件TreeView

TreeView顾名思义,就是以树形展示数据。最简单的比喻就是一个文件目录树。

UWP中的TreeView是一个XAML控件,支持以下功能:

  • N级嵌套
  • 支持选择一个或者多个节点
  • 支持ItemsSource的数据绑定
  • 以TreeViewItem为TreeView的根节点
  • TreeViewItem的内容可以是任意类型
  • 在TreeView之间拖放

由于可以嵌套,TreeView的节点带有Chevron图标,用于展示节点是关闭还是打开。另外,每一个节点也额外携带一个图标。

创建一个TreeView,既可以指定其ItemsSource,也可以显示往其RootNodes辖属中添加TreeViewNode。每个TreeViewNode的Children属性也可以添加子TreeViewNode。

1809 Win10 开始,TreeView的才支持ItemsSource辖属。不过你也可以直接使用WinUI中的版本。

下面是一个显示指定TreeViewNode的例子:

<muxc:TreeView>
    <muxc:TreeView.RootNodes>
        <muxc:TreeViewNode Content="Flavors"
                           IsExpanded="True">
            <muxc:TreeViewNode.Children>
                <muxc:TreeViewNode Content="Vanilla"/>
                <muxc:TreeViewNode Content="Strawberry"/>
                <muxc:TreeViewNode Content="Chocolate"/>
            </muxc:TreeViewNode.Children>
        </muxc:TreeViewNode>
    </muxc:TreeView.RootNodes>
</muxc:TreeView>

下面的例子针对对于层级类型的数据:

<muxc:TreeView ItemsSource="{x:Bind DataSource}">
    <muxc:TreeView.ItemTemplate>
        <DataTemplate x:DataType="local:Item">
            <muxc:TreeViewItem ItemsSource="{x:Bind Children}"
                               Content="{x:Bind Name}"/>
        </DataTemplate>
    </muxc:TreeView.ItemTemplate>
</muxc:TreeView>

如果使用TreeView.ItemsSource,下面的API或许有帮助:

  • ItemFromContainer,从TreeViewItem中获取数据条目
  • ContainerFromItem,从数据条目获取TreeViewItem
  • NodeFromContainer, 从TreeViewItem获取TreeViewNode
  • ContainerFromNode,从TreeViewNode获取TreeViewitem

每个TreeViewNode有下面的辖属:

  • Children
  • HasChildren
  • HasUnrealizedChildren
  • Depth,到RootNodes的距离
  • Parent

TreeViewNode的Content支持IInspectable类型。如果Content不是String类型,那么需要指定ItemTemplate来告诉TreeView怎么显示这个Content。在1803 Win10以前,ItemTemplate不存在,如果内容不是string的话,需要定制TreeView的模板来显示内容。

在TreeView上,可以通过修改ItemContainerStyle或者ItemContainerStyleSelector来定制应用于TreeViewItem的样式。下面是例子:

<muxc:TreeView>
    <muxc:TreeView.ItemContainerStyle>
        <Style TargetType="muxc:TreeViewItem">
            <Setter Property="CollapsedGlyph" Value="&#xE948;"/>
            <Setter Property="ExpandedGlyph" Value="&#xE949;"/>
            <Setter Property="GlyphBrush" Value="DarkOrange"/>
        </Style>
    </muxc:TreeView.ItemContainerStyle>
    <muxc:TreeView.RootNodes>
        <muxc:TreeViewNode Content="Flavors"
               IsExpanded="True">
            <muxc:TreeViewNode.Children>
                <muxc:TreeViewNode Content="Vanilla"/>
                <muxc:TreeViewNode Content="Strawberry"/>
                <muxc:TreeViewNode Content="Chocolate"/>
            </muxc:TreeViewNode.Children>
        </muxc:TreeViewNode>
    </muxc:TreeView.RootNodes>
</muxc:TreeView>

ItemTemplateSelector的话,则是让TreeView在应对不同的TreeViewNode采用不同的DataTemplate。例子:

<Page.Resources>
    <DataTemplate x:Key="FolderTemplate" x:DataType="local:ExplorerItem">
        <muxc:TreeViewItem ItemsSource="{x:Bind Children}">
            <StackPanel Orientation="Horizontal">
                <Image Width="20" Source="Assets/folder.png"/>
                <TextBlock Text="{x:Bind Name}" />
            </StackPanel>
        </muxc:TreeViewItem>
    </DataTemplate>

    <DataTemplate x:Key="FileTemplate" x:DataType="local:ExplorerItem">
        <muxc:TreeViewItem>
            <StackPanel Orientation="Horizontal">
                <Image Width="20" Source="Assets/file.png"/>
                <TextBlock Text="{x:Bind Name}"/>
            </StackPanel>
        </muxc:TreeViewItem>
    </DataTemplate>

    <local:ExplorerItemTemplateSelector
            x:Key="ExplorerItemTemplateSelector"
            FolderTemplate="{StaticResource FolderTemplate}"
            FileTemplate="{StaticResource FileTemplate}" />
</Page.Resources>

<Grid>
    <muxc:TreeView
        ItemsSource="{x:Bind DataSource}"
        ItemTemplateSelector="{StaticResource ExplorerItemTemplateSelector}"/>
</Grid>

需要你自定义一个DataTemplateSelector:

public class ExplorerItemTemplateSelector : DataTemplateSelector
{
    public DataTemplate FolderTemplate { get; set; }
    public DataTemplate FileTemplate { get; set; }

    protected override DataTemplate SelectTemplateCore(object item)
    {
        var explorerItem = (ExplorerItem)item;
        if (explorerItem.Type == ExplorerItem.ExplorerItemType.Folder) return FolderTemplate;

        return FileTemplate;
    }
}

传给SelectTemplateCore的参数取决于:

  • 如果使用的是ItemsSource,那么则取决于ItemsSource中条目的类型
  • 如果使用的是TreeViewNode,那么传入的参数就是TreeViewNode类型,可以从TreeViewNode.Content获取数据

Interacting with a tree view

TreeView支持以下操作:

  • 展开或者合并节点
  • 单点或者多点选择
  • 点击调用节点

具有子节点的枝干节点可以被点击展开或者合并。也可以在代码中调用TreeView的Collapse以及Expand方法来实现同样的操作,所需参数是具体的TreeViewNode。每个TreeViewNode有IsExpanded辖属,可以用来查看是否展开。

一个节点的内容可以在展开的时候加载。你需要处理Expanding事件,并判断HasUnrealizedChildren是否为真。下面是一个例子:

private void SampleTreeView_Expanding(muxc.TreeView sender, muxc.TreeViewExpandingEventArgs args)
{
    if (args.Node.HasUnrealizedChildren)
    {
        FillTreeNode(args.Node);
    }
}

你也可以选择处理Collapsed事件来移除子节点:

private void SampleTreeView_Collapsed(muxc.TreeView sender, muxc.TreeViewCollapsedEventArgs args)
{
    args.Node.Children.Clear();
    args.Node.HasUnrealizedChildren = true;
}

用户点击的时候可以触发某个操作,而不是去选中对应的节点。这可以通过ItemInvoked事件来实现。ListView也可以用于动作触发,但是需要开启IsItemClickEnabled辖属。

对于条目选取的话,默认是关的,需要设置TreeView.SelectionMode成Single或者Multiple。多选的情况下,每个节点前面会出现一个选择框,供选取多个用。选取的节点会被添加到TreeView的SelectedNodes合集。另外可以通过SelectAll方法来选取所有节点。确保只在多选模式下调用SelectAll。

对于unrealized的节点,在选取的时候不做考虑。其他一些额外的注意事项:

  • 当用户选取了一个父节点,所有具现化的节点会被选取。同样的,子节点的选取也会导致父节点选取。
  • SelectAll 只会添加具现化的节点到SelectedNodes。
  • 如果一个父节点被选中,那么其子节点具现化的时候也会被选取。

下面是一些例子:

(完)