Создание шкалы Viewbox по вертикали, но растяжение по горизонтали
Я хочу создать Viewbox (или нечто подобное), который масштабирует только его высоту, а затем растягивает его содержимое по горизонтали.
Если я сделаю это:
<Viewbox>
<StackPanel>
<Button>Foo</Button>
<Button>Bar</Button>
</StackPanel>
</Viewbox>
тогда я получаю это:
![]()
(источник: excastle.com)
Он действует так, как будто обе кнопки имеют HorizontalAlignment = "Center", а затем масштабирует результат. Но я не хочу HorizontalAlignment = "Center"; Я хочу HorizontalAlignment = "Растянуть", например:
![]()
(источник: excastle.com)
Поэтому я хочу, чтобы он считал желаемую высоту содержимого, вычислял коэффициент масштабирования, основываясь только на высоте, а затем позволял масштабированному содержимому растягиваться по горизонтали.
Есть ли способ сделать это с помощью Viewbox и/или какой-нибудь сторонней панели?
Ответы
Ответ 1
В 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>
Это определенно нуждается в некоторой работе, но это может заставить вас начать.
Ответ 2
Чтобы обеспечить правильную работу ширины:
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;
}
Ответ 3
Я уверен, что ответ "нелегко".
Вот идея, которая, похоже, работает, но она довольно неуклюжая:
-
Бросьте свой StackPanel в UserControl (UserControl1
в приведенном ниже примере).
-
Поместите две копии этого UserControl в контейнер (сетка в примере ниже).
а. Выровняйте первую верхнюю/верхнюю копию, чтобы она оставалась по умолчанию. Эта копия предназначена исключительно для целей измерения и должна иметь значение "Видимость" для "Скрыто".
б. Поместите вторую копию в поле "Просмотр". Эта копия является той, которую пользователь действительно увидит.
-
Используйте 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]()
Ответ 4
У меня была почти такая же проблема. Моя панель должна была приспосабливать всех своих детей, помещая их в ряд и растягивая их, равномерно заполняя панель. Алгоритм выше использует преобразование рендеринга для элементов шкалы. Проблема заключается в том, что преобразование рендеринга растягивает самих детей, но игнорирует маржу. Если маржа высока, а масштабный коэффициент ниже 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;
}
Еще раз: в моем проекте все дети схожи по размеру и маржи, алгоритм BREAKS в противном случае.