-->

How to wrap TextBlock content in TreeView?

2019-02-26 06:28发布

问题:

I have TreeView, which displays some data using data templates. Here's XAML:

    <TreeView Grid.Row="0" ItemsSource="{Binding Railways}" x:Name="tvDatawareObjects"
              ScrollViewer.HorizontalScrollBarVisibility="Disabled">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
                <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.Resources>
<!-- other templates here... -->
            <HierarchicalDataTemplate DataType="{x:Type viewModels:ProjectViewModel}" ItemsSource="{Binding Phases}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <TextBlock Text="{Binding Model.Code}" FontWeight="DemiBold" />
                    <TextBlock Text="{Binding Model.Title}" TextWrapping="Wrap" Foreground="Gray" Grid.Row="1" />
                </Grid>
            </HierarchicalDataTemplate>

            <HierarchicalDataTemplate DataType="{x:Type viewModels:CollectionViewModel}" ItemsSource="{Binding Items}">
                <TextBlock Text="{Binding CollectionName}" />
            </HierarchicalDataTemplate>
        </TreeView.Resources>
    </TreeView>

Text wrapping for <TextBlock Text="{Binding Model.Title}" TextWrapping="Wrap" Foreground="Gray" Grid.Row="1" /> doesn't work. What am I doing wrong?

回答1:

I believe the TextBlock isn't wrapping because it doesn't have a defined width. The grid column that the TextBlock is in has a * width which will grow as the TextBlock grows in width. Try setting a width on the TextBlock or the column and see if the change causes the TextBlock to wrap.

Update:

To be more specific, the problem is that the TreeViewItem will size itself to the size of its contents, the ColumnDefinition will fill the (infinitely) available space and the TextBlock, with no width restriction, will never wrap. This post does a good job of describing how the TreeViewItem behaves. To sum it up: the content area of the TreeViewItem is set to 'Auto' so it will grow to fit the contents. To explicitly set the width of the TreeViewItem try binding your ColumnDefinition width to the TreeView's ActualWidth.

XAML:

<TreeView Width="100">
    <TreeViewItem>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=ActualWidth}"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <TextBlock Text="Lorem Ipsum" />
            <TextBlock Text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book." 
                       TextWrapping="Wrap" Grid.Row="1"/>
            </Grid>
        </TreeViewItem>
    </TreeView>


回答2:

I did it. :)

Accordind to the link, provided in the Dan's answer, the reason is lying in the default TreeViewItemTemplate.

Unfortunately, simple binding to TreeView.ActualWidth can't help. This is because width of each item is less than TreeView.ActualWidth by definition - items are rendered with some horizontal offset, depending on their level.

Hence, to solve the problem, I need to calculate width of item like this:

width = actual_width_of_tree_view - relative_horizontal_offset_of_item

To be more precise, I need ScrollViewer.ViewportWidth, so as TreeViewContent might be scrolled vertically, and visible area of TreeView will be less than TreeView.ActualWidth in this case.

Here's attached property:

    public static Double GetProjectTitleWidth(DependencyObject obj)
    {
        return (Double)obj.GetValue(ProjectTitleWidthProperty);
    }

    public static void SetProjectTitleWidth(DependencyObject obj, Double value)
    {
        obj.SetValue(ProjectTitleWidthProperty, value);
    }

    public static readonly DependencyProperty ProjectTitleWidthProperty = DependencyProperty.RegisterAttached(
        "ProjectTitleWidth", 
        typeof(Double), 
        typeof(DatawareSearchView),
        new UIPropertyMetadata(0.0, ProjectTitleWidthChanged));

    private static void ProjectTitleWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var targetElement = d as FrameworkElement;
        if (targetElement != null)
        {
            var bindingExpr = targetElement.GetBindingExpression(ProjectTitleWidthProperty);
            var sourceElement = bindingExpr.DataItem as FrameworkElement;
            if (sourceElement != null)
            {
                // calculating relative offset
                var leftTop = targetElement.TranslatePoint(new Point(0.0, 0.0), sourceElement);

                // trying to find ScrollViewer
                var border = VisualTreeHelper.GetChild(sourceElement, 0);
                if (border != null)
                {
                    var scrollViewer = VisualTreeHelper.GetChild(border, 0) as ScrollViewer;
                    if (scrollViewer != null)
                    {
                        // setting width of target element
                        targetElement.Width = scrollViewer.ViewportWidth - leftTop.X;
                    }
                }
            }
        }
    }

...and markup:

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

    <TextBlock Text="{Binding Model.Code}" FontWeight="DemiBold" />
    <TextBlock Text="{Binding Model.Title}" TextWrapping="Wrap" Foreground="Gray" x:Name="tbTitle" Grid.Row="1"
               localviews:DatawareSearchView.ProjectTitleWidth="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=ActualWidth}"/>
</Grid>

Of course, provided solution isn't universal - it assumes, that TreeView has Border and ScrollViewer.



回答3:

try this

<TextBlock Text="{Binding Model.Title}" Width="{Binding ActualWidth,
      ElementName=tvDatawareObjects}" TextWrapping="Wrap" Foreground="Gray" Grid.Row="1"/>