如何自动缩放一组控件的字体大小?

Ala*_*lan 46 wpf font-size mvvm

我在网格中的WPF中有一些TextBlocks,我想根据它们的可用宽度/高度进行缩放.当我搜索自动缩放字体大小时,典型的建议是将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>
Run Code Online (Sandbox Code Playgroud)

它会自动缩放每个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>
Run Code Online (Sandbox Code Playgroud)

副作用

老实说,缺少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>
Run Code Online (Sandbox Code Playgroud)

没有MaxSize的文字太大

在这里,我想限制字体的大小,这样就不会浪费垂直窗口的空间.我希望输出左,中,右对齐,Font尽可能大,达到所需的最大尺寸.


@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>
Run Code Online (Sandbox Code Playgroud)

这是结果.请注意,它并不知道它会在早期被剪切,然后当它替换ViewBox时,它看起来好像Grid默认为列大小"Auto"并且不再对齐center.

与adabyron的建议相符

Mik*_*chs 31

我想编辑我已经提供的答案,但后来决定发布一个新的更有意义,因为它实际上取决于我更喜欢​​的要求.这可能更适合艾伦的想法,因为

  • 中间的文本块保留在窗口的中间
  • 由于高度削减而进行的字体大小调整是适应的
  • 相当多一般
  • 没有涉及查看框

在此输入图像描述

在此输入图像描述

另一个的优点是

  • 文本块的空间分配更有效(没有不必要的边距)
  • 文本块可能具有不同的字体大小

我在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>
Run Code Online (Sandbox Code Playgroud)

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)


mat*_*ieu 10

将您的网格放在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>
Run Code Online (Sandbox Code Playgroud)

  • 仅供参考:你知道你可以定义{Binding Text1,FallbackValue ='SomeLongText'}来在Bindings空白时在设计器中显示内容吗?在开发时帮助我很多. (6认同)