我想制作一个
Viewbox
(或类似的东西),仅缩放其高度,然后水平拉伸其内容。
如果我这样做:
<Viewbox>
<StackPanel>
<Button>Foo</Button>
<Button>Bar</Button>
</StackPanel>
</Viewbox>
然后我明白了:
(来源:excastle.com)
它的作用就好像两个按钮都有
HorizontalAlignment="Center"
,然后缩放结果。但我不想要HorizontalAlignment="Center";
我想要HorizontalAlignment="Stretch"
,就像这样:
(来源:excastle.com)
所以,我希望它读取其内容所需的高度,仅根据高度计算缩放因子,然后允许缩放后的内容水平拉伸。
有什么方法可以使用
Viewbox
和/或某些第三方面板来完成此操作吗?
WPF 中没有这样的控件,但您可以自己编写一个,不会太麻烦。 这是一个具有您的规格的自定义 ViewboxPanel:
public class ViewboxPanel : Panel
{
private double scale;
protected override Size MeasureOverride(Size availableSize)
{
double height = 0;
Size unlimitedSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
foreach (UIElement child in Children)
{
child.Measure(unlimitedSize);
height += child.DesiredSize.Height;
}
scale = availableSize.Height / height;
return availableSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
Transform scaleTransform = new ScaleTransform(scale, scale);
double height = 0;
foreach (UIElement child in Children)
{
child.RenderTransform = scaleTransform;
child.Arrange(new Rect(new Point(0, scale * height), new Size(finalSize.Width / scale, child.DesiredSize.Height)));
height += child.DesiredSize.Height;
}
return finalSize;
}
}
你可以像这样使用它:
<local:ViewboxPanel>
<Button>Foo</Button>
<Button>Bar</Button>
</local:ViewboxPanel>
它肯定需要一些工作,但这可能会让你开始。
保持宽度正常工作:
protected override Size MeasureOverride(Size availableSize)
{
double height = 0;
Size unlimitedSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
foreach (UIElement child in Children)
{
child.Measure(unlimitedSize);
height += child.DesiredSize.Height;
}
scale = availableSize.Height / height;
foreach (UIElement child in Children)
{
unlimitedSize.Width = availableSize.Width / scale;
child.Measure(unlimitedSize);
}
return availableSize;
}
我很确定答案是“不容易”。
这是一个似乎可行但有点笨拙的想法:
将 StackPanel 放入 UserControl(下例中的
UserControl1
)。将此 UserControl 的两个副本放入容器(下例中的网格)中。
a.将第一个副本顶部/左侧对齐,使其保持默认大小。该副本严格用于测量目的,应将其可见性设置为隐藏。
b.将第二个副本放入视图框内。该副本是用户实际看到的副本。
使用带有 MultiValueConverter 的 MultiBinding 来调整第二个 UserControl 的宽度,使其在 被 Viewbox 展开之前具有正确的宽高比。
这是标记:
<Grid>
<local:UserControl1 x:Name="RawControl" HorizontalAlignment="Left" VerticalAlignment="Top" Visibility="Hidden" />
<Viewbox>
<local:UserControl1>
<local:UserControl1.Width>
<MultiBinding Converter="{StaticResource WidthAdjuster}">
<Binding ElementName="RawControl" Path="ActualHeight" />
<Binding RelativeSource="{RelativeSource AncestorType=Grid}" Path="ActualWidth" />
<Binding RelativeSource="{RelativeSource AncestorType=Grid}" Path="ActualHeight" />
</MultiBinding>
</local:UserControl1.Width>
</local:UserControl1>
</Viewbox>
</Grid>
这是多值转换器
public class WidthAdjuster : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var rawHeight = (double)values[0];
var containerWidth = (double)values[1];
var containerHeight = (double)values[2];
var ratio = containerWidth / containerHeight;
return rawHeight * ratio;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
525 x 350 容器的结果
我有一个几乎类似的问题。我的面板必须适合所有子面板,将它们排成一排并拉伸它们,均匀地填充面板。上面的算法使用渲染变换来缩放元素。问题是渲染变换会拉伸子项本身,但忽略边距。如果边距较高且比例系数低于 1,则元素会从面板中脱落。您必须根据该轴上的边距校正 RenderTransform 的比例,并使用相同的比例系数进行排列。我使用的MeasureOverride是
protected override Size MeasureOverride(Size availableSize)
{
double width = 0; double maxHeight = 0; double mY=0; double mX=0;
Size unlimitedSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
foreach (UIElement child in Children)
{
child.Measure(unlimitedSize);
width += child.DesiredSize.Width;
maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
FrameworkElement cld = child as FrameworkElement;
mY = cld.Margin.Top + cld.Margin.Bottom;
mX = cld.Margin.Left + cld.Margin.Right;
}
double scaleX = availableSize.Width / width;
double scaleY = availableSize.Height / maxHeight;
//That is scale for arranging
positionScaling = Math.Min(scaleX, scaleY);
try
{
// Let FrameworkElement hight be Xn. mY = Element.Margin.Top + Element.Margin.bottom.
// DesiredSize includes margin therefore:
// (Yn + mY) * scaleY = availableSize.Height
// But render transform doesn't scales margin. Actual render height with margin will be
// Yn * RenderScaleY + mY = availableSize.Height;
// We must find render transform scale coeff like this:
double yn = availableSize.Height / scaleY - mY;
scaleY = (availableSize.Height - mY) / yn;
double xn = availableSize.Width / scaleX - mX;
scaleX = (availableSize.Width - mX) / xn;
scale = Math.Min(scaleX, scaleY); //scale to use in RenderTransform
// In my project all children are similar in size and margin, algorithm BREAKS otherwise!!!
}
catch { scale = 1; }
return availableSize;
}
再一次:在我的项目中,所有子项的大小和边距都相似,否则算法会崩溃。
除了Rick Sladkey的回答之外,当所有元素的总高度变得太小并且水平方向发生尺寸过大,使得元素在右侧被切割时,这里有一个测量方法的小修复:
protected override Size MeasureOverride(Size availableSize)
{
double height = 0;
Size unlimitedSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
foreach (UIElement child in Children)
{
child.Measure(unlimitedSize);
height += child.DesiredSize.Height;
}
scale = availableSize.Height / height;
foreach (UIElement child in Children)
{
var childDesireWidth = child.DesiredSize.Width;
if (childDesireWidth * scale > availableSize.Width)
scale = availableSize.Width / childDesireWidth;
}
return availableSize;
}