2010-12-28 7 views
15

Ich möchte eine Viewbox (oder etwas ähnliches) erstellen, die nur ihre Höhe skaliert und dann ihren Inhalt horizontal streckt.Eine Viewbox vertikal skalieren, aber horizontal strecken

Wenn ich dies tun:

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

dann bekomme ich diese:

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

Es wirkt, als ob beide der Tasten Horizontal = "Center" hatte, und dann skaliert das Ergebnis . Aber ich möchte nicht HorizontalAlignment = "Center"; Ich will Horizontal = „Stretch“, wie folgt aus:

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

So will ich es seinen Inhalt gewünschte Höhe lesen, einen Skalierungsfaktor nur auf der Höhe der Grundlage zu berechnen, und dann kann der skalierte Inhalt strecken horizontal.

Gibt es eine Möglichkeit, dies mithilfe der Viewbox und/oder eines Drittanbieter-Panels zu erreichen?

Antwort

16

Es gibt keine solche Kontrolle mit WPF, aber Sie können eine selbst ohne zu viel Mühe schreiben.Hier ist ein eigener ViewboxPanel, die Ihren Spezifikationen hat:

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

und verwenden Sie es wie folgt aus:

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

Es muss auf jeden Fall etwas Arbeit, aber dies könnte Ihnen den Start.

+0

Danke für die tolle Lösung. Ich habe hinzugefügt 'if (System.ComponentModel.DesignerProperties.GetIsInDesignMode (this)) { availableSize = neue Größe (200, 200); } ' in der Methode' protected override Size MeasureOverride (Größe availableSize) 'so der Designer funktioniert, ohne eine Ausnahme zu werfen. – Mike

0

Ich bin mir ziemlich sicher, die Antwort ist "nicht leicht".

Hier ist eine Idee, die zu funktionieren scheint, aber es ist irgendwie klobig:

  1. Werfen Sie Ihre Stackpanel in einem Usercontrol (UserControl1 im Beispiel unten).

  2. Platzieren Sie zwei Kopien dieses Benutzersteuerelements in einen Container (im folgenden Beispiel ein Raster).

    a. Richten Sie die erste Kopie oben/links so aus, dass die Standardgröße beibehalten wird. Diese Kopie dient ausschließlich zu Messzwecken und sollte als Sichtbarkeit auf Versteckt gesetzt sein.

    b. Legen Sie die zweite Kopie in eine Viewbox. Diese Kopie ist diejenige, die der Benutzer tatsächlich sieht.

  3. eine Multibinding mit einem MultiValueConverter über die Breite des zweiten Benutzersteuerelementes so anzupassen, dass es das richtige Seitenverhältnis hat, bevor es durch die Viewbox erweitert wird.

Hier ist das Markup:

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

Hier die MultiValueConverter ist

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

Ergebnis für 525 x 350 Behälter

alt text

4

Breite zu halten richtig funktionieren:

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

ich ein fast ähnliches Problem hatte. Mein Paneel musste zu allen seinen Kindern passen, sie in einer Reihe aufreihen und strecken und das Paneel gleichmäßig ausfüllen. Der obige Algorithmus verwendet Render-Transformation zu Skalierungselementen. Das Problem besteht darin, dass die Rendertransformation Kinder selbst dehnt, aber den Rand ignoriert. Wenn der Rand hoch ist und der Skalierungskoeffizient unter 1 liegt, fallen Elemente aus dem Panel heraus. Sie müssen den Maßstab für RenderTransform entsprechend dem Rand auf dieser Achse korrigieren und denselben Skalierungskoeffizienten zum Anordnen verwenden. MeasureOverride ich verwendet wurde

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

Noch einmal: in meinem Projekt alle Kinder eine ähnliche Größe und Spielraum, BRICHT Algorithmus anders.