You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm looking for an algorithm to auto adapt the dialog to its contents while preserving a certain aspect ratio that looks OK on-screen.
This is for Avalonia, but the question could fit any other technology, because the problem remains the same: Choosing a size depending on the content.
I have this so far:
using System;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.ReactiveUI;
using Avalonia.Threading;
using Avalonia.Xaml.Interactivity;
namespace AvaloniaApplication5
{
/// <summary>
/// Automatic window resizing behavior with harmonic distribution
/// </summary>
public class AutoResizeBehavior : Behavior<Window>
{
public static readonly StyledProperty<double> MinWidthProperty =
AvaloniaProperty.Register<AutoResizeBehavior, double>(nameof(MinWidth), 320);
public static readonly StyledProperty<double> MinHeightProperty =
AvaloniaProperty.Register<AutoResizeBehavior, double>(nameof(MinHeight), 240);
public static readonly StyledProperty<double> MaxWidthProperty =
AvaloniaProperty.Register<AutoResizeBehavior, double>(nameof(MaxWidth), double.PositiveInfinity);
public static readonly StyledProperty<double> MaxHeightProperty =
AvaloniaProperty.Register<AutoResizeBehavior, double>(nameof(MaxHeight), double.PositiveInfinity);
public static readonly StyledProperty<Thickness> MarginProperty =
AvaloniaProperty.Register<AutoResizeBehavior, Thickness>(nameof(Margin), new Thickness(20));
public static readonly StyledProperty<bool> UseGoldenRatioProperty =
AvaloniaProperty.Register<AutoResizeBehavior, bool>(nameof(UseGoldenRatio), true);
public static readonly StyledProperty<bool> CenterOnScreenProperty =
AvaloniaProperty.Register<AutoResizeBehavior, bool>(nameof(CenterOnScreen), true);
public double MinWidth
{
get => GetValue(MinWidthProperty);
set => SetValue(MinWidthProperty, value);
}
public double MinHeight
{
get => GetValue(MinHeightProperty);
set => SetValue(MinHeightProperty, value);
}
public double MaxWidth
{
get => GetValue(MaxWidthProperty);
set => SetValue(MaxWidthProperty, value);
}
public double MaxHeight
{
get => GetValue(MaxHeightProperty);
set => SetValue(MaxHeightProperty, value);
}
public Thickness Margin
{
get => GetValue(MarginProperty);
set => SetValue(MarginProperty, value);
}
public bool UseGoldenRatio
{
get => GetValue(UseGoldenRatioProperty);
set => SetValue(UseGoldenRatioProperty, value);
}
public bool CenterOnScreen
{
get => GetValue(CenterOnScreenProperty);
set => SetValue(CenterOnScreenProperty, value);
}
private IDisposable? _layoutSubscription;
private Size _lastMeasuredSize;
private bool _isResizing;
protected override void OnAttached()
{
base.OnAttached();
if (AssociatedObject != null)
{
AssociatedObject.Opened += OnWindowOpened;
AssociatedObject.Closing += OnWindowClosing;
}
}
protected override void OnDetaching()
{
if (AssociatedObject != null)
{
AssociatedObject.Opened -= OnWindowOpened;
AssociatedObject.Closing -= OnWindowClosing;
}
_layoutSubscription?.Dispose();
base.OnDetaching();
}
private void OnWindowOpened(object? sender, EventArgs e)
{
StartMonitoring();
}
private void OnWindowClosing(object? sender, WindowClosingEventArgs e)
{
_layoutSubscription?.Dispose();
}
private void StartMonitoring()
{
if (AssociatedObject?.Content is not Control content) return;
_layoutSubscription = Observable
.FromEventPattern<EventHandler, EventArgs>(
h => content.LayoutUpdated += h,
h => content.LayoutUpdated -= h)
.Where(_ => !_isResizing)
.Throttle(TimeSpan.FromMilliseconds(100))
.ObserveOn(AvaloniaScheduler.Instance)
.Subscribe(_ => CheckAndResize());
Dispatcher.UIThread.Post(CheckAndResize, DispatcherPriority.Loaded);
}
private void CheckAndResize()
{
if (AssociatedObject?.Content is not Control content || _isResizing) return;
var desiredSize = MeasureContent(content);
if (Math.Abs(desiredSize.Width - _lastMeasuredSize.Width) < 1 &&
Math.Abs(desiredSize.Height - _lastMeasuredSize.Height) < 1)
return;
_lastMeasuredSize = desiredSize;
var optimalSize = CalculateOptimalSize(desiredSize);
ResizeWindow(optimalSize);
}
private Size MeasureContent(Control content)
{
// For text content, measure with a reasonable width constraint to avoid extremely wide windows
var maxReasonableWidth = Math.Min(800, GetScreenBounds().Width * 0.6);
// First try with width constraint
content.Measure(new Size(maxReasonableWidth, double.PositiveInfinity));
var constrainedSize = content.DesiredSize;
// If the content doesn't need the full width, use unconstrained measurement
content.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var unconstrainedSize = content.DesiredSize;
// Use constrained if unconstrained would be too wide, otherwise use the smaller option
var desiredSize = unconstrainedSize.Width > maxReasonableWidth ? constrainedSize : unconstrainedSize;
var chromeSize = GetWindowChromeSize();
var totalMargin = Margin.Left + Margin.Right + Margin.Top + Margin.Bottom;
return new Size(
desiredSize.Width + totalMargin + chromeSize.Width,
desiredSize.Height + totalMargin + chromeSize.Height
);
}
private Size GetWindowChromeSize()
{
if (AssociatedObject == null) return new Size(0, 0);
var titleBarHeight = AssociatedObject.ExtendClientAreaToDecorationsHint ? 0 : 30;
var borderWidth = AssociatedObject.ExtendClientAreaToDecorationsHint ? 0 : 8;
return new Size(borderWidth, titleBarHeight + borderWidth);
}
private Size CalculateOptimalSize(Size desiredSize)
{
var screenBounds = GetScreenBounds();
var maxAvailableWidth = screenBounds.Width * 0.9;
var maxAvailableHeight = screenBounds.Height * 0.9;
var width = Math.Max(MinWidth, Math.Min(desiredSize.Width, Math.Min(MaxWidth, maxAvailableWidth)));
var height = Math.Max(MinHeight, Math.Min(desiredSize.Height, Math.Min(MaxHeight, maxAvailableHeight)));
if (UseGoldenRatio)
{
const double goldenRatio = 1.618;
var currentRatio = width / height;
// More aggressive golden ratio application
if (currentRatio > goldenRatio)
{
// Window is too wide - adjust height to match golden ratio
var idealHeight = width / goldenRatio;
if (idealHeight >= MinHeight && idealHeight <= maxAvailableHeight && idealHeight <= MaxHeight)
{
height = idealHeight;
}
else if (idealHeight > maxAvailableHeight || idealHeight > MaxHeight)
{
// If we can't increase height, reduce width instead
var maxAllowedHeight = Math.Min(maxAvailableHeight, MaxHeight);
width = maxAllowedHeight * goldenRatio;
height = maxAllowedHeight;
}
}
else if (currentRatio < goldenRatio * 0.9)
{
// Window is too tall - adjust width to match golden ratio
var idealWidth = height * goldenRatio;
if (idealWidth >= MinWidth && idealWidth <= maxAvailableWidth && idealWidth <= MaxWidth)
{
width = idealWidth;
}
else if (idealWidth > maxAvailableWidth || idealWidth > MaxWidth)
{
// If we can't increase width, reduce height instead
var maxAllowedWidth = Math.Min(maxAvailableWidth, MaxWidth);
height = maxAllowedWidth / goldenRatio;
width = maxAllowedWidth;
}
}
// Ensure we don't go below minimums
width = Math.Max(width, MinWidth);
height = Math.Max(height, MinHeight);
}
return new Size(width, height);
}
private PixelRect GetScreenBounds()
{
var screen = AssociatedObject?.Screens.ScreenFromWindow(AssociatedObject);
return screen?.WorkingArea ?? new PixelRect(0, 0, 1920, 1080);
}
private void ResizeWindow(Size newSize)
{
if (AssociatedObject == null) return;
_isResizing = true;
try
{
AssociatedObject.Width = newSize.Width;
AssociatedObject.Height = newSize.Height;
if (CenterOnScreen)
{
var screenBounds = GetScreenBounds();
var x = screenBounds.X + (screenBounds.Width - AssociatedObject.Width) / 2;
var y = screenBounds.Y + (screenBounds.Height - AssociatedObject.Height) / 2;
AssociatedObject.Position = new PixelPoint((int)x, (int)y);
}
}
finally
{
_isResizing = false;
}
}
}
}
The problem is that it doesn't really adapt the size to the contents:
Notice the space below the text. So, how to do something good enough? It should adapt the size if the content size changes, too. Imagine a content that is invisible, but becomes visible and the layout changes. The idea is not not clip anything, but preserving the overall aspect ratio of the window if possible.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
I'm looking for an algorithm to auto adapt the dialog to its contents while preserving a certain aspect ratio that looks OK on-screen.
This is for Avalonia, but the question could fit any other technology, because the problem remains the same: Choosing a size depending on the content.
I have this so far:
The problem is that it doesn't really adapt the size to the contents:

Notice the space below the text. So, how to do something good enough? It should adapt the size if the content size changes, too. Imagine a content that is invisible, but becomes visible and the layout changes. The idea is not not clip anything, but preserving the overall aspect ratio of the window if possible.
Beta Was this translation helpful? Give feedback.
All reactions