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

标签 c# wpf textbox grid viewbox

所以我正在尝试构建一个表单,它会根据父容器的可用宽度和相同的列百分比自动按比例上下缩放,如下所示:

Desired result

周围的其他内容也需要缩放,例如图像和按钮(它们不会在同一个网格中),根据我目前所读的内容,使用 Viewbox 是可行的方法。

但是,当我用 Stretch="Uniform"将我的网格包裹在一个 Viewbox 中时,Textbox 控制每个折叠到它们的最小宽度,看起来像这样: Viewbox with grid and collapsed textboxes

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

如果我在文本框中键入任何内容,它们将增加宽度以包含文本: Textboxes expand to fit content

...但我不想要这种行为 - 我希望文本框元素宽度与网格列宽相关联,而不是依赖于内容。

现在,我查看了各种 SO 问题,这个问题最接近我所追求的问题: How to automatically scale font size for a group of controls?

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

我尝试了多种方法 - 不同的 Horizo​​ntalAlignment="Stretch"设置等等,但到目前为止都没有任何效果。这是我的 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>

最佳答案

此行为的原因是 Viewbox 子级被赋予了无限的空间来测量其所需的大小。将 TextBoxes 拉伸(stretch)到无限 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();
    }
}

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

<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>

更新

I'm having trouble understanding the original problem of the infinite grid width.

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

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

Invade

我用标签 FooBar 替换了文本框,并将它们的背景颜色设置为灰色。现在我们可以看到 Bar 正在入侵 Body 领域,这显然不是我们想要发生的事情。

同样,问题的根源来自 Viewbox 不知道如何将无穷大分成 6 等份(映射到列宽 1*2 *, 1*,2*), 所以我们需要做的就是用网格宽度恢复链接。在 ToWidthConverter 中,目的是将 TextBoxWidth 映射到 GridColumnWidth 2*,所以我使用了 gridWidth * 2/6。现在 Viewbox 可以再次求解方程:每个 TextBox 得到 gridwidth 的三分之一,每个 Label 得到一个一半(1* 对比 2*)。

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

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

增加一些灵 active 的一种方法是将 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;
    }
}

gridWidth 平均分成 10 份,并将这些份额相应地分配给组件的示例。

<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>

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

Parts

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

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

关于c# - 使用 Viewbox 缩放包含标签和文本框的网格,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50457409/

相关文章:

wpf - 在文本框中输入时执行 viewmodels 命令

C# fileattachment 内容到字符串

c# - Equals 和 GetHashCode 如何在匿名类型上实现?

c# - 将部分文件添加到现有表单文件

c# - 文本框 - 绑定(bind)的属性值不会立即更新

c# - 在 XAML 中使用 System.Type

c# - 在 WPF 中显示 BitmapSource 不起作用

javascript - 在两个文本字段中显示完全相同的选择范围

C# CancelButton 关闭对话框?

c# - 字母数字文本框 - 粘贴前验证/清理剪贴板文本