1
votes

Following ListView shall resize its Items, wich are rendered with a TextBlock DataTemplate, by decreasing the FontSize. The initial FontSize is specified in Style and should be the maximum (23pt).

If Item Text get's to long, it is wrapped to a maximum of 2 lines.

When the Window resizes its height or the space for the control gets smaller than needed for displaying the Items, the FontSize shall be reduced untill all Items can be displayed. Formerly wrapped Text shall be "unwrapped" if possible

<ListView x:Name="myListView" Grid.Row="0" Grid.Column="0"
                  Background="White" BorderBrush="Black" BorderThickness="1"
                  IsHitTestVisible="False"
                  ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                  ScrollViewer.VerticalScrollBarVisibility="Disabled">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock MaxHeight="80"
                               Margin="0,0,0,21"
                               Text="{Binding}"
                               TextTrimming="CharacterEllipsis"
                               TextWrapping="Wrap">
                        <TextBlock.Style>
                            <Style TargetType="{x:Type TextBlock}">
                                <Setter Property="FontSize" Value="23pt" />
                                <Setter Property="TextTrimming" Value="CharacterEllipsis" />
                                <Setter Property="FontFamily" Value="Arial Regular" />
                                <Setter Property="VerticalAlignment" Value="Center" />
                                <Setter Property="HorizontalAlignment" Value="Left" />
                            </Style>
                        </TextBlock.Style>
                    </TextBlock>
                </DataTemplate>
            </ListView.ItemTemplate>
            <ListView.ItemContainerStyle>
                ...
            </ListView.ItemContainerStyle>
        </ListView>

Example Picture showing what i want enter image description here

I already tried to achieve the requirement by using a ViewBox around the ListView, but this will resize height and width as well (ratio) ==> But the Available width for the text shall be constant.

Another way was to override MeasureOverride for ListView to decrease FontSize, if desired size of the listView > available size for the control. But this didn't work neither.

Maybe some clever guy can help me with this problem.

Best Regards,

EDIT 17.08.2017

I missed some necessary information. The ListView is inside a grid at row = 0 , column = 0:

    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="250" />
        <ColumnDefinition Width="300" />
    </Grid.ColumnDefinitions>

The GridCell 0,0 can only resize in height. The ListView shall take the whole Cellsize and adjust the FontSize of its children to fit in (with the max FontSize avalaible for that). Wrapping of the Text to max 2 textlines is also important.

1
I updated my answer with some fully working code.Bradley Uffner

1 Answers

0
votes

You might need to do something like bind the FontSize to the width of the ListView and use a custom IValueConverter to change that width in to a FontSize that will allow the text to fit on one line.

Unfortunately there is no built-in functionality to do what you want.


I've got a working solution for you now! I had to use a few tricks to get this to work. The converter needs to be able to see more information about the ListView than it is typically able to see. To get around that, I used an IMultiValueConverter to send it the entire ListView element. It starts at MaxFontSize and drops down one font size until if finds a size where the longest line completely fits within the width of the LitView or it gets to MinFontSize.

You will probably have to make a few adjustments to perfectly suit your needs, but this should take care of the hardest part. You may want to look at optimizing the size code, as it could have performance issues with a lot of items. There should be several fairly obvious optimization that I don't have time to do right now.

enter image description here

Add the following class:

class FontSizeConverter : FrameworkElement, IMultiValueConverter
{
    public double MaxFontSize { get; set; } = 50;
    public double MinFontSize { get; set; } = 1;

    private Size MeasureString(string candidate, Typeface typeface, double fontSize, ListView listView)
    {
        var formattedText = new FormattedText(candidate,
                                              CultureInfo.CurrentUICulture,
                                              FlowDirection.LeftToRight,
                                              typeface,
                                              fontSize,
                                              Brushes.Black,
                                              VisualTreeHelper.GetDpi(listView).PixelsPerDip);

        return new Size(formattedText.Width, formattedText.Height);
    }

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var containerListView = values[1] as ListView;
        if (containerListView == null || containerListView.ItemsSource==null)
        {
            return MaxFontSize;
        }

        Typeface typeface = new Typeface(containerListView.FontFamily, containerListView.FontStyle, containerListView.FontWeight, containerListView.FontStretch);
        double size = MaxFontSize;
        while (size > 0)
        {
            var biggestWidth = ((IEnumerable<string>)containerListView.ItemsSource).Select(s => MeasureString(s, typeface, size, containerListView).Width).Max();
            if (biggestWidth <= containerListView.ActualWidth)
            {
                return size;
            }
            size--;
        }
        return MinFontSize;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Use this XAML:

<Window
    x:Class="WpfApp4.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WpfApp4"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:system="clr-namespace:System;assembly=mscorlib"
    Title="MainWindow"
    Width="525"
    Height="350"
    mc:Ignorable="d">
    <Window.Resources>
        <x:Array x:Key="Data" Type="{x:Type system:String}">
            <system:String>Testing Testing Testing</system:String>
            <system:String>Some more text Some more text Some more text</system:String>
            <system:String>Longer text</system:String>
            <system:String>text text text text text texttext text text text text text</system:String>
            <system:String>Testing</system:String>
        </x:Array>
        <local:FontSizeConverter x:Key="FontSizeConverter" />
    </Window.Resources>
    <ListView
        Name="TheListView"
        Margin="8"
        ItemsSource="{Binding Source={StaticResource Data}}"
        ScrollViewer.HorizontalScrollBarVisibility="Disabled">
        <ListView.FontSize>
            <MultiBinding Converter="{StaticResource FontSizeConverter}">
                <Binding ElementName="TheListView" Path="ActualWidth" />
                <Binding ElementName="TheListView" />
            </MultiBinding>
        </ListView.FontSize>
        <ListView.Resources />
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Window>