【问题标题】:how to get the available (visible) width of the TreeViewItem inside a TreeView如何获取 TreeView 内 TreeViewItem 的可用(可见)宽度
【发布时间】:2022-04-08 14:08:34
【问题描述】:

我有TreeViewItems,其中HierarchicalDataTemplate 包含一个具有以下定义的3 列网格:

<Grid>    
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto" />
    <ColumnDefinition Width="1*" />
    <ColumnDefinition Width="Auto" />
...

我想设置网格的宽度,使其完全占用TreeViewTreeViewItem 的所有可用空间。因此,网格的第三列应该在 TreeView 内右对齐。

如何获得网格宽度的正确值?

我知道对于ListBoxItemTemplate,我可以通过绑定到ScrollContentPresenter来设置宽度:

Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ScrollContentPresenter, AncestorLevel=1}, Path=ActualWidth}"

这个技巧在 TreeView 中不起作用,因为子级的可用空间比根树视图项目少。

有什么想法吗?

【问题讨论】:

    标签: wpf treeview width treeviewitem


    【解决方案1】:

    经过几个小时的研究,我找到了一个使用Multibinding两个转换器的可行解决方案。

    一、XAML中HierarchicalDataTemplate的定义:

    <HierarchicalDataTemplate>
    <Grid>
       <Grid.Width>
          <MultiBinding Converter="{StaticResource SumConverterInstance}">
              <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=ScrollContentPresenter, AncestorLevel=1}" Path="ActualWidth" />
              <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType=TreeViewItem, AncestorLevel=1}" Converter="{StaticResource ParentCountConverterInstance}" />
          </MultiBinding>
        </Grid.Width>   
       .... (content of the template) ....
    </Grid>
    </HierarchicalDataTemplate>
    

    多重绑定中的第一个绑定获取TreeViewScrollContentPresenter的宽度,即TreeView的总可见宽度。第二个绑定以TreeViewItem 作为参数调用转换器,并计算TreeViewItem 在到达根项之前有多少父项。使用这两个输入,我们使用 Multibinding 中的 SumConverterInstance 来计算给定 TreeViewItem 的可用宽度。

    以下是 XAML 中定义的转换器实例:

      <my:SumConverter x:Key="SumConverterInstance" />
      <my:ParentCountConverter x:Key="ParentCountConverterInstance" />
    

    以及两个转换器的代码:

    // combine the width of the TreeView control and the number of parent items to compute available width
    public class SumConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            double totalWidth = (double)values[0];
            double parentCount = (double)values[1];
            return totalWidth - parentCount * 20.0;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
    // count the number of TreeViewItems before reaching ScrollContentPresenter
    public class ParentCountConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            int parentCount = 1;
            DependencyObject o = VisualTreeHelper.GetParent(value as DependencyObject);
            while (o != null && o.GetType().FullName != "System.Windows.Controls.ScrollContentPresenter")
            {
                if (o.GetType().FullName == "System.Windows.Controls.TreeViewItem")
                    parentCount += 1;
                o = VisualTreeHelper.GetParent(o);
            }
            return parentCount;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    

    现在是正确的样子:

    【讨论】:

    • 对我来说也很完美:)
    【解决方案2】:

    您应该让布局系统为您处理这个问题,而不是在您的ItemTemplate 中强制使用固定宽度。对于ListBox,您只需设置HorizontalContentAlignment="Stretch"。这也是TreeView 的第一步,但不幸的是,默认模板中有一些其他布局需要额外更改。它使用 3 列 Grid 将内容放入第二列,只有子项扩展到第三 (*) 列。通过在包含BorderContentPresenter 上添加Grid.ColumnSpan="2",内容将延伸到整个项目的区域。

    <PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
    <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="Transparent" Stroke="#FF989898">
                            <Path.RenderTransform>
                                <RotateTransform Angle="135" CenterY="3" CenterX="3"/>
                            </Path.RenderTransform>
                        </Path>
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Stroke" TargetName="ExpandPath" Value="#FF1BBBFA"/>
                            <Setter Property="Fill" TargetName="ExpandPath" Value="Transparent"/>
                        </Trigger>
                        <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>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
    <ControlTemplate TargetType="{x:Type TreeViewItem}">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition MinWidth="19" Width="Auto"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
            <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"
                    Grid.Column="1" Grid.ColumnSpan="2" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
            </Border>
            <ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
        </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.ControlBrushKey}}"/>
                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
            </MultiTrigger>
            <Trigger Property="IsEnabled" Value="false">
                <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
            </Trigger>
        </ControlTemplate.Triggers>
    </ControlTemplate>
    

    【讨论】:

    • 感谢您的回答约翰,但不幸的是,这不是我问题的答案。我在底部的帖子中描述了为什么因为 cmets 的大小有限......
    • 您正在使用的 DataTemplate 用于填充 TreeViewItem 模板中的 ContentPresenter,因此您可以在 DataTemplate 中执行的操作受到 ControlTemplate 中 ContentPresenter 和 ItemsPresenter 的布局的限制。例如,如果 ControlTemplate 为其根 Grid 指定宽度为 0,则无论您在 DataTemplate 中做什么,都不会显示任何内容。尝试在 Snoop 中检查您的布局(宽度、高度和对齐方式)以更好地了解问题。
    【解决方案3】:

    您描述的是TreeViewItemContainerStyle,而我上面描述的网格是HierarchicalDataTemplate

    在我的TreeView 中,我正在显示电子邮件附件。 TreeViewItems 包含一个图标(网格的第一列)、文件名和大小(第二列)和时间(第三列)。我想根据TreeView 的宽度查看拉伸或挤压的项目(通过包裹网格的中间列)。

    以下是一个无效的示例。如您所见,文件名未包装,因此您看不到某些项目的日期。

    【讨论】:

      【解决方案4】:

      对此的另一种解决方案是将ItemsPanelTemplateMaximumWidth 设置为ContentPresenterActualWidth。它应该具有完全相同的效果,并且只在一个地方完成,每个项目都不需要大量的计算工作:

      <TreeView ScrollViewer.HorizontalScrollBarVisibility="Disabled">
         <TreeView.ItemsPanel>
            <ItemsPanelTemplate>
               <StackPanel MaxWidth="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType=ContentPresenter, AncestorLevel=1}}" />
            </ItemsPanelTemplate>
         </TreeView.ItemsPanel>
      </TreeView>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-15
        • 2011-10-11
        • 1970-01-01
        • 1970-01-01
        • 2011-07-10
        相关资源
        最近更新 更多