2010-12-28 6 views
15

Quiero hacer un Viewbox (o algo similar) que solo escala su altura, y luego estira su contenido horizontalmente.Hacer que Viewbox se escale verticalmente pero estirar horizontalmente

Si hago esto:

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

entonces me sale esto:

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

Actúa como si tanto de los botones tenían HorizontalAlignment = "centro" y, a continuación, escala el resultado . Pero no quiero HorizontalAlignment = "Center"; Quiero HorizontalAlignment = "estiramiento", así:

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

Así que desea que diga altura deseada de su contenido, calcular un factor de escala basándose sólo en la altura, y luego permitir que el contenido de escalar a estirar horizontalmente.

¿Hay alguna forma de lograr esto usando Viewbox y/o algún panel de terceros?

Respuesta

16

Este tipo de control no se incluye con WPF, pero puede escribir uno sin demasiados problemas.Aquí está una ViewboxPanel personalizada que tiene sus especificaciones:

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

y se utiliza de esta manera:

<local:ViewboxPanel> 
    <Button>Foo</Button> 
    <Button>Bar</Button> 
</local:ViewboxPanel> 

Sin duda necesita un poco de trabajo, pero esto podría ayudarle a empezar.

+0

Gracias por la gran solución. He añadido 'if (System.ComponentModel.DesignerProperties.GetIsInDesignMode (this)) { availableSize = new Size (200, 200); } ' en el método' protección anulada Tamaño MeasureOverride (Tamaño availableSize) 'para que el diseñador funcione sin lanzar una excepción. – Mike

0

Estoy bastante seguro de que la respuesta es "no fácil".

He aquí una idea que parece funcionar, pero es una especie de torpe:

  1. Lanza tu StackPanel en un control de usuario (UserControl1 en el ejemplo a continuación).

  2. Coloque dos copias de este UserControl en un contenedor (una cuadrícula en el ejemplo a continuación).

    a. Alinee la primera copia arriba/izquierda para que permanezca en su tamaño predeterminado. Esta copia es estrictamente para fines de medición y debe tener su Visibilidad establecida en Oculto.

    b. Coloque la segunda copia dentro de una Viewbox. Esta copia es la que el usuario realmente verá.

  3. Utilice un MultiBinding con un MultiValueConverter para ajustar la anchura de la segunda UserControl de modo que tenga la relación de aspecto correcta antes, el fichero es ampliado por el Viewbox.

Así es el marcado:

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

Aquí está la 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(); 
    } 
} 

Resultado para 525 x 350 contenedor

alt text

4

Para mantener el ancho para que funcione correctamente:

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

tuve un problema casi similar. Mi panel tenía que adaptarse a todos sus niños, colocando tham en una fila y estirándolos, llenando el panel de manera uniforme. El algoritmo anterior usa transformación de transformación para escalar elementos. El problema es que la transformación de render estira a los niños ellos mismos, pero ignora el margen. Si el margen es alto y el coeficiente de escala está por debajo de 1, los elementos se caen del panel. Debe corregir la escala para RenderTransform de acuerdo con el margen en ese eje y usar el mismo coeficiente de escala para organizar. MeasureOverride que utilicé fue

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

Una vez más: en mi proyecto todos los niños son similares en tamaño y el margen, el algoritmo ROMPE lo contrario.

Cuestiones relacionadas