Comment ajuster 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 je cherchais une mise à l’échelle automatique de la taille de la police, la suggestion typique consiste à placer le TextBlock dans une ViewBox.

Donc j’ai fait ça:

                

Et il redimensionne automatiquement la police pour chaque TextBlock. Cependant, cela semble amusant car si l’un des TextBlocks a un texte plus long, sa police sera plus petite alors que les éléments de grid voisins auront 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 permettant de dimensionner automatiquement leur police.

par exemple

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

Idéalement, une fois que l’un des champs de texte commence à redimensionner la taille de la police, ils correspondent tous. Quelqu’un at-il trouvé une solution pour cela ou peut-il me donner une idée de la manière dont vous allez le faire fonctionner? Si un code personnalisé est requirejs, nous pourrons, nous l’espérons, le reconditionner dans un comportement Blend ou Attached personnalisé afin de le réutiliser ultérieurement. Je pense que c’est un problème assez général, mais je n’ai pas pu trouver quoi que ce soit en cherchant.


Mise à jour J’ai essayé la suggestion de Mathieu et cela fonctionne en quelque sorte, mais cela a des effets secondaires:

                      

Effets secondaires

Honnêtement, manquer de colonnes proportionnelles me convient probablement. Cela ne me dérangerait pas que AutoSizing des colonnes utilise 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:

                     

Texte trop grand sans MaxSize

Ici, je voudrais limiter la taille de la police, de sorte qu’elle ne gaspille pas les propriétés des 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 c’est la meilleure pour le moment), mais elle a ses limites. Par exemple, au départ, je voulais que mes colonnes soient proportionnelles (la deuxième devrait être centrée). Par exemple, mes TextBlocks peuvent marquer le début, le centre et l’arrêt d’un graphique là où l’alignement est important.

                                       

Et voici le résultat. Notez qu’il ne sait pas qu’il est coupé très tôt, et quand il remplace ViewBox, il semble que la grid utilise par défaut la taille de colonne “Auto” et qu’elle ne soit plus alignée au centre.

Mise à l'échelle avec suggestion d'adabyron

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

  • Le bloc de texte du milieu rest au milieu de la fenêtre
  • Réglage de la taille de la police en raison de la taille
  • Un peu plus générique
  • Pas de viewbox impliqué

entrer la description de l'image ici

entrer la description de l'image ici

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 des tailles de police différentes

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

Notez qu’en jouant avec les largeurs / hauteurs de colonne / ligne (auto / starized), vous pouvez obtenir différents comportements. Donc, il serait également possible d’avoir les trois colonnes textblock en écanvas, 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 réside la grid est de taille automatique, le découpage de hauteur ne se produira jamais.

Xaml:

                          

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 { // 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 tbs = VisualHelper.FindVisualChildren(this.AssociatedObject); // get grid height (if limited) double gridHeight = double.MaxValue; Grid parentGrid = VisualHelper.FindUpVisualTree(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 FindVisualChildren(DependencyObject obj) where T : DependencyObject { List children = new List(); 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(o)); // recursive } } return children; } public static T FindUpVisualTree(DependencyObject initial) where T : DependencyObject { DependencyObject current = initial; while (current != null && current.GetType() != typeof(T)) { current = VisualTreeHelper.GetParent(current); } return current as T; } 

Placez votre grid dans la ViewBox, ce qui permettra de mettre à l’échelle toute la grid:

             

Je pense que je sais comment aller et vous laissera le rest. Dans cet exemple, j’ai lié FontSize à ActualHeight du TextBlock, en utilisant un convertisseur (le convertisseur est ci-dessous):

                       [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(); } } 

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 à celle-ci. L’utilisation d’une viewbox est vraiment difficile à mélanger avec des ajustements de mise en page. Le pire de tout, ActualWidth, etc. ne change pas à l’intérieur d’une fenêtre. J’ai donc finalement décidé d’utiliser la viewbox uniquement si cela était absolument nécessaire, c’est-à-dire quand l’écrêtage aurait lieu. Je déplace donc le contenu entre un ContentPresenter et une Viewbox, en fonction de l’espace disponible.


entrer la description de l'image ici

entrer la description de l'image ici


Cette solution n’est pas aussi générique que je le souhaiterais, principalement le MoveToViewboxBehavior suppose qu’il est attaché à une grid avec la structure suivante. Si cela ne peut pas être accueilli, le comportement devra très probablement être ajusté. Créer un contrôle utilisateur et indiquer les parties nécessaires (PART _…) peut constituer une alternative valable.

Notez que j’ai étendu les colonnes de la grid 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, au lieu de cela il est centré entre les blocs de texte à gauche et à droite.

            

Xaml:

                                   

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 { // 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(grid); ContentPresenter cp = VisualHelper.FindVisualChild(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(this.AssociatedObject, "TextBlockContainer"); List tbs = VisualHelper.FindVisualChildren(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(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(o); // recursive if (child != null) break; } } } return child; } public static List FindVisualChildren(DependencyObject obj) where T : DependencyObject { List children = new List(); 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(o)); // recursive } } return children; } public static T FindVisualChildByName(DependencyObject parent, ssortingng 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(o, name); if (child != null) break; } } return child; } } 

Vous pouvez utiliser un ItemsControl caché dans une ViewBox.

                                               

ou

                                                     

Une solution pourrait être quelque chose comme ça:

Choisissez une taille maximale, puis définissez la taille de police à afficher en fonction de la fenêtre en cours 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 unique” pour toute la grid:

 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); } }