简体   繁体   中英

Avalonia UI Scaling Issues

I am developing a cross platform desktop MVVM application using Avalonia and my problem is of rather simple nature:

I'd like to adapt the whole window to the display resolution of the target device. For example the UI should always be scaled to cover like 25 to 50% of the display but shouldn't be larger. Scaling should include all font sizes, width and height properties, etc. To visualize it here is how it looks on my 4K desktop, which is how it's supposed to look like:

桌面

On my linux laptop that has a way lower resolution it looks like this:

在此处输入图像描述

What I'd like to do is to scale everything up or down according to the display resolution so it doesn't look like trash and so that the font is always clearly readable (so font needs to scale as well). (There's probably also a WinAPI call for UI scaling, but the solution should work on linux and OSX too)

How could this be implemented using Avalonia? Or is there a preferred way of achieving this?

Note that the width to height ratio of the application should remain constant and the application should never cover the whole screen.

My ideas so far:

  • I could probably create bindings for every size parameter and recalculate the optimal size on application startup using a scaling factor of some sort, but this would require a bunch of new code and would be a pain to implement with the font sizes.

  • Maybe create bindings and a bunch of style presets like CSS media queries? That would be a bunch of work too though.

Is there a better way of achieving the desired effect?

I could probably create bindings for every size parameter and recalculate the optimal size on application startup using a scaling factor of some sort, but this would require a bunch of new code and would be a pain to implement with the font sizes.

This ended up being what I did. I had to create a whole separate namespace to hold all the classes needed to do this. So it's too much code to put in this answer but I put my full code on GitHub if anyone is interested.

Basically it all came down to iterating over the Avalonia LogicalTree like this:

/// <summary>
/// Recursively registers all <see cref="ILogical"/>s in <paramref name="logicals"/> to the <paramref name="bindingContext"/>.
/// </summary>
/// <param name="logicals">A <see cref="Queue{T}"/> containing the root controls that will be recursively registered.</param>
/// <param name="bindingContext">The <see cref="BindingContext"/> the <see cref="ScalableObject"/>s will be registered to.</param>
public static void RegisterControls(Queue<IEnumerable<ILogical>> logicals, BindingContext bindingContext)
{
    while (logicals.Count > 0)
    {
        IEnumerable<ILogical> children = logicals.Dequeue();
        foreach (ILogical child in children)
        {
            logicals.Enqueue(child.GetLogicalChildren());
            if (child is AvaloniaObject avaloniaObject)
            {
                ScalableObject scalableObject = new ScalableObject(avaloniaObject);
                bindingContext.Add(scalableObject);
            }
        }
    }
}

where the constructor of my ScalableObject looks like this:

/// <summary>
/// Initializes a new <see cref="ScalableObject"/> from the provided <paramref name="avaloniaObject"/>.
/// </summary>
/// <param name="avaloniaObject">The <see cref="AvaloniaObject"/> to be mapped to this new instance of <see cref="ScalableObject"/>.</param>
public ScalableObject(AvaloniaObject avaloniaObject)
{
    if (avaloniaObject is TextBlock textBlock)
    {
        Register(avaloniaObject, TextBlock.FontSizeProperty, textBlock.FontSize);
    }
    if (avaloniaObject is TemplatedControl templatedControl)
    {
        Register(avaloniaObject, TemplatedControl.FontSizeProperty, templatedControl.FontSize);
    }
    if (avaloniaObject is Border border)
    {
        Register(avaloniaObject, Border.CornerRadiusProperty, border.CornerRadius);
    }
    // .... This goes on like this for a while
}

I can then apply a new UI scaling factor iterating over all created bindings like this:

/// <summary>
/// Applies the specified <paramref name="scalingFactor"/> to this <see cref="ScalableObject"/> and all of it's children.
/// </summary>
/// <param name="scalingFactor">The scaling factor to be applied to all <see cref="IScalable"/>s of this <see cref="ScalableObject"/>.</param>
public void ApplyScaling(double scalingFactor)
{
    PreScalingAction?.Invoke();
    foreach (IScalable binding in Bindings.Values)
    {
        binding.ApplyScaling(scalingFactor);
    }
    PostScalingAction?.Invoke();
}

Again it's really too much code to put in this answer but hopefully it gives you an idea as to how my solution was implemented.

This is the result:

在此处输入图像描述

can be scaled to this

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM