Search code examples
c#wpfword-wrapviewbox

How do I get a textblock with textwrapping to fill a viewbox


The XAML is a page with a list of items on it inside a Viewbox. The goal is to have a department name and them a paragraph of notes underneath that. I set the background of my itemscontrol to be Red to make it easy to see how it fills in the page.

<Grid >
    <Viewbox x:Name="slideViewBox">
        <ItemsControl x:Name="itemsControl" Background="Red" ItemsSource="{Binding JournalEntries}" Grid.IsSharedSizeScope="True">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Grid x:Name="ParentGrid">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>
                        <TextBlock Grid.Row="0" HorizontalAlignment="Left" Foreground="White" Text="{Binding Department}" FontSize="32"/>
                        <TextBlock x:Name="detailsTextBlock" Grid.Row="1" Foreground="White" Text="{Binding DetailedExplaination}" HorizontalAlignment="Left" TextWrapping="Wrap" FontSize="20" />
                    </Grid>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
    </Viewbox>
</Grid>

What it generates: enter image description here What I ideally want: enter image description here

Notice that in the first picture the details TextBlock didn't word wrap at all and a lot of usable space on the page was wasted.

I did write some code in the Size_Changed event handler that got me closer to my goal but not all the way there. I manually play with the itemscontrol width to force the text to wrap. I just need to get it to do this automatically, so that it looks like the second picture, right when the page loads.

private void Page_SizeChanged(object sender, SizeChangedEventArgs e)
{
    var child = VisualTreeHelper.GetChild(slideViewBox, 0) as ContainerVisual;
    var scale = child.Transform as ScaleTransform;

    double pageheight = e.NewSize.Height;
    double pagewidth = e.NewSize.Width;
    double textHeight;
    double textWidth;

    textHeight = itemsControl.ActualHeight * scale.ScaleY;
    textWidth = itemsControl.ActualWidth * scale.ScaleX;

    if (textWidth == pagewidth)
    {
        itemsControl.Width = itemsControl.ActualWidth * .9;
    }
    else if (textHeight == pageheight)
    {
        itemsControl.Width = itemsControl.ActualWidth * 1.1;
    }
}

Solution

  • I took @user3284736's idea of applying a fixed width to the inner content, and expanded it.

    First, add a property "ContainerWidth" to the view-model, and keep it updated by handling the SizeChanged event of the view:

    public MyUserControl()
    {
        SizeChanged += OnSizeChanged;
    }
    
    void OnSizeChanged(object sender, SizeChangedEventArgs e)
    {
        ViewModel.ContainerWidth= e.NewSize.Width;
    }
    

    Now bind the width of the item template's container ("ParentGrid" in the example) to the overall container size:

    <Grid x:Name="ParentGrid" 
          Width="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl},Path=DataContext.ContainerWidth}">
    

    Setting a fixed size has the effect of forcing the text to wrap before the ViewBox applies its scaling.

    Viewbox

    Edit Here's the XAML I used to test:

    <Grid x:Name="LayoutRoot" Background="Red">
        <Viewbox Stretch="Fill" StretchDirection="Both">
            <ItemsControl x:Name="itemsControl" ItemsSource="{Binding JournalEntries}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Grid x:Name="ParentGrid" 
                              Width="{Binding RelativeSource={RelativeSource AncestorType=ItemsControl},Path=DataContext.ContainerWidth}">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            <TextBlock Grid.Row="0" HorizontalAlignment="Left" Foreground="White" Text="{Binding Department}" 
                                       FontSize="32"/>
                            <TextBlock x:Name="detailsTextBlock" Grid.Row="1" Foreground="White" Text="{Binding DetailedExplanation}" HorizontalAlignment="Left" TextWrapping="Wrap" FontSize="20" />
                        </Grid>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Viewbox>
    </Grid>