2010-12-28 15 views
15

Chcę utworzyć Viewbox (lub coś podobnego), który skaluje tylko jego wysokość, a następnie rozciąga jego zawartość w poziomie.Dokonywanie skali Viewbox w pionie, ale rozciąganie w poziomie

Gdybym to zrobić:

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

następnie uzyskać to:

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

Działa jak wtedy, gdy oba przyciski miał horizontalAlignment = "center", a następnie skaluje wynik . Ale nie chcę HorizontalAlignment = "Center"; Chcę HorizontalAlignment = „Stretch”, tak:

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

Więc chcę czytać pożądaną wysokość jego zawartość, obliczyć współczynnik skalowania opartego tylko na wysokości, a następnie pozostawić skalowany zawartość rozciągnąć poziomo.

Czy można to osiągnąć za pomocą Viewbox i/lub panelu innej firmy?

Odpowiedz

16

Nie ma takiej kontroli z WPF, ale możesz napisać samemu bez większych problemów.Tutaj jest zwyczaj ViewboxPanel że ma swoje wymagania:

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

i go używać tak:

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

To na pewno wymaga trochę pracy, ale może to dobry początek.

+0

Dzięki za świetne rozwiązanie. Dodałem 'if (System.ComponentModel.DesignerProperties.GetIsInDesignMode (this)) { availableSize = new Size (200, 200); } ' w metodzie' protected override Rozmiar MeasureOverride (Size availableSize) ', więc projektant działa bez wyjątku. – Mike

0

Jestem prawie pewien, że odpowiedź brzmi "nie jest łatwo".

Oto pomysł, który wydaje się działać, ale to trochę niezgrabne:

  1. Rzuć StackPanel w UserControl (UserControl1 w poniższym przykładzie).

  2. Umieść dwie kopie tej kontrolki UserControl w pojemniku (siatka w poniższym przykładzie).

    a. Dopasuj pierwszą kopię w górę/w lewo, aby pozostała ona w domyślnym rozmiarze. Ta kopia jest przeznaczona wyłącznie do celów pomiarowych i powinna mieć Widoczność ustawioną na Ukrytą.

    b. Umieść drugą kopię w oknie podglądu. Ta kopia jest tą, którą użytkownik zobaczy.

  3. Użyć MultiBinding z MultiValueConverter dostosować szerokość drugiej UserControl tak, że ma prawidłową proporcji przed to zostanie rozszerzony przez Viewbox.

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

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

Wynik dla 525 x 350 pojemnik

alt text

4

Aby zachować szerokość, aby działać poprawnie:

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

miałem prawie podobny problem. Mój panel musiał pasować do wszystkich swoich dzieci, umieszczając go w szeregu i rozciągając, wypełniając panel równomiernie. Powyższy algorytm używa renderowania do skalowania elementów. Problem polega na tym, że transformacja renderowania rozciąga same dzieci, ale ignoruje margines. Jeśli margines jest wysoki, a współczynnik skali jest niższy niż 1, elementy wypadają z panelu. Musisz skorygować skalę dla RenderTransform zgodnie z marginesem na tej osi i użyć tego samego współczynnika skali do aranżacji. MeasureOverride Kiedyś był

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

Jeszcze raz: w moim projekcie wszystkie dzieci są podobne pod względem wielkości i marginesu, algorytm WAKACJE inaczej.

Powiązane problemy