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:
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.
October 15, 2013 at 8:54 am |
Thank you!
November 5, 2013 at 12:36 pm |
thank you
February 10, 2014 at 2:16 pm |
Thanks! Just what I was looking for
May 6, 2014 at 9:49 am |
Thank you, works perfectly!
October 16, 2014 at 1:08 pm |
many thanks to you 🙂
February 18, 2015 at 9:20 pm |
Many thanks. I’m working on a video app for a client and my aspect ratio was off. Not anymore. Thanks again.
June 6, 2015 at 10:40 pm |
Perfect! Thanks
November 10, 2015 at 8:34 am |
Thank you !
December 28, 2015 at 4:39 pm |
Thanks. That works perfectly. Saved me a ton of messing around.
March 4, 2016 at 12:03 am |
Thanks muchly!
March 18, 2016 at 5:23 pm |
Thanks!
August 28, 2016 at 9:03 pm |
Works very nicely. Thanks!
November 20, 2016 at 5:37 pm |
great stuff! Thanks a lot
March 16, 2017 at 11:54 am |
very nice, thank you
April 5, 2017 at 11:35 am |
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?
April 5, 2017 at 5:54 pm |
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”
May 6, 2017 at 1:58 pm |
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!
February 6, 2019 at 8:40 pm |
Thank you, this is great. Putting this into use. It would be outstanding if you could officially license this code (under something like Creative Commons, MIT, Apache 2.0); helps with business adoption.
February 6, 2019 at 8:53 pm |
You can do with the code what you want. If you need something more formal, I will hereby license it under the MIT license as well. Thanks for letting me know that it was useful for you. Good luck in your projects!
September 4, 2019 at 10:59 am |
Good job, thank you!
March 25, 2020 at 10:47 am |
Unfortunately, I get a “Error BC30284 function ‘MeasureOverride’ cannot be declared ‘Overrides’ because it does not override a function in a base class.” (the same for ArrangeOverride)….no idea why!?
July 30, 2020 at 1:20 pm |
Thanks, it works for me.
May 6, 2021 at 6:33 pm |
A massive thank you for this one. I had been banging my head for hours, due to a canvas in a grid element not resizing when I have a full window height panel that can expand only horizontally.
January 21, 2022 at 12:02 pm |
Thank you!
July 28, 2022 at 8:34 pm |
Awesome solution! I tried about 10 different approaches that caused screen flickers or just didn’t work. We have soo much ugly code around this problem.
This looks like the “right” way!
March 14, 2023 at 3:47 pm |
Great solution, it works! Thank you very much! It takes for me sereval days “fighting” incorrect resizing and aspect ratio for one control of third-party vendor. But now it works like a charm.