使Viewbox垂直缩放但水平拉伸

时间:2010-12-28 00:17:30

标签: wpf viewbox

我想创建一个仅扩展其高度的Viewbox(或类似的东西),然后水平拉伸其内容。

如果我这样做:

<Viewbox>
  <StackPanel>
    <Button>Foo</Button>
    <Button>Bar</Button>
  </StackPanel>
</Viewbox>

然后我明白了:

http://www.excastle.com/misc/viewbox-center.png

它就好像两个按钮都具有Horizo​​ntalAlignment =“Center”,然后缩放结果。但我不希望Horizo​​ntalAlignment =“Center”;我想要Horizo​​ntalAlignment =“Stretch”,如下所示:

http://www.excastle.com/misc/viewbox-stretch.png

所以我希望它读取其内容的所需高度,仅根据高度计算缩放因子,然后允许缩放内容水平拉伸。

有没有办法使用Viewbox和/或某些第三方面板完成此操作?

4 个答案:

答案 0 :(得分:16)

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>

这肯定需要一些工作,但这可能会让你开始。

答案 1 :(得分:4)

为了保持宽度正常工作:

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

答案 2 :(得分:0)

我很确定答案是“不容易”。

这个想法似乎有效,但它有点笨重:

  1. 将StackPanel扔进UserControl(下例中的UserControl1)。

  2. 将此UserControl的两个副本放入容器中(以下示例中为Grid)。

    一个。将第一个副本顶部/左侧对齐,使其保持默认大小。此副本仅用于测量目的,其“可见性”设置为“隐藏”。

    湾将第二个副本放在Viewbox中。此副本是用户实际看到的副本。

  3. 使用MultiBinding和MultiValueConverter调整第二个UserControl的宽度,使其在之前具有正确的宽高比。它可以通过Viewbox进行扩展。

  4. 以下是标记:

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

    这是MultiValueConverter

    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容器的结果

    alt text

答案 3 :(得分:0)

我有一个几乎相似的问题。我的面板必须适合所有的孩子,将tham放在一排并拉伸它们,均匀地填充面板。上面的算法使用渲染变换来缩放元素。问题是渲染变换会拉伸孩子自己,但忽略了边距。如果保证金很高且比例系数低于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;
}

再一次:在我的项目中,所有孩子的大小和边距都相似,否则算法会打破。