如何自动缩放一组控件的字体大小?
Posted
技术标签:
【中文标题】如何自动缩放一组控件的字体大小?【英文标题】:How to automatically scale font size for a group of controls? 【发布时间】:2013-03-16 12:12:00 【问题描述】:我在一个网格中的 WPF 中有几个 TextBlock,我想根据它们的可用宽度/高度进行缩放。当我搜索自动缩放字体大小时,典型的建议是将 TextBlock 放入 ViewBox。
所以我这样做了:
<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>
它会自动缩放每个 TextBlock 的字体。然而,这看起来很有趣,因为如果其中一个 TextBlocks 有更长的文本,那么它的字体会更小,而它的相邻网格元素会使用更大的字体。我希望字体大小按组缩放,如果我可以为一组控件指定“SharedSizeGroup”以自动调整其字体大小,那可能会很好。
例如
第一个文本块文本可能是“3/26/2013 10:45:30 AM”,第二个 TextBlocks 文本可能是“FileName.ext”。如果这些跨越窗口的宽度,并且用户开始调整窗口的大小越来越小。日期将开始使其字体小于文件名,具体取决于文件名的长度。
理想情况下,一旦某个文本字段开始调整字体点大小,它们就会全部匹配。有没有人想出一个解决方案,或者可以给我一个镜头,看看你将如何使它工作?如果它需要自定义代码,那么希望我们/我可以将它重新打包成自定义的混合或附加行为,以便将来可重用。我认为这是一个非常普遍的问题,但我无法通过搜索找到任何内容。
更新 我尝试了 Mathieu 的建议,它有点工作,但它有一些副作用:
<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>
老实说,缺少 hte 比例列对我来说可能没问题。我不介意自动调整列大小以巧妙利用空间,但它必须跨越窗口的整个宽度。
注意没有 maxsize,在这个扩展示例中,文本太大了:
<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>
在这里,我想限制字体的大小,这样就不会浪费垂直窗口的空间。 我希望输出左、中、右对齐,字体尽可能大,达到所需的最大尺寸。
@adabyron
您提出的解决方案还不错(而且是最好的),但它确实有一些限制。例如,最初我希望我的列是成比例的(第二个应该居中)。例如,我的 TextBlocks 可能会标记图表的起点、中心和终点,其中对齐很重要。
<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>
结果如下。请注意,它不知道它会在早期被剪裁,然后当它替换 ViewBox 时,它看起来好像 Grid 默认为列大小“自动”并且不再对齐中心。
【问题讨论】:
这个问题可能对你有帮助:social.msdn.microsoft.com/Forums/en-US/wpf/thread/… 我不确定我是否仍然知道问题所在。您实施了下面的解决方案并获得了问题中的最后一个屏幕截图,对吗?您能否发布另一个屏幕截图,其中出现您现在尝试避免的情况?我只是无法想象? =) @Akku 该解决方案应该看起来像第一个屏幕截图,但没有左/右浪费的边距。文本块的字体大小应相同,但左侧、中心和右侧对齐。这有意义吗?我想创建一个“组”控件,如果由于空间有限而必须将它们调整为更小,那么它们都选择相同的大小但字体更小。 那么左边的词应该多在左边,右边的词应该多在右边吗?或者文本是否应该被拉伸以看起来有点尴尬? :-) @Akku 是的,我绝对不希望字体拉伸不成比例。我只想要 3 个等距的列,第一个文本左对齐,第二个文本居中对齐,第三个文本右对齐。例如假设 FontSize 为 18,但网格变得太短或太薄而无法显示文本,文本将变为较小的字体大小以使其适合。每个文本框应该是相同大小的字体。它应该不看起来像一种字体是 8pt 而另一种字体是 16pt。还应保持间距,使其看起来不像是居中的自动调整大小的网格,但具有较大的边距 L&R。 【参考方案1】:我想编辑我已经提供的答案,但后来决定发布一个新答案更有意义,因为这实际上取决于我更喜欢哪一个的要求。这里可能更符合 Alan 的想法,因为
中间的文本块停留在窗口的中间 可以调整因高度剪裁导致的字体大小 更通用一点 不涉及视图框other one 的优势在于
更有效地分配文本块的空间(没有不必要的边距) 文本块可能有不同的字体大小我也在 StackPanel/DockPanel 类型的顶部容器中测试了这个解决方案,表现得体。
请注意,通过调整列/行宽度/高度(自动/按星标),您可以获得不同的行为。因此,也可以将所有三个文本块列都设置为星形,但这意味着宽度裁剪确实发生得更早,并且有更多的边距。或者,如果网格所在的行是自动调整大小的,则永远不会发生高度裁剪。
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);
视觉助手:
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;
【讨论】:
感谢您的努力,这似乎工作得很好,代码也很有意义。看起来不错。 不幸的是,此解决方案在 WinRT 中不起作用。似乎缺少交互命名空间。 WinRT 项目可以引用行为 SDK,但它只包含 IBehavior。如果有人可以帮助我为 WinRT 应用程序提供这样的解决方案,那就太好了。 @SeBo:您不需要使用 Expression Blend 行为命名空间,这只是添加了诸如强类型AssociatedObject
之类的便利功能。您可以重写代码,而无需对附加行为进行太多更改。我有一个示例 here,但是在谷歌搜索 附加行为 时应该会找到很多帮助。【参考方案2】:
将您的网格放入 ViewBox 中,它将缩放整个网格:
<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>
【讨论】:
我遇到了不寻常的行为,我可能会尝试一个示例应用程序来简化它以进行仔细检查。但它似乎没有工作...... 好吧,你可以看到我设置了 MaxHeight,因为我不希望字体变得任意大(并且在设计器中它占据了一半的屏幕,因为绑定是空白的)。但是,如果我在 ViewBox 上设置 MaxHeight,则 Grid 的宽度不会跨越窗口的宽度,文本列也不会。如果我在 TextBlocks 上设置 MaxHeight 则无效。此外,在 ViewBox 内部,ColumnDefinition Width="*" 不起作用,看起来我已将所有列宽度设置为“Auto”,因为它们不相等。查看我的更新。 仅供参考:您知道您可以定义 Binding Text1, FallbackValue='SomeLongText' 以在绑定为空白时在设计器中显示内容吗?在开发过程中对我有很大帮助。 请注意这不是答案。这很有帮助,我投了赞成票,但是在发布此答案后我开始了赏金,因为这还不够。 这对我来说很简单【参考方案3】:我想我知道要走的路,剩下的交给你。在本例中,我使用转换器(转换器如下)将 FontSize 绑定到 TextBlock 的 ActualHeight:
<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();
【讨论】:
宽度呢?宽度可能比高度更受关注,它取决于文本内容。 我会根据所有三个文本的总和的长度来限制转换器中的字体大小,但我没有在我的示例中实现这一点,因为它会很容易摆弄文本-三个字符串的长度。您还说您不想拥有按比例拉伸的文本(从而设置字体宽度,无论如何都不存在)。所以剩下的只是微调转换器,也许在包含字符串的 viewModel 对象的帮助下。【参考方案4】:一般说明:整个文本缩放的一种可能替代方法是仅在 TextBlocks 上使用 TextTrimming。
我一直在努力寻找解决方案。使用视图框很难与任何布局调整混合使用。最糟糕的是,ActualWidth 等不会在视图框内更改。所以我最终决定只在绝对必要时才使用视图框,也就是剪裁发生的时候。因此,我根据可用空间在 ContentPresenter 和 Viewbox 之间移动内容。
这个解决方案并不像我想要的那样通用,主要是 MoveToViewboxBehavior 确实假设它附加到具有以下结构的网格。如果不能适应,则很可能必须调整行为。创建一个用户控件并表示必要的部分(PART_...)可能是一个有效的替代方法。
请注意,我已将网格的列从三列扩展到五列,因为这使解决方案更容易。这意味着中间的文本块在绝对坐标的意义上不会正好在中间,而是在左右文本块之间居中。
<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);
视觉助手:
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;
【讨论】:
有条件地使用 ViewBox 的想法很聪明,但正如您提到的那样,它确实有一些限制。首先,您需要修改 Grid 的列。其次,一旦 ViewBoxes 在里面,就不可能得到比例列。第三,它不考虑 IsClipped 中的高度,但这可能是一个简单的更改,除非我不相信如果您在这种情况下使用 ViewBox,宽度会起作用。基本上,不像你提到的那样普遍。不过,这不是一个坏主意,将其打包为一种行为,并通过切换到 ViewBoxes 来跳出框框思考,以避免 1/2 的问题。 请扩展您的第一个和第二个限制。我似乎在实现您的目标时遇到了一些问题,因为我的解决方案 确实 看起来像“第一个屏幕截图,但没有左/右浪费的边距”,字体大小始终相等,最大字体大小可以轻松设置...请参阅我的屏幕截图,看看应该有什么不同。我没有包括从顶部剪裁,因为我没有看到窗口应该如何缩小这么小并且仍然有意义,但我同意这是缺失的。 我更新了我的帖子以说明我在前两点中所指的内容。我希望这有助于解释。在原始问题的第一个代码 sn-p 中,我打算使用比例( Width="*" )列。我所指的第三点是,如果我有一个超过 1 行(比如 2-3)的网格,其中高度截止可能变得相关。然后我需要改变 IsClipped 以考虑高度。如果由于高度裁剪而插入 ViewBox,那么宽度将再次以边距结束,不是吗? 例如,在您的第一个屏幕截图中,将第一个 TextBlock 设置为“SomeLongLongLongLongText”,第二个 Text 将偏离中心,因为它们是自动调整大小的。当 ViewBox 接管时,它总是自动调整网格列的大小。 (大概是因为它给了它无限的空间,所以比例逻辑并没有真正起作用) 为澄清干杯。我已经发布了another answer,希望它更适合你,但我会留下这个,因为例如状态栏,这就是我希望它的行为方式。【参考方案5】:您可以在 ViewBox 中使用隐藏的 ItemsControl。
<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>
或
<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>
【讨论】:
【参考方案6】:解决方案可能是这样的:
选择一个 maxFontSize,然后使用线性方程定义要显示的适当 FontSize 考虑当前窗口。窗口的高度或宽度会限制最终的 FontSize 选择。
让我们以整个网格的“单一类型文本块”为例:
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);
【讨论】:
以上是关于如何自动缩放一组控件的字体大小?的主要内容,如果未能解决你的问题,请参考以下文章
AppCompatTextView自动缩放字体在RecyclerView中卡顿,自定义高效自动缩放TextView