Делаем Viewbox масштабируемым вертикально, но растягиваемым горизонтально - PullRequest
16 голосов
/ 28 декабря 2010

Я хочу создать Viewbox (или нечто подобное), который масштабирует только его высоту, а затем растягивает его содержимое по горизонтали.

Если я сделаю это:

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

тогда я получаю это:

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

Он действует так, как если бы обе кнопки имели HorizontalAlignment = "Center", а затем масштабирует результат. Но я не хочу HorizontalAlignment = "Center"; Я хочу HorizontalAlignment = "Растянуть", как это:

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

Итак, я хочу, чтобы он считал желаемую высоту содержимого, вычислял масштабный коэффициент, основываясь только на высоте, а затем позволил масштабированному содержимому растягиваться горизонтально.

Есть ли способ сделать это с помощью Viewbox и / или какой-нибудь сторонней панели?

Ответы [ 4 ]

16 голосов
/ 28 декабря 2010

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

Это определенно нуждается в некоторой работе, но это может помочь вам начать.

4 голосов
/ 01 февраля 2011

Чтобы ширина работала правильно:

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;
    }
0 голосов
/ 23 марта 2014

У меня была почти похожая проблема.Моя панель должна была соответствовать всем ее дочерним элементам, помещая их в ряд и растягивая их, равномерно заполняя панель.Приведенный выше алгоритм использует преобразование рендеринга для масштабирования элементов.Проблема в том, что преобразование render растягивает самих детей, но игнорирует их.Если маржа высока и масштабный коэффициент меньше 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;
}

Еще раз: в моем проекте все дочерние элементы одинаковы по размеру и размеру, алгоритм ломает в противном случае.

0 голосов
/ 28 декабря 2010

Я почти уверен, что ответ "не легко".

Вот идея, которая работает, но она неуклюжа:

  1. Бросьте свою StackPanelв UserControl (UserControl1 в приведенном ниже примере).

  2. Поместите две копии этого UserControl в контейнер (Grid в примере ниже).

    a,Выровняйте первую копию по верхнему / левому краю, чтобы она осталась в размере по умолчанию.Эта копия предназначена исключительно для целей измерения, и ее видимость должна быть скрытой.

    b.Поместите вторую копию в Viewbox.Эта копия - та, которую пользователь фактически увидит.

  3. Используйте MultiBinding с MultiValueConverter, чтобы отрегулировать ширину второго UserControl так, чтобы он имел правильное соотношение сторон перед это расширяется с помощью окна просмотра.

Вот разметка:

<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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...