Resizing a WPF control while maintaining a specific aspect ratio

When you are developing a Windows Presentation Foundation (WPF) application you have a lot of options to layout your controls automatically. Builtin panels like the Grid, the DockPanel or the StackPanel allows for flexible layouts. There is also the Viewbox decorator control which scales it child while (optionally) preserving its aspect ratio.

The disadvantage of the Viewbox is however that it scales its content instead of resizing it. So if you put a Button inside a Viewbox you will notice that its text and the control itself will appear “zoomed in” which makes it inapt for certain applications.

For this reason I wrote my own decorator which overwrites the Measure and Arrange methods to resize its child to a specific AspectRatio that you can give it as a dependency property. After understanding how Measure and Arrange work together it was actually quite easy to develop and does not require a lot of code.

Here is a visual comparison between the Viewbox decorator and my own AspectRatioLayoutDecorator:

Comparison of ViewBox and AspectRatioLayoutDecorator

As you can see the ViewBox scales the button and makes it look really ugly, while our custom control will resize it content nicely.

The usage in WPF is quite easily. Just wrap the content you want to resize in the AspectRatioLayoutDecorator:

<my:AspectRatioLayoutDecorator AspectRatio="1.25">
   <Button>Test</Button>
</my:AspectRatioLayoutDecorator>

Of course instead of setting a fixed AspectRatio you can animate and data bind it like any other dependency property.

Here is the source code of the control:

public class AspectRatioLayoutDecorator : Decorator
{
   public static readonly DependencyProperty AspectRatioProperty =
      DependencyProperty.Register(
         "AspectRatio",
         typeof (double),
         typeof (AspectRatioLayoutDecorator),
         new FrameworkPropertyMetadata
            (
               1d,
               FrameworkPropertyMetadataOptions.AffectsMeasure
            ),
         ValidateAspectRatio);

   private static bool ValidateAspectRatio(object value)
   {
      if (!(value is double))
      {
         return false;
      }

      var aspectRatio = (double) value;
      return aspectRatio > 0
               && !double.IsInfinity(aspectRatio)
               && !double.IsNaN(aspectRatio);
   }

   public double AspectRatio
   {
      get { return (double) GetValue(AspectRatioProperty); }
      set { SetValue(AspectRatioProperty, value); }
   }

   protected override Size MeasureOverride(Size constraint)
   {
      if (Child != null)
      {
         constraint = SizeToRatio(constraint, false);
         Child.Measure(constraint);

         if(double.IsInfinity(constraint.Width)
            || double.IsInfinity(constraint.Height))
         {
            return SizeToRatio(Child.DesiredSize, true);
         }

         return constraint;
      }

      // we don't have a child, so we don't need any space
      return new Size(0, 0);
   }

   public Size SizeToRatio(Size size, bool expand)
   {
      double ratio = AspectRatio;

      double height = size.Width/ratio;
      double width = size.Height*ratio;

      if (expand)
      {
         width = Math.Max(width, size.Width);
         height = Math.Max(height, size.Height);
      }
      else
      {
         width = Math.Min(width, size.Width);
         height = Math.Min(height, size.Height);
      }

      return new Size(width, height);
   }

   protected override Size ArrangeOverride(Size arrangeSize)
   {
      if (Child != null)
      {
         var newSize = SizeToRatio(arrangeSize, false);

         double widthDelta = arrangeSize.Width - newSize.Width;
         double heightDelta = arrangeSize.Height - newSize.Height;

         double top = 0;
         double left = 0;

         if (!double.IsNaN(widthDelta)
            && !double.IsInfinity(widthDelta))
         {
            left = widthDelta/2;
         }

         if (!double.IsNaN(heightDelta)
            && !double.IsInfinity(heightDelta))
         {
            top = heightDelta/2;
         }

         var finalRect = new Rect(new Point(left, top), newSize);
         Child.Arrange(finalRect);
      }

      return arrangeSize;
   }
}

The control not only works in a Grid, but also quite nicely in a StackPanel or any other form of layout.

I hope it’s useful to someone else. If you think it’s useful for you and you want to use it in your own project it would be nice if you leave a small “thank you” in the comments, so that I know it actually helped someone.

Advertisements

Tags: , , , ,

17 Responses to “Resizing a WPF control while maintaining a specific aspect ratio”

  1. vppuzakov Says:

    Thank you!

  2. makamp Says:

    thank you

  3. Mark Pitt Says:

    Thanks! Just what I was looking for

  4. Kef Says:

    Thank you, works perfectly!

  5. Amr Gamal Says:

    many thanks to you 🙂

  6. Alexander L Dagana Says:

    Many thanks. I’m working on a video app for a client and my aspect ratio was off. Not anymore. Thanks again.

  7. Yvan Rodrigues Says:

    Perfect! Thanks

  8. Shruti Says:

    Thank you !

  9. johnlammers Says:

    Thanks. That works perfectly. Saved me a ton of messing around.

  10. Soleil Says:

    Thanks muchly!

  11. Peter Says:

    Thanks!

  12. Donatello Says:

    Works very nicely. Thanks!

  13. Tim Says:

    great stuff! Thanks a lot

  14. Romain Coury Says:

    very nice, thank you

  15. Gareth Says:

    Thanks. Just what i’m looking for! I’ve just tried converting to VB but getting an error in the code registering the DependencyProperty in the callback part, Argument not specified for ValidateAspectRatio. Does this need altering massively to get it to work?

  16. akzent Says:

    Since you want to pass the function as a delegate instead of calling it, I think you need to use the AddressOf operator in VB.NET. So instead of “ValidateAspectRatio” you should write it as “AddressOf ValidateAspectRatio”

  17. Maciej Says:

    Hi, could you provide some more informations about how to use your code? I tried pasting it in many places also creating a new UC for it and nothing worked. I get this error: The tag ‘AspectRatioLayoutDecorator’ does not exist in XML namespace ‘http://schemas.microsoft.com/winfx/2006/xaml/presentation’. Thanks in advance!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: