首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >WPF TreeView:如何使控件相对于每个TreeViewItem对齐,但仍然对头文本产生缩进效果?

WPF TreeView:如何使控件相对于每个TreeViewItem对齐,但仍然对头文本产生缩进效果?
EN

Stack Overflow用户
提问于 2020-04-14 21:27:21
回答 1查看 958关注 0票数 1

下面是我想要完成的任务的一个直观的想法:

我希望所有的按钮在左边和右边垂直对齐,无论TreeViewItem在树中的位置。我在实现这一效果时遇到了困难,同时还以典型的嵌套方式将头缩进。

我最近的尝试涉及修改TreeViewItem模板;将按钮放置在一个DockPanel (停靠左或右)中,该按钮跨越主网格中的所有列,将扩展器&标头放在中间列,并使ItemsPresenter (ItemsHost)跨下一行的所有列。这使得每件事都对齐,包括标题内容。

下面是我目前为我的TreeViewItem样式提供的简化版本:

代码语言:javascript
运行
复制
<Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
    <Setter Property="Focusable" Value="False"/>
    <Setter Property="Width" Value="16"/>
    <Setter Property="Height" Value="16"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ToggleButton}">
                <Border Background="Transparent" Height="16" Padding="5,5,5,5" Width="16">
                    <Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
                        <Path.RenderTransform>
                            <RotateTransform Angle="135" CenterY="3" CenterX="3"/>
                        </Path.RenderTransform>
                    </Path>
                </Border>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsChecked" Value="True">
                        <Setter Property="RenderTransform" TargetName="ExpandPath">
                            <Setter.Value>
                                <RotateTransform Angle="180" CenterY="3" CenterX="3"/>
                            </Setter.Value>
                        </Setter>
                        <Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
                        <Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
                    </Trigger>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
                        <Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsMouseOver" Value="True"/>
                            <Condition Property="IsChecked" Value="True"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
                        <Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
                    </MultiTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<HierarchicalDataTemplate x:Key="HeaderTemplate" DataType="{x:Type models:Entity}" ItemsSource="{Binding Path=Entities}">
    <Label VerticalAlignment="Center">test</Label>
</HierarchicalDataTemplate>

<Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="HeaderTemplate" Value="{StaticResource HeaderTemplate}"></Setter>
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
    <Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
    <Setter Property="HorizontalAlignment" Value="Left"></Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TreeViewItem}">
                <Grid HorizontalAlignment="Left" Width="300">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="30"></ColumnDefinition>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                        <ColumnDefinition Width="30"></ColumnDefinition>
                    </Grid.ColumnDefinitions>

                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>

                    <DockPanel Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="0" Margin="1,0,0,0">
                        <Button
                            DockPanel.Dock="Left"/>

                        <Button
                            DockPanel.Dock="Left"/>

                        <Button
                            DockPanel.Dock="Left"
                        Margin="0"/>

                        <Button
                            DockPanel.Dock="Right"
                            HorizontalAlignment="Right"/>

                        <Button
                            DockPanel.Dock="Right"
                            HorizontalAlignment="Right"/>

                        <Button
                            DockPanel.Dock="Right"
                            HorizontalAlignment="Right"/>
                    </DockPanel>

                    <StackPanel Orientation="Horizontal" Grid.Column="1" Grid.Row="0" Margin="25,0,0,0">
                        <ToggleButton x:Name="Expander" ClickMode="Press" Margin="15,0,0,0" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>

                        <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                            <ContentPresenter x:Name="PART_Header"  ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </Border>

                    </StackPanel>

                    <ItemsPresenter x:Name="ItemsHost" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Width="305"/>
                </Grid>

                <ControlTemplate.Triggers>
                    <Trigger Property="IsExpanded" Value="false">
                        <Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
                    </Trigger>
                    <Trigger Property="HasItems" Value="false">
                        <Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
                    </Trigger>
                    <Trigger Property="IsSelected" Value="true">
                        <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
                    </Trigger>
                    <MultiTrigger>
                        <MultiTrigger.Conditions>
                            <Condition Property="IsSelected" Value="true"/>
                            <Condition Property="IsSelectionActive" Value="false"/>
                        </MultiTrigger.Conditions>
                        <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>
                    </MultiTrigger>
                    <Trigger Property="IsEnabled" Value="false">
                        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

我如何使标题嵌套在图片中,同时保持对接按钮的对齐?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-04-16 01:26:52

就像你现在可能已经知道的,这并不是微不足道的事情。问题在于,与ListBox和DataGrid等人不同,TreeView没有类似行的结构。相反,它使用的层次结构看起来有点像这样:

有一个网格可以包装整个控件,但是对于任何给定的“行”,TreeViewItem并不会一直延伸到您想要放置按钮的左边。

因此,要实现这一点,您必须重新模板TreeViewItem。默认模板使用网格来放置其内容,行内容放在第0行,子元素(如果有的话)放在第1行。重要的是,子元素也放在第1列中,这就是TreeView进行缩进的方式。因此,第一步是在左边多放3列,在右边再放3列,以容纳您想要添加到每一行的6个按钮。然后,您将希望在每个级别上更改Grid.Column和Grid.ColumnSpan,以便它占用整个控件的宽度。

当然,现在的问题是您丢失了缩进,所以您必须向网格中添加另一列才能将其重新添加进去。要正确地为任何给定级别进行缩进,您需要知道其父级的缩进,该缩进以前是布局本身固有的,但现在已被删除。对此有几种解决方案,但最简单的IMO是使用附加属性,我将称之为TreeViewItemHelper.Indent。对于您的ItemsPresenter中的每个TreeView,您需要计算要用于当前级别以下的所有子级的缩进:

代码语言:javascript
运行
复制
<ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="10" Grid.Column="0" Grid.Row="1"
    local:TreeViewItemHelper.Indent="{Binding Path=(local:TreeViewItemHelper.Indent), Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}, Converter={StaticResource IndentConverter}}" />

请注意,这里我实际上没有使用任何值,只是通过将每个TreeViewItemHelper.Indent绑定到上一级的一个并通过一个只添加固定数量(即用于扩展树节点的ToggleButtons的宽度)的转换器来计算它应该是什么:

代码语言:javascript
运行
复制
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    return new GridLength(((GridLength)value).Value + IndentSize);
}

最后,您需要在某个地方实际应用缩进。我们已经在网格中为它创建了一个列,因此我们只需向该列添加一个虚拟控件,并将其宽度绑定到它所处的任何ItemsPresenter的缩进级别:

代码语言:javascript
运行
复制
<Rectangle Grid.Column="3" Width="{Binding Path=(local:TreeViewItemHelper.Indent).Value, Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}}" Fill="Transparent"/>

将所有这些添加到一起,摇得很好,下面是XAML重新模板TreeViewItem并使用样式分配它的样子:

代码语言:javascript
运行
复制
<local:IndentConverter x:Key="IndentConverter" />

<ControlTemplate x:Key="TreeViewItemControlTemplate1" TargetType="{x:Type TreeViewItem}">
    <Grid x:Name="tvGrid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <!--<ColumnDefinition Width="0"/>-->
            <ColumnDefinition MinWidth="19" Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <!-- Left buttons -->
        <Button Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />
        <Button Grid.Column="1" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />
        <Button Grid.Column="2" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />

        <Rectangle Grid.Column="3" Width="{Binding Path=(local:TreeViewItemHelper.Indent).Value, Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}}" Fill="Transparent"/>

        <ToggleButton x:Name="Expander" Grid.Column="4" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}">
            <ToggleButton.Style>
                <Style TargetType="{x:Type ToggleButton}">
                    <Setter Property="Focusable" Value="False"/>
                    <Setter Property="Width" Value="16"/>
                    <Setter Property="Height" Value="16"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ToggleButton}">
                                <Border Background="Transparent" Height="16" Padding="5" Width="16">
                                    <Path x:Name="ExpandPath" Data="M0,0 L0,6 L6,0 z" Fill="White" Stroke="#FF818181">
                                        <Path.RenderTransform>
                                            <RotateTransform Angle="135" CenterY="3" CenterX="3"/>
                                        </Path.RenderTransform>
                                    </Path>
                                </Border>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="IsChecked" Value="True">
                                        <Setter Property="RenderTransform" TargetName="ExpandPath">
                                            <Setter.Value>
                                                <RotateTransform Angle="180" CenterY="3" CenterX="3"/>
                                            </Setter.Value>
                                        </Setter>
                                        <Setter Property="Fill" TargetName="ExpandPath" Value="#FF595959"/>
                                        <Setter Property="Stroke" TargetName="ExpandPath" Value="#FF262626"/>
                                    </Trigger>
                                    <Trigger Property="IsMouseOver" Value="True">
                                        <Setter Property="Stroke" TargetName="ExpandPath" Value="#FF27C7F7"/>
                                        <Setter Property="Fill" TargetName="ExpandPath" Value="#FFCCEEFB"/>
                                    </Trigger>
                                    <MultiTrigger>
                                        <MultiTrigger.Conditions>
                                            <Condition Property="IsMouseOver" Value="True"/>
                                            <Condition Property="IsChecked" Value="True"/>
                                        </MultiTrigger.Conditions>
                                        <Setter Property="Stroke" TargetName="ExpandPath" Value="#FF1CC4F7"/>
                                        <Setter Property="Fill" TargetName="ExpandPath" Value="#FF82DFFB"/>
                                    </MultiTrigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ToggleButton.Style>
        </ToggleButton>
        <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="5" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="True">
            <ContentPresenter x:Name="PART_Header" ContentTemplate="{TemplateBinding HeaderTemplate}" Content="{TemplateBinding Header}" ContentStringFormat="{TemplateBinding HeaderStringFormat}" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
        </Border>

        <!-- Right buttons -->
        <Button Grid.Column="7" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />
        <Button Grid.Column="8" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />
        <Button Grid.Column="9" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="2" />

        <ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="10" Grid.Column="0" Grid.Row="1"
            local:TreeViewItemHelper.Indent="{Binding Path=(local:TreeViewItemHelper.Indent), Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}, Converter={StaticResource IndentConverter}}" />
    </Grid>
    <ControlTemplate.Triggers>
        <Trigger Property="IsExpanded" Value="False">
            <Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
        </Trigger>
        <Trigger Property="HasItems" Value="False">
            <Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
        </Trigger>
        <Trigger Property="IsSelected" Value="True">
            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
        </Trigger>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="IsSelected" Value="True"/>
                <Condition Property="IsSelectionActive" Value="False"/>
            </MultiTrigger.Conditions>
            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>
        </MultiTrigger>
        <Trigger Property="IsEnabled" Value="False">
            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

    <Style TargetType="TreeViewItem">
        <Setter Property="Template" Value="{DynamicResource TreeViewItemControlTemplate1}" />
        <Setter Property="IsExpanded" Value="True" />
    </Style>

下面是它使用的附加属性的类:

代码语言:javascript
运行
复制
public static class TreeViewItemHelper
{
    public static GridLength GetIndent(DependencyObject obj)
    {
        return (GridLength)obj.GetValue(IndentProperty);
    }

    public static void SetIndent(DependencyObject obj, GridLength value)
    {
        obj.SetValue(IndentProperty, value);
    }

    // Using a DependencyProperty as the backing store for Indent.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IndentProperty =
        DependencyProperty.RegisterAttached("Indent", typeof(GridLength), typeof(TreeViewItemHelper), new PropertyMetadata(new GridLength(0)));
}

最后,压痕转换器:

代码语言:javascript
运行
复制
public class IndentConverter : IValueConverter
{
    private const int IndentSize = 16;  // hard-coded into the XAML template

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return new GridLength(((GridLength)value).Value + IndentSize);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return Binding.DoNothing;
    }
}

结果是:

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61217523

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档