web-dev-qa-db-fra.com

Comment mettre à l'échelle automatiquement la taille de la police pour un groupe de contrôles?

J'ai quelques TextBlocks dans WPF dans une grille que je voudrais mettre à l'échelle en fonction de leur largeur/hauteur disponible. Lorsque j'ai recherché la mise à l'échelle automatique de la taille de la police, la suggestion typique est de placer le TextBlock dans une ViewBox.

Alors j'ai fait ça:

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

    <Viewbox MaxHeight="18" Grid.Column="0" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <TextBlock Text="{Binding Text1}" />
    </Viewbox>

    <Viewbox MaxHeight="18" Grid.Column="1" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <TextBlock Text="{Binding Text2}" />
    </Viewbox>

    <Viewbox MaxHeight="18" Grid.Column="2" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
        <TextBlock Text="{Binding Text3}" />
    </Viewbox>
</Grid>

Et il redimensionne automatiquement la police de chaque TextBlock. Cependant, cela semble drôle parce que si l'un des TextBlocks a un texte plus long, il sera dans une police plus petite tandis que ses éléments de grille voisins seront dans une police plus grande. Je veux que la taille de la police soit mise à l'échelle par groupe, ce serait peut-être bien si je pouvais spécifier un "SharedSizeGroup" pour un ensemble de contrôles pour dimensionner automatiquement leur police.

par exemple.

Le premier texte bloque le texte peut être "26/03/2013 10:45:30 AM", et le deuxième texte TextBlocks peut dire "FileName.ext". Si ceux-ci sont sur la largeur d'une fenêtre, et l'utilisateur commence à redimensionner la fenêtre de plus en plus petite. La date commencera à rendre sa police plus petite que le nom de fichier, selon la longueur du nom de fichier.

Idéalement, une fois que l'un des champs de texte commence à redimensionner la taille du point de police, ils correspondent tous. Quelqu'un a-t-il trouvé une solution pour cela ou peut-il me donner une idée de la façon dont vous le feriez fonctionner? Si cela nécessite du code personnalisé, nous espérons que nous pourrions le reconditionner dans un mélange personnalisé ou un comportement attaché afin qu'il soit réutilisable pour l'avenir. Je pense que c'est un problème assez général, mais je n'ai rien trouvé en recherchant.


pdate J'ai essayé la suggestion de Mathieu et ça marche, mais ça a des effets secondaires:

<Window x:Class="WpfApplication6.MainWindow"
        xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="270" Width="522">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Rectangle Grid.Row="0" Fill="SkyBlue" />

        <Viewbox Grid.Row="1" MaxHeight="30"  Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
                    <ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
                </Grid.ColumnDefinitions>

                <TextBlock Grid.Column="0" Text="SomeLongText" Margin="5" />
                <TextBlock Grid.Column="1" Text="TextA" Margin="5" />
                <TextBlock Grid.Column="2" Text="TextB" Margin="5" />

            </Grid>
        </Viewbox>
    </Grid>
</Window>

Side-Effects

Honnêtement, manquer des colonnes proportionnelles hte me convient probablement. Cela ne me dérangerait pas qu'il redimensionne automatiquement les colonnes pour utiliser intelligemment l'espace, mais il doit couvrir toute la largeur de la fenêtre.

Remarquez sans maxsize, dans cet exemple étendu le texte est trop grand:

<Window x:Class="WpfApplication6.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="270" Width="522">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Rectangle Grid.Row="0" Fill="SkyBlue" />

    <Viewbox Grid.Row="1"  Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
                <ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
                <ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
            </Grid.ColumnDefinitions>

            <TextBlock Grid.Column="0" Text="SomeLongText" Margin="5" />
            <TextBlock Grid.Column="1" Text="TextA" Margin="5" />
            <TextBlock Grid.Column="2" Text="TextB" Margin="5" />

        </Grid>
    </Viewbox>
</Grid>

Text too large without MaxSize

Ici, je voudrais limiter la taille de la police, afin qu'elle ne gaspille pas les fenêtres verticales. Je m'attends à ce que la sortie soit alignée à gauche, au centre et à droite, la police étant aussi grande que possible jusqu'à la taille maximale souhaitée.


@ adabyron

La solution que vous proposez n'est pas mauvaise (et est la meilleure à ce jour) mais elle a certaines limites. Par exemple, au départ, je voulais que mes colonnes soient proportionnelles (la deuxième devrait être centrée). Par exemple, mes TextBlocks pourraient étiqueter le début, centre et arrêt d'un graphique où l'alignement est important.

<Window x:Class="WpfApplication6.Window1"
        xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
        xmlns:b="clr-namespace:WpfApplication6.Behavior"
        Title="MainWindow" Height="350" Width="525">

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

        <Rectangle Grid.Row="0" Fill="SkyBlue" />
        <Line X1="0.5" X2="0.5" Y1="0" Y2="1" Stretch="Fill" StrokeThickness="3" Stroke="Red" />

        <Grid Grid.Row="1">

            <i:Interaction.Behaviors>
                <b:MoveToViewboxBehavior />
            </i:Interaction.Behaviors>

            <Viewbox Stretch="Uniform" />
            <ContentPresenter >
                <ContentPresenter.Content>
                    <Grid x:Name="TextBlockContainer">
                        <Grid.Resources>
                            <Style TargetType="TextBlock" >
                                <Setter Property="FontSize" Value="16" />
                                <Setter Property="Margin" Value="5" />
                            </Style>
                        </Grid.Resources>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*"  />
                            <ColumnDefinition Width="*"  />
                            <ColumnDefinition Width="*"  />
                            <ColumnDefinition Width="*"  />
                            <ColumnDefinition Width="*"  />
                        </Grid.ColumnDefinitions>

                        <TextBlock Grid.Column="0" Text="SomeLongText" VerticalAlignment="Center" HorizontalAlignment="Center" />
                        <TextBlock Grid.Column="2" Text="TextA" HorizontalAlignment="Center" VerticalAlignment="Center" />
                        <TextBlock Grid.Column="4" Text="TextB" HorizontalAlignment="Center" VerticalAlignment="Center" />
                    </Grid>
                </ContentPresenter.Content>
            </ContentPresenter>
        </Grid>
    </Grid>
</Window>

Et voici le résultat. Notez qu'il ne sait pas qu'il est écrêté au début, puis lorsqu'il remplace ViewBox, il semble que la grille par défaut ait la taille de colonne "Auto" et n'aligne plus le centre.

Scaling with adabyron's suggestion

44
Alan

Je voulais modifier la réponse que j'avais déjà proposée, mais j'ai ensuite décidé qu'il était plus logique d'en publier une nouvelle, car cela dépend vraiment des exigences que je préfère. Cela correspond probablement mieux à l'idée d'Alan, car

  • Le bloc de texte du milieu reste au milieu de la fenêtre
  • L'ajustement de la taille de police en raison de l'écrêtage de la hauteur est possible
  • Un peu plus générique
  • Aucune boîte de vue impliquée

enter image description here

enter image description here

l'autre a l'avantage que

  • L'espace pour les blocs de texte est alloué plus efficacement (pas de marges inutiles)
  • Les blocs de texte peuvent avoir différentes tailles de police

J'ai également testé cette solution dans un conteneur supérieur de type StackPanel/DockPanel, se comportant décemment.

Notez qu'en jouant avec les largeurs/hauteurs de colonne/ligne (auto/étoiles), vous pouvez obtenir différents comportements. Ainsi, il serait également possible d'avoir les trois colonnes de bloc de texte en étoile, mais cela signifie que l'écrêtage de la largeur se produit plus tôt et qu'il y a plus de marge. Ou si la ligne dans laquelle se trouve la grille est dimensionnée automatiquement, un écrêtage en hauteur ne se produira jamais.

Xaml:

<Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
            xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
            xmlns:beh="clr-namespace:WpfApplication1.Behavior"
            Title="MainWindow" Height="350" Width="525">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="0.9*"/>
            <RowDefinition Height="0.1*" />
        </Grid.RowDefinitions>

        <Rectangle Fill="DarkOrange" />

        <Grid x:Name="TextBlockContainer" Grid.Row="1" >
            <i:Interaction.Behaviors>
                <beh:ScaleFontBehavior MaxFontSize="32" />
            </i:Interaction.Behaviors>
            <Grid.Resources>
                <Style TargetType="TextBlock" >
                    <Setter Property="Margin" Value="5" />
                    <Setter Property="VerticalAlignment" Value="Center" />
                </Style>
            </Grid.Resources>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"  />
                <ColumnDefinition Width="Auto"  />
                <ColumnDefinition Width="*"  />
            </Grid.ColumnDefinitions>

            <TextBlock Grid.Column="0" Text="SomeLongText" />
            <TextBlock Grid.Column="1" Text="TextA" HorizontalAlignment="Center"  />
            <TextBlock Grid.Column="2" Text="TextB" HorizontalAlignment="Right"  />
        </Grid>
    </Grid>
</Window>

ScaleFontBehavior:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using WpfApplication1.Helpers;

namespace WpfApplication1.Behavior
{
    public class ScaleFontBehavior : Behavior<Grid>
    {
        // MaxFontSize
        public double MaxFontSize { get { return (double)GetValue(MaxFontSizeProperty); } set { SetValue(MaxFontSizeProperty, value); } }
        public static readonly DependencyProperty MaxFontSizeProperty = DependencyProperty.Register("MaxFontSize", typeof(double), typeof(ScaleFontBehavior), new PropertyMetadata(20d));

        protected override void OnAttached()
        {
            this.AssociatedObject.SizeChanged += (s, e) => { CalculateFontSize(); };
        }

        private void CalculateFontSize()
        {
            double fontSize = this.MaxFontSize;

            List<TextBlock> tbs = VisualHelper.FindVisualChildren<TextBlock>(this.AssociatedObject);

            // get grid height (if limited)
            double gridHeight = double.MaxValue;
            Grid parentGrid = VisualHelper.FindUpVisualTree<Grid>(this.AssociatedObject.Parent);
            if (parentGrid != null)
            {
                RowDefinition row = parentGrid.RowDefinitions[Grid.GetRow(this.AssociatedObject)];
                gridHeight = row.Height == GridLength.Auto ? double.MaxValue : this.AssociatedObject.ActualHeight;
            }

            foreach (var tb in tbs)
            {
                // get desired size with fontsize = MaxFontSize
                Size desiredSize = MeasureText(tb);
                double widthMargins = tb.Margin.Left + tb.Margin.Right;
                double heightMargins = tb.Margin.Top + tb.Margin.Bottom; 

                double desiredHeight = desiredSize.Height + heightMargins;
                double desiredWidth = desiredSize.Width + widthMargins;

                // adjust fontsize if text would be clipped vertically
                if (gridHeight < desiredHeight)
                {
                    double factor = (desiredHeight - heightMargins) / (this.AssociatedObject.ActualHeight - heightMargins);
                    fontSize = Math.Min(fontSize, MaxFontSize / factor);
                }

                // get column width (if limited)
                ColumnDefinition col = this.AssociatedObject.ColumnDefinitions[Grid.GetColumn(tb)];
                double colWidth = col.Width == GridLength.Auto ? double.MaxValue : col.ActualWidth;

                // adjust fontsize if text would be clipped horizontally
                if (colWidth < desiredWidth)
                {
                    double factor = (desiredWidth - widthMargins) / (col.ActualWidth - widthMargins);
                    fontSize = Math.Min(fontSize, MaxFontSize / factor);
                }
            }

            // apply fontsize (always equal fontsizes)
            foreach (var tb in tbs)
            {
                tb.FontSize = fontSize;
            }
        }

        // Measures text size of textblock
        private Size MeasureText(TextBlock tb)
        {
            var formattedText = new FormattedText(tb.Text, CultureInfo.CurrentUICulture,
                FlowDirection.LeftToRight,
                new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch),
                this.MaxFontSize, Brushes.Black); // always uses MaxFontSize for desiredSize

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

VisualHelper:

public static List<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
    List<T> children = new List<T>();
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        var o = VisualTreeHelper.GetChild(obj, i);
        if (o != null)
        {
            if (o is T)
                children.Add((T)o);

            children.AddRange(FindVisualChildren<T>(o)); // recursive
        }
    }
    return children;
}

public static T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
    DependencyObject current = initial;

    while (current != null && current.GetType() != typeof(T))
    {
        current = VisualTreeHelper.GetParent(current);
    }
    return current as T;
}
30
Mike Fuchs

Mettez votre grille dans la ViewBox, qui mettra à l'échelle toute la grille:

<Viewbox Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <TextBlock Grid.Column="0" Text="{Binding Text1}" Margin="5" />
        <TextBlock Grid.Column="1" Text="{Binding Text2}" Margin="5" />
        <TextBlock Grid.Column="2" Text="{Binding Text3}" Margin="5" />

    </Grid>
</Viewbox>
9
mathieu

Je pense que je connais le chemin à parcourir et je vous laisse le reste. Dans cet exemple, j'ai lié le FontSize au ActualHeight du TextBlock, en utilisant un convertisseur (le convertisseur est ci-dessous):

<Window x:Class="MyNamespace.Test"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml" xmlns:Converters="clr-namespace:UpdateYeti.Converters"
    Title="Test" Height="570" Width="522">
<Grid Height="370" Width="522">
    <Grid.Resources>
        <Converters:HeightToFontSizeConverter x:Key="conv" />
    </Grid.Resources>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>

    <Rectangle Grid.Row="0" Fill="SkyBlue" />
        <Grid Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="60" Background="Beige">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Text="SomeLongText" Margin="5" 
                   FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
        <TextBlock Grid.Column="1" Text="TextA" Margin="5" HorizontalAlignment="Center" 
                   FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
        <TextBlock Grid.Column="2" Text="TextB" Margin="5" FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
        </Grid>
    </Grid>
</Window>


[ValueConversion(typeof(double), typeof(double))]
class HeightToFontSizeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // here you can use the parameter that you can give in here via setting , ConverterParameter='something'} or use any Nice login with the VisualTreeHelper to make a better return value, or maybe even just hardcode some max values if you like
        var height = (double)value;
        return .65 * height;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
1
Akku

Vous pouvez utiliser un ItemsControl masqué dans une ViewBox.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Viewbox VerticalAlignment="Bottom">
        <Grid>
            <TextBlock Text="SomeLongText"/>
            <ItemsControl Visibility="Hidden">
                <ItemsPanelTemplate>
                    <Grid/>
                </ItemsPanelTemplate>
                <TextBlock Text="SomeLongText"/>
                <TextBlock Text="TextA"/>
                <TextBlock Text="TextB"/>
            </ItemsControl>
        </Grid>
    </Viewbox>
    <Viewbox Grid.Column="1" VerticalAlignment="Bottom">
        <Grid>
            <TextBlock Text="TextA"/>
            <ItemsControl Visibility="Hidden">
                <ItemsPanelTemplate>
                    <Grid/>
                </ItemsPanelTemplate>
                <TextBlock Text="SomeLongText"/>
                <TextBlock Text="TextA"/>
                <TextBlock Text="TextB"/>
            </ItemsControl>
        </Grid>
    </Viewbox>
    <Viewbox Grid.Column="2" VerticalAlignment="Bottom">
        <Grid>
            <TextBlock Text="TextB"/>
            <ItemsControl Visibility="Hidden">
                <ItemsPanelTemplate>
                    <Grid/>
                </ItemsPanelTemplate>
                <TextBlock Text="SomeLongText"/>
                <TextBlock Text="TextA"/>
                <TextBlock Text="TextB"/>
            </ItemsControl>
        </Grid>
    </Viewbox>
</Grid>

ou

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Viewbox VerticalAlignment="Bottom">
        <Grid>
            <TextBlock Text="{Binding Text1}"/>
            <ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
                <ItemsPanelTemplate>
                    <Grid/>
                </ItemsPanelTemplate>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding}"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Grid>
    </Viewbox>
    <Viewbox Grid.Column="1" VerticalAlignment="Bottom">
        <Grid>
            <TextBlock Text="{Binding Text2}"/>
            <ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
                <ItemsPanelTemplate>
                    <Grid/>
                </ItemsPanelTemplate>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding}"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Grid>
    </Viewbox>
    <Viewbox Grid.Column="2" VerticalAlignment="Bottom">
        <Grid>
            <TextBlock Text="{Binding Text3}"/>
            <ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
                <ItemsPanelTemplate>
                    <Grid/>
                </ItemsPanelTemplate>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding}"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </Grid>
    </Viewbox>
</Grid>
1
Tempeck

Remarque générale: Une alternative possible à la mise à l'échelle du texte entier pourrait être d'utiliser simplement TextTrimming sur les TextBlocks.

J'ai eu du mal à trouver une solution à celui-ci. L'utilisation d'une fenêtre d'affichage est vraiment difficile à mélanger avec les ajustements de mise en page. Le pire de tout, ActualWidth etc. ne change pas à l'intérieur d'une fenêtre de visualisation. J'ai donc finalement décidé d'utiliser la boîte de visualisation uniquement si c'était absolument nécessaire, c'est-à-dire lorsque l'écrêtage se produirait. Je déplace donc le contenu entre un ContentPresenter et une Viewbox, en fonction de l'espace disponible.


enter image description here

enter image description here


Cette solution n'est pas aussi générique que je le souhaiterais, principalement le MoveToViewboxBehavior suppose qu'il est attaché à une grille avec la structure suivante. Si cela ne peut être accepté, le comportement devra très probablement être ajusté. La création d'un contrôle utilisateur et l'indication des pièces nécessaires (PART _...) peuvent être une alternative valide.

Notez que j'ai étendu les colonnes de la grille de trois à cinq, car cela rend la solution beaucoup plus facile. Cela signifie que le bloc de texte du milieu ne sera pas exactement au milieu, dans le sens des coordonnées absolues, mais qu'il est centré entre les blocs de texte à gauche et à droite.

<Grid > <!-- MoveToViewboxBehavior attached to this grid -->
    <Viewbox />
    <ContentPresenter>
        <ContentPresenter.Content> 
            <Grid x:Name="TextBlockContainer">                       
                <TextBlocks ... />
            </Grid>
        </ContentPresenter.Content>
    </ContentPresenter>
</Grid>

Xaml:

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.Microsoft.com/expression/2010/interactivity"
        xmlns:beh="clr-namespace:WpfApplication1.Behavior"
        Title="MainWindow" Height="350" Width="525">

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

        <Rectangle Grid.Row="0" Fill="SkyBlue" />

        <Grid Grid.Row="1">

            <i:Interaction.Behaviors>
                <beh:MoveToViewboxBehavior />
            </i:Interaction.Behaviors>

            <Viewbox Stretch="Uniform" />
            <ContentPresenter >
                <ContentPresenter.Content>
                    <Grid x:Name="TextBlockContainer">
                        <Grid.Resources>
                            <Style TargetType="TextBlock" >
                                <Setter Property="FontSize" Value="16" />
                                <Setter Property="Margin" Value="5" />
                            </Style>
                        </Grid.Resources>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"  />
                            <ColumnDefinition Width="*"  />
                            <ColumnDefinition Width="Auto"  />
                            <ColumnDefinition Width="*"  />
                            <ColumnDefinition Width="Auto"  />
                        </Grid.ColumnDefinitions>

                        <TextBlock Grid.Column="0" Text="SomeLongText" />
                        <TextBlock Grid.Column="2" Text="TextA"  />
                        <TextBlock Grid.Column="4" Text="TextB"  />
                    </Grid>
                </ContentPresenter.Content>
            </ContentPresenter>
        </Grid>
    </Grid>
</Window>

MoveToViewBoxBehavior:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using WpfApplication1.Helpers;

namespace WpfApplication1.Behavior
{
    public class MoveToViewboxBehavior : Behavior<Grid>
    {
        // IsClipped 
        public bool IsClipped { get { return (bool)GetValue(IsClippedProperty); } set { SetValue(IsClippedProperty, value); } }
        public static readonly DependencyProperty IsClippedProperty = DependencyProperty.Register("IsClipped", typeof(bool), typeof(MoveToViewboxBehavior), new PropertyMetadata(false, OnIsClippedChanged));

        private static void OnIsClippedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var beh = (MoveToViewboxBehavior)sender;
            Grid grid = beh.AssociatedObject;

            Viewbox vb = VisualHelper.FindVisualChild<Viewbox>(grid);
            ContentPresenter cp = VisualHelper.FindVisualChild<ContentPresenter>(grid);

            if ((bool)e.NewValue) 
            {
                // is clipped, so move content to Viewbox
                UIElement element = cp.Content as UIElement;
                cp.Content = null;
                vb.Child = element;
            }
            else
            {
                // can be shown without clipping, so move content to ContentPresenter
                cp.Content = vb.Child;
                vb.Child = null;
            }
        }

        protected override void OnAttached()
        {
            this.AssociatedObject.SizeChanged += (s, e) => { IsClipped = CalculateIsClipped(); };
        }

        // Determines if the width of all textblocks within TextBlockContainer (using MaxFontSize) are wider than the AssociatedObject grid
        private bool CalculateIsClipped()
        {
            double totalDesiredWidth = 0d;
            Grid grid = VisualHelper.FindVisualChildByName<Grid>(this.AssociatedObject, "TextBlockContainer");
            List<TextBlock> tbs = VisualHelper.FindVisualChildren<TextBlock>(grid);

            foreach (var tb in tbs)
            {
                if (tb.TextWrapping != TextWrapping.NoWrap)
                    return false;

                totalDesiredWidth += MeasureText(tb).Width + tb.Margin.Left + tb.Margin.Right + tb.Padding.Left + tb.Padding.Right;
            }

            return Math.Round(this.AssociatedObject.ActualWidth, 5) < Math.Round(totalDesiredWidth, 5);
        }

        // Measures text size of textblock
        private Size MeasureText(TextBlock tb)
        {
            var formattedText = new FormattedText(tb.Text, CultureInfo.CurrentUICulture,
                FlowDirection.LeftToRight,
                new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch),
                tb.FontSize, Brushes.Black);

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

VisualHelper:

public static class VisualHelper
{
    public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
    {
        T child = null;
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            var o = VisualTreeHelper.GetChild(obj, i);
            if (o != null)
            {
                child = o as T;
                if (child != null) break;
                else
                {
                    child = FindVisualChild<T>(o); // recursive
                    if (child != null) break;
                }
            }
        }
        return child;
    }

    public static List<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
    {
        List<T> children = new List<T>();
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
        {
            var o = VisualTreeHelper.GetChild(obj, i);
            if (o != null)
            {
                if (o is T)
                    children.Add((T)o);

                children.AddRange(FindVisualChildren<T>(o)); // recursive
            }
        }
        return children;
    }

    public static T FindVisualChildByName<T>(DependencyObject parent, string name) where T : FrameworkElement
    {
        T child = default(T);
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
        {
            var o = VisualTreeHelper.GetChild(parent, i);
            if (o != null)
            {
                child = o as T;
                if (child != null && child.Name == name)
                    break;
                else
                    child = FindVisualChildByName<T>(o, name);

                if (child != null) break;
            }
        }
        return child;
    }
}
1
Mike Fuchs

Une solution pourrait être quelque chose comme ça:

Choisissez un maxFontSize, puis définissez le FontSize approprié à afficher en tenant compte de la fenêtre actuelle en utilisant une équation linéaire. La hauteur ou la largeur de la fenêtre limiterait le choix final de FontSize.

Prenons le cas d'un "TextBlock de type unique" pour l'ensemble de la grille:

Window.Current.SizeChanged += (sender, args) =>
        {
            int minFontSize = a;
            int maxFontSize = b;
            int maxMinFontSizeDiff = maxFontSize - minFontSize;

            int gridMinHeight = c;
            int gridMaxHeight = d;
            int gridMaxMinHeightDiff = gridMaxHeight - gridMinHeight;

            int gridMinWidth = e;
            int gridMaxWidth = f;
            int gridMaxMinHeightDiff = gridMaxWidth - gridMaxWidth;

            //Linear equation considering "max/min FontSize" and "max/min GridHeight/GridWidth"
            double heightFontSizeDouble = (maxMinFontSizeDiff / gridMaxMinHeightDiff ) * Grid.ActualHeight + (maxFontSize - (gridMaxHeight * (maxMinFontSizeDiff  / gridMaxMinHeightDiff)))
            double widthFontSizeDouble = (maxMinFontSizeDiff / gridMaxMinWidthDiff ) * Grid.ActualWidth + (maxFontSize - (gridMaxWidth * (maxMinFontSizeDiff  / gridMaxMinWidthDiff)))

            int heightFontSize = (int)Math.Round(heightFontSizeDouble)
            int widthFontSize = (int)Math.Round(widthFontSizeDouble)

            foreach (var children in Grid.Children)
            {                    
                (children as TextBlock).FontSize = Math.Min(heightFontSize, widthFontSize);
            }
        }
0
Javert