How to automatically scale font size for a group of controls?

后端 未结 6 2060
予麋鹿
予麋鹿 2020-12-04 06:55

I have a few TextBlocks in WPF in a Grid that I would like to scale depending on their available width / height. When I searched for automatically scaling Font size the typi

6条回答
  •  长情又很酷
    2020-12-04 07:44

    General remark: A possible alternative to the whole text scaling could be to just use TextTrimming on the TextBlocks.

    I've struggled to find a solution to this one. Using a viewbox is really hard to mix with any layout adjustments. Worst of all, ActualWidth etc. do not change inside a viewbox. So I finally decided to use the viewbox only if absolutely necessary, which is when clipping would occur. I'm therefore moving the content between a ContentPresenter and a Viewbox, depending upon the available space.


    enter image description here

    enter image description here


    This solution is not as generic as I would like, mainly the MoveToViewboxBehavior does assume it is attached to a grid with the following structure. If that cannot be accomodated, the behavior will most likely have to be adjusted. Creating a usercontrol and denoting the necessary parts (PART_...) might be a valid alternative.

    Note that I have extended the grid's columns from three to five, because that makes the solution a lot easier. It means that the middle textblock will not be exactly in the middle, in the sense of absolute coordinates, instead it is centered between the textblocks to the left and right.

     
        
        
             
                                       
                    
                
            
        
    
    

    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, 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(o, name);
    
                    if (child != null) break;
                }
            }
            return child;
        }
    }
    

提交回复
热议问题