使用Viewbox缩放包含标签和文本框的网格

jhi*_*man 7 c# wpf grid textbox viewbox

因此,我正在尝试构建一个表单,该表单将根据父容器的可用宽度和相同的列百分比自动按比例上下调整,如下所示:

期望的结果

还有其他周边内容需要扩展,如图像和按钮(不会在同一网格中),从我到目前为止所读到的内容,使用Viewbox是可行的方法.

但是,当我将网格包装在Stretch ="Uniform"的Viewbox中时,Textbox控制每个向下折叠到它们的最小宽度,如下所示: 带有网格和折叠文本框的Viewbox

如果我增加容器宽度,一切都按预期(好)缩放,但文本框仍然折叠到最小可能宽度(坏): 增加宽度

如果我在文本框中键入任何内容,他们将增加其宽度以包含文本: 文本框展开以适合内容

...但我不希望这种行为 - 我希望Textbox元素的宽度与网格列的宽度相关联,而不是依赖于内容.

现在,我已经看过各种各样的SO问题了,这个问题最接近我所追求的: 如何自动缩放一组控件的字体大小?

...但它仍然没有真正处理文本框宽度行为(当它与Viewbox beahvior交互时),这似乎是主要问题.

我尝试过各种各样的东西 - 不同的Horizo​​ntalAlignment ="拉伸"设置等等,但到目前为止还没有任何工作.这是我的XAML:

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" />
            <ColumnDefinition Width="5" />
            <ColumnDefinition Width="2*" />
        </Grid.ColumnDefinitions>

        <StackPanel Grid.Column="0">
            <StackPanel.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="Silver" Offset="0"/>
                    <GradientStop Color="White" Offset="1"/>
                </LinearGradientBrush>
            </StackPanel.Background>

            <Viewbox Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                <Grid Background="White">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="1*" />
                        <ColumnDefinition Width="2*" />
                        <ColumnDefinition Width="1*" />
                        <ColumnDefinition Width="2*" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Label Content="Field A" Grid.Column="0" Grid.Row="0" />
                    <TextBox Grid.Column="1" Grid.Row="0" HorizontalAlignment="Stretch"></TextBox>
                    <Label Content="Field B" Grid.Column="2" Grid.Row="0"  />
                    <TextBox Grid.Column="3" Grid.Row="0" HorizontalAlignment="Stretch"></TextBox>
                </Grid>
            </Viewbox>
            <Label Content="Other Stuff"/>
        </StackPanel>
        <GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" Height="100" Width="5"/>
        <StackPanel Grid.Column="2">
            <Label Content="Body"/>
        </StackPanel>
    </Grid>
</Window>
Run Code Online (Sandbox Code Playgroud)

Fun*_*unk 6

这种行为的原因Viewbox是给了孩子无限的空间来测量其所需的大小。将TextBoxes 扩展到无限Width没有多大意义,因为无论如何都无法渲染,因此返回它们的默认大小。

您可以使用转换器来实现所需的效果。

public class ToWidthConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double gridWidth = (double)value;
        return gridWidth * 2/6;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以将所有内容连接起来添加这些资源。

<Viewbox.Resources>
    <local:ToWidthConverter x:Key="ToWidthConverter"/>
    <Style TargetType="{x:Type TextBox}">
        <Setter Property="Width" 
                Value="{Binding ActualWidth, 
                    RelativeSource={RelativeSource AncestorType={x:Type Grid}}, 
                    Converter={StaticResource ToWidthConverter}}"/>
    </Style>
</Viewbox.Resources>
Run Code Online (Sandbox Code Playgroud)

更新

我无法理解无限网格宽度的原始问题。

无限空间的方法经常被用来确定DesiredSizeUIElement。简而言之,您可以为控件提供它可能需要的所有空间(无限制),然后对其进行测量以检索其所需的大小。 Viewbox使用这种方法来测量它的孩子,但我们的Grid大小是动态的(没有HeightWidth在代码中设置),所以Viewbox在网格孩子的另一个级别下,看看它是否可以通过获取组件的总和来确定大小。

但是,当组件的总和超过可用的总大小时,您可能会遇到问题,如下所示。

入侵

我换成文本框与标签Foo,并Bar和设置他们的backgroundColor至灰色。现在我们可以看到Bar正在入侵Body领土,这显然不是我们想要发生的事情。

同样,问题的根源在于Viewbox不知道如何将无穷大分成 6 个相等的份额(映射到 columnwidths 1*, 2*, 1*, 2*),所以我们需要做的就是恢复与网格宽度的链接。在ToWidthConverter目的是要映射TextBoxWidthGrid小号ColumnWidth2*,所以我用gridWidth * 2/6。现在Viewbox可以再次解方程:每个人TextBox得到 的三分之一gridwidth,以及每人的Label一半 ( 1*vs 2*)。

当然,当你把事情弄乱时,通过引入新的列,你必须注意保持组件的总和与总可用宽度同步。换句话说,方程必须是可解的。在数学中,所需大小(您没有约束的控件,我们示例中的标签)和转换后的大小(作为gridWidth我们示例中的文本框的一部分)的总和需要小于或等于可用大小(gridWidth在我们的例子中)。

我发现如果您使用TextBoxes转换后的大小,缩放效果会很好,并让星形大小的ColumnWidths 处理大多数其他大小。请记住保持在总可用大小范围内。

增加一些灵活性的一种方法是ConverterParameter在混合中添加一个。

public class PercentageToWidthConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double gridWidth = (double)value;
        double percentage = ParsePercentage(parameter);
        return gridWidth * percentage;
    }

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

    private double ParsePercentage(object parameter)
    {
        // I chose to let it fail if parameter isn't in right format            
        string[] s = ((string)parameter).Split('/');
        double percentage = Double.Parse(s[0]) / Double.Parse(s[1]);
        return percentage;
    }
}
Run Code Online (Sandbox Code Playgroud)

一个示例,将gridWidth10 个相等的份额分开,并相应地将这些份额分配给组件。

<Viewbox Stretch="Uniform">
    <Viewbox.Resources>
        <local:PercentageToWidthConverter x:Key="PercentageToWidthConverter"/>
    </Viewbox.Resources>
    <Grid Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="3*" />
            <ColumnDefinition Width="1*" />
        </Grid.ColumnDefinitions>
        <Label Content="Field A" Grid.Column="0" />
        <TextBox Grid.Column="1"
                 Width="{Binding ActualWidth, 
                     RelativeSource={RelativeSource AncestorType={x:Type Grid}}, 
                     Converter={StaticResource PercentageToWidthConverter}, 
                     ConverterParameter=2/10}" />
        <Label Content="Field B" Grid.Column="2"  />
        <TextBox Grid.Column="3"
                 Width="{Binding ActualWidth, 
                     RelativeSource={RelativeSource AncestorType={x:Type Grid}}, 
                     Converter={StaticResource PercentageToWidthConverter}, 
                     ConverterParameter=3/10}" />
        <Button Content="Ok" Grid.Column="4"
                Width="{Binding ActualWidth, 
                    RelativeSource={RelativeSource AncestorType={x:Type Grid}}, 
                    Converter={StaticResource PercentageToWidthConverter}, 
                    ConverterParameter=1/10}" />
    </Grid>
</Viewbox>
Run Code Online (Sandbox Code Playgroud)

注意每个控件的份额,分组为 2 - 2 - 2 - 3 - 1(按钮宽度为 1)。

部分

最后,根据您所追求的可重用性,还有一些其他方法可以处理此问题:

  • 在您的 root 上设置固定大小Grid。缺点:
    • 每次更改组件时都需要进行微调(以达到所需的水平/垂直/字体大小比例)
    • 这个比例可能会因不同的主题、Windows 版本、...
  • 添加一个Behavior. 正如在链接FontSize帖子中的一个答案中所做的那样,而是实施将列宽映射到gridWidth.
  • 按照@Grx70 的建议创建自定义面板。