因此,我正在尝试构建一个表单,该表单将根据父容器的可用宽度和相同的列百分比自动按比例上下调整,如下所示:
还有其他周边内容需要扩展,例如图像和按钮(不会在同一个网格中),从我到目前为止所读到的内容,使用Viewbox是可行的方法。
然而,当我将网格包装在一个Stretch =“Uniform”的Viewbox中时,Textbox控制每个网格折叠到它们的最小宽度,如下所示:
如果我增加容器宽度,所有内容都会按预期(好)进行缩放,但文本框仍然会折叠到最小可能宽度(错误):
...但我不希望这种行为 - 我希望Textbox元素的宽度与网格列的宽度相关联,而不是依赖于内容。
现在,我已经看过各种各样的SO问题了,这个问题最接近我所追求的问题: How to automatically scale font size for a group of controls?
...但它仍然没有真正处理文本框宽度行为(当它与Viewbox beahvior交互时),这似乎是主要问题。
我尝试过各种各样的东西 - 不同的HorizontalAlignment =“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>
答案 0 :(得分:4)
此行为的原因是Viewbox
子项被赋予无限空间来测量其所需大小。将TextBox
es拉伸到无限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>
<强>更新强>
我无法理解无限的原始问题 网格宽度。
无限空间方法通常用于确定UIElement
的{{3}}。简而言之,您可以为控件提供它可能需要的所有空间(无约束),然后测量它以检索其所需的大小。
Viewbox
使用此方法衡量其子级,但我们的Grid
大小是动态的(代码中未设置Height
或Width
),因此Viewbox
在网格儿童的另一个级别,看看它是否可以通过获取组件的总和来确定大小。
但是,如果此组件总数超出总可用大小,则可能会遇到问题,如下所示。
我将文本框替换为标签Foo
和Bar
,并将其背景颜色设置为灰色。现在我们可以看到Bar
正在入侵Body
领域,这显然不是我们想要发生的事情。
同样,问题的根源来自Viewbox
,不知道如何将无穷大划分为6个相等的份额(映射到列宽1*
,2*
,1*
,2*
),所以我们需要做的就是恢复网格宽度的链接。在ToWidthConverter
中,目标是将TextBox
'Width
映射到Grid
的{{1}} ColumnWidth
,因此我使用了2*
}。现在gridWidth * 2/6
能够再次解决方程:每个Viewbox
得到TextBox
的三分之一,而gridwidth
的一半得到Label
vs {{1*
1}})。
当然,当您通过引入新列来加扰时,您必须注意保持组件的总和与总可用宽度同步。换句话说,该等式需要是可解的。 输入数学,所需大小(您没有约束的控件,我们示例中的标签)和转换后的大小(作为2*
的部分,我们示例中的文本框)的总和需要是小于或等于可用大小(在我们的示例中为gridWidth
)。
如果您使用gridWidth
es的转换尺寸,我会发现缩放效果很好,并让星号TextBox
处理大多数其他尺寸。请记住保持在总可用尺寸范围内。
添加一些灵活性的一种方法是在混合中添加ColumnWidth
。
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;
}
}
除以10个相等份额的示例,并相应地将这些份额分配给组件。
gridWidth
请注意每个控件的份额,分组为2 - 2 - 2 - 3 - 1(按钮宽度为1)。
最后,根据您所使用的可重用性,还有其他一些方法可以解决这个问题:
<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>
上设置固定大小。缺点:
Grid
。正如您在链接的Behavior
帖子中的一个答案中所做的那样,而是实现了将列宽映射到FontSize
的部分。答案 1 :(得分:1)
您的方法存在的问题是,Grid
并不能完全按照我们直觉的方式工作。也就是说,如果满足以下条件, star 大小可以按预期工作 :
Grid
的水平对齐设置为Stretch
Grid
包含在有限大小的容器中,即其Measure
方法接收限制为Width
(不是double.PositiveInfinity
)的约束这与列大小有关;行大小是对称的。在您的情况下,第二个条件不符合。我不知道任何简单的技巧让Grid
按预期工作,所以我的解决方案是创建可以完成工作的自定义Panel
。这样您就可以完全控制控件的布局方式。虽然它需要对 WPF 布局系统的工作原理有一定程度的了解,但这并不难实现。
以下是进行出价的示例实施。为了简洁起见,它只能水平工作,但要将其扩展到垂直工作并不困难。
public class ProportionalPanel : Panel
{
protected override Size MeasureOverride(Size constraint)
{
/* Here we measure all children and return minimal size this panel
* needs to be to show all children without clipping while maintaining
* the desired proportions between them. We should try, but are not
* obliged to, fit into the given constraint (available size) */
var desiredSize = new Size();
if (Children.Count > 0)
{
var children = Children.Cast<UIElement>().ToList();
var weights = children.Select(GetWeight).ToList();
var totalWeight = weights.Sum();
var unitWidth = 0d;
if (totalWeight == 0)
{
//We should handle the situation when all children have weights set
//to 0. One option is to measure them with 0 available space. To do
//so we simply set totalWeight to something other than 0 to avoid
//division by 0 later on.
totalWeight = children.Count;
//We could also assume they are to be arranged uniformly, so we
//simply coerce their weights to 1
for (var i = 0; i < weights.Count; i++)
weights[i] = 1;
}
for (var i = 0; i < children.Count; i++)
{
var child = children[i];
child.Measure(new Size
{
Width = constraint.Width * weights[i] / totalWeight,
Height = constraint.Height
});
desiredSize.Width += child.DesiredSize.Width;
desiredSize.Height =
Math.Max(desiredSize.Height, child.DesiredSize.Height);
if (weights[i] != 0)
unitWidth =
Math.Max(unitWidth, child.DesiredSize.Width / weights[i]);
}
if (double.IsPositiveInfinity(constraint.Width))
{
//If there's unlimited space (e.g. when the panel is nested in a Viewbox
//or a StackPanel) we need to adjust the desired width so that no child
//is given less than desired space while maintaining the desired
//proportions between them
desiredSize.Width = totalWeight * unitWidth;
}
}
return desiredSize;
}
protected override Size ArrangeOverride(Size constraint)
{
/* Here we arrange all children into their places and return the
* actual size this panel is. The constraint will never be smaller
* than the value of DesiredSize property, which is determined in
* the MeasureOverride method. If the desired size is larger than
* the size of parent element, the panel will simply be clipped
* or appear "outside" of the parent element */
var size = new Size();
if (Children.Count > 0)
{
var children = Children.Cast<UIElement>().ToList();
var weights = children.Select(GetWeight).ToList();
var totalWeight = weights.Sum();
if (totalWeight == 0)
{
//We perform same routine as in MeasureOverride
totalWeight = children.Count;
for (var i = 0; i < weights.Count; i++)
weights[i] = 1;
}
var offset = 0d;
for (var i = 0; i < children.Count; i++)
{
var width = constraint.Width * weights[i] / totalWeight;
children[i].Arrange(new Rect
{
X = offset,
Width = width,
Height = constraint.Height,
});
offset += width;
size.Width += children[i].RenderSize.Width;
size.Height = Math.Max(size.Height, children[i].RenderSize.Height);
}
}
return size;
}
public static readonly DependencyProperty WeightProperty =
DependencyProperty.RegisterAttached(
name: "Weight",
propertyType: typeof(double),
ownerType: typeof(ProportionalPanel),
defaultMetadata: new FrameworkPropertyMetadata
{
AffectsParentArrange = true, //because it's set on children and is used
//in parent panel's ArrageOverride method
AffectsParentMeasure = true, //because it's set on children and is used
//in parent panel's MeasuerOverride method
DefaultValue = 1d,
},
validateValueCallback: ValidateWeight);
private static bool ValidateWeight(object value)
{
//We want the value to be not less than 0 and finite
return value is double d
&& d >= 0 //this excludes double.NaN and double.NegativeInfinity
&& !double.IsPositiveInfinity(d);
}
public static double GetWeight(UIElement d)
=> (double)d.GetValue(WeightProperty);
public static void SetWeight(UIElement d, double value)
=> d.SetValue(WeightProperty, value);
}
用法如下:
<local:ProportionalPanel>
<Label Content="Field A" local:ProportionalPanel.Weight="1" />
<TextBox local:ProportionalPanel.Weight="2" />
<Label Content="Field B" local:ProportionalPanel.Weight="1" />
<TextBox local:ProportionalPanel.Weight="2" />
</local:ProportionalPanel>