Primary APIs:
Layoutable.Measure(Size)Layoutable.Arrange(Rect)Layoutable.MeasureOverride(Size)Layoutable.ArrangeOverride(Size)Layoutable.DesiredSizeLayoutable.IsMeasureValidLayoutable.IsArrangeValidLayoutable.InvalidateMeasure()Layoutable.InvalidateArrange()Layoutable.AffectsMeasure<T>(...)Layoutable.AffectsArrange<T>(...)Panel.ChildrenPanel.AffectsParentMeasure<TPanel>(...)Panel.AffectsParentArrange<TPanel>(...)Reference source files:
src/Avalonia.Base/Layout/Layoutable.cssrc/Avalonia.Controls/Panel.cssrc/Avalonia.Base/Layout/LayoutManager.csMeasure computes requested size (DesiredSize) under parent constraints.
Pass model:
child.Measure(availableSize).MeasureOverride(availableSize) if invalid or size changed.DesiredSize.DesiredSize to compute its own size.Rules for authoring MeasureOverride:
availableSize as an upper bound, not a target size.Important behavior:
Width/Height/Min*/Max*/Margin/alignment properties affect measure by default on Layoutable.Arrange finalizes geometry and sets bounds.
Pass model:
child.Arrange(rect).ArrangeOverride(finalSize) if invalid or rect changed.Bounds are updated.Rules for authoring ArrangeOverride:
finalSize as actual available arrangement space.For a single-child layout behavior, derive from Decorator and control child sizing/placement.
using Avalonia;
using Avalonia.Controls;
public class AspectRatioDecorator : Decorator
{
public static readonly StyledProperty<double> AspectRatioProperty =
AvaloniaProperty.Register<AspectRatioDecorator, double>(
nameof(AspectRatio), 16d / 9d);
static AspectRatioDecorator()
{
AffectsMeasure<AspectRatioDecorator>(AspectRatioProperty);
AffectsArrange<AspectRatioDecorator>(AspectRatioProperty);
}
public double AspectRatio
{
get => GetValue(AspectRatioProperty);
set => SetValue(AspectRatioProperty, value);
}
protected override Size MeasureOverride(Size availableSize)
{
if (Child is null)
return default;
var ratio = AspectRatio <= 0 ? 1 : AspectRatio;
var width = availableSize.Width;
var height = double.IsInfinity(width) ? availableSize.Height : width / ratio;
if (double.IsInfinity(height))
height = 0;
var constrained = new Size(
double.IsInfinity(width) ? 0 : width,
height);
Child.Measure(constrained);
return Child.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
if (Child is null)
return finalSize;
var ratio = AspectRatio <= 0 ? 1 : AspectRatio;
var width = finalSize.Width;
var height = width / ratio;
if (height > finalSize.Height && finalSize.Height > 0)
{
height = finalSize.Height;
width = height * ratio;
}
var x = (finalSize.Width - width) * 0.5;
var y = (finalSize.Height - height) * 0.5;
Child.Arrange(new Rect(x, y, width, height));
return finalSize;
}
}
For multi-child placement, derive from Panel.
using Avalonia;
using Avalonia.Controls;
public class UniformRowPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
if (Children.Count == 0)
return default;
var childWidth = availableSize.Width / Children.Count;
var maxHeight = 0d;
foreach (var child in Children)
{
child.Measure(new Size(childWidth, availableSize.Height));
maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
}
return new Size(availableSize.Width, maxHeight);
}
protected override Size ArrangeOverride(Size finalSize)
{
if (Children.Count == 0)
return finalSize;
var childWidth = finalSize.Width / Children.Count;
for (var i = 0; i < Children.Count; i++)
{
Children[i].Arrange(new Rect(i * childWidth, 0, childWidth, finalSize.Height));
}
return finalSize;
}
}
If child attached properties affect panel geometry, use:
Panel.AffectsParentMeasure<TPanel>(...)Panel.AffectsParentArrange<TPanel>(...)InvalidateMeasure() only when desired size can change.InvalidateArrange() when only position or final placement changes.UpdateLayout() in normal UI flow.MeasureOverride/ArrangeOverride.Default mode:
XAML-first usage example:
<StackPanel xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MyApp.Controls"
Spacing="12">
<local:AspectRatioDecorator AspectRatio="1.7778" Height="180">
<Border Background="CornflowerBlue" />
</local:AspectRatioDecorator>
<local:UniformRowPanel>
<Button Content="One" />
<Button Content="Two" />
<Button Content="Three" />
</local:UniformRowPanel>
</StackPanel>
Code-only alternative (on request):
var row = new UniformRowPanel();
row.Children.Add(new Button { Content = "One" });
row.Children.Add(new Button { Content = "Two" });
row.Children.Add(new Button { Content = "Three" });
var aspect = new AspectRatioDecorator
{
AspectRatio = 16d / 9d,
Height = 180,
Child = new Border { Background = Brushes.CornflowerBlue }
};
InvalidateMeasure()/InvalidateArrange() called every pass.MeasureOverride returns default even when content exists.Arrange rectangle math.finalSize.