Nostromo
Nostromo

Reputation: 1264

Keeping aspect ratio of a control

I've looked for quite a while now for a way to be able to tell a WPF control (or window) to keep a certain aspect ratio.

  1. For a Window I found this solution, that works quite well. But since it uses the Win32 API and window handles it's not working for any WPF Controls (because as far as I know in WPF only the window itself has a handle)
  2. For a Control one usually gets the advice to put the Control in a ViewBox, but I don't want to scale my controls, I want them to resize (and keep any border width or font size).
  3. Other "solutions" for a Control involve any form of binding the Width to the ActualHeight or the Height to the ActualWidth, or using the SizeChanged event, but this results in heavy flickering while resizing and it's not very reliable. In case of binding the Width to the ActualHeight you can't resize only the Width (by dragging the right border) because the ActualHeight doesn't change. In case of the event it gets tricky when width and height change at the same time, then you'd have to change the size inside the SizeChanged event... and did I mention the flickering?

After a lot of reading and searching I came to the conclusion that the best way to force any control to keep a certain aspect ratio would be to do that inside the Measure and Arrange functions.

I found this solution that creates a Decorator control with overridden Measure and Measure functions, but that would mean to put any control that's supposed to keep it's aspect ratio inside it's own Decorator. I could live with that if I had to, but I wonder if there's a better way to do it.

So, here's my question. Is it possible to create an attached property Ratio and an attached property KeepRatio and somehow override the Measure and Arrange functions of the controls in question in the OnKeepRatioChanged and RatioChanged callbacks of the attached properties?

Upvotes: 1

Views: 1034

Answers (1)

El Barrent
El Barrent

Reputation: 174

If you want to override Arrange/Measure methods then there is no need in attached properties. This wrapper should be fine:

public partial class RatioKeeper : UserControl
{
    public static readonly DependencyProperty VerticalAspectProperty = DependencyProperty.Register(
        "VerticalAspect", typeof(double), typeof(RatioKeeper), new PropertyMetadata(1d));

    public static readonly DependencyProperty HorizontalAspectProperty = DependencyProperty.Register(
        "HorizontalAspect", typeof(double), typeof(RatioKeeper), new PropertyMetadata(1d));

    public double HorizontalAspect
    {
        get { return (double) GetValue(HorizontalAspectProperty); }
        set { SetValue(HorizontalAspectProperty, value); }
    }

    public double VerticalAspect
    {
        get { return (double) GetValue(VerticalAspectProperty); }
        set { SetValue(VerticalAspectProperty, value); }
    }
    public RatioKeeper()
    {
        InitializeComponent();
    }

    //arrangeBounds provides size of a host.
    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        //Calculation of a content size that wont exceed host's size and will be of the desired ratio at the same time
        var horizontalPart = arrangeBounds.Width / HorizontalAspect;
        var verticalPart = arrangeBounds.Height / VerticalAspect;
        var minPart = Math.Min(horizontalPart, verticalPart);
        var size = new Size(minPart * HorizontalAspect, minPart * VerticalAspect);
        //apply size to wrapped content
        base.ArrangeOverride(size);
        //return size to host
        return size;
    }
}

Upvotes: 1

Related Questions