Avalonia开发小结

停止更新,整理中,参考另一个帖子。

Avalonia开发笔记(2023/12/7更新) - wzwyc - 博客园
https://www.cnblogs.com/wzwyc/p/17578218.html

 

官网:

https://avaloniaui.net/

源码:

https://github.com/AvaloniaUI/Avalonia

目前最新版本:11.0.5

 

 

 

 

 

系统测试情况

目前试了一下,能够正常运行的系统,除了Windows系统外,流行的Ubuntu,Centos,Redhat这些系统应该都没啥问题。

目前因为国产化要求,经常需要运行在银河麒麟系统下,目前测试了没有啥问题。

在Linux系统下运行,偶尔会因为字体的原因运行失败,所以保险起见,建议AvaloniaUI应用都加上自字义字体的代码。

 

 

 

常见问题解决

1、Ubuntu系统下TextBox中文显示乱码

貌似Avalonia必须指定一下当前窗体的字体,不然中文就是会显示乱码,之前是直接设置成“Microsoft YaHei”,会导致Ubuntu系统下找不到相应的字体:

private static string GetPlatformFontFamily()
{
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
    {
        return "Microsoft YaHei";
    }
    else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
    {
        return "Menlo"; //换成OSX下的中文字体
    }
    else
    {
        return "Noto Sans CJK SC";
    }
}

 

2、ToolTip的显示

在要显示ToolTip的控件上加上附加属性:

ToolTip.Tip="Tip内容"

 

3、Ubuntu设置.net core程序开机自启动

在/etc/systemd/system/ 目录下创建.service文件。
UploadServer.service文件:

[Unit]
Description=UploadServer
After=network.target
 
[Service]
WorkingDirectory=/www/wwwroot/db.cnsos.net/UploadServer/UploadServer/bin/Debug/netcoreapp3.1
ExecStart=/usr/bin/dotnet /www/wwwroot/db.cnsos.net/UploadServer/UploadServer/bin/Debug/netcoreapp3.1/UploadServer.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
SyslogIdentifier=UploadServer

[Install]
WantedBy=multi-user.target

3、UploadServer.service无法正常启动

 发现程序在Ubuntu的终端下用dotnet run可以正常启动和运行,但是设置为service以后,就是无法正常使用。

通过下面的命令看了一下:

sudo journalctl -f -u FileServer.service

发现服务在不停地启动和停止。

看了一下代码,Main函数的未尾使用了Console.ReadLine();

换成:

while (true)
{
    Thread.Sleep(1000);
}

应该是service会自动跳过Console.ReadLine(),然后程序就结束了,然后服务本身设置了自动重启,所以不停地停止和重启。

3、Ubuntu系统下路径不正常的问题

 程序在Windows系统下测试良好,但是在Ubuntu系统上却无法正常运行,看了一下,是文件路径的问题。

因为客户端在Windows下运行的,客户端上传的路径里的“\”需要换成“/”。写了一个路径转换函数:

public static string ChangePath(string path)
{
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
    {
        path = path.Replace("\\", "/");
        if (path.StartsWith("/"))
            path = path.Substring(1);

        return path;
    }

    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
    {
        path = path.Replace("/", "\\");
        if (path.StartsWith("\\"))
            path = path.Substring(1);

        return path;
    }

    return path;
}

另外,在使用Path.Combine(path1,path2)进行路径组合的时候,path2不能以"\"或“/”开头,不然的话,路径会组合失败。

3、“Default font family name can't be null or empty.”错误问题

部分Linux操作系统下,能够正常编译,但是无法启动应用,会报“Default font family name can't be null or empty.”的错误。应该是跟字体有关系。网上找了一下资料。可以参照以下网址的方法来尝试解决。

https://www.cnblogs.com/joyandjoys/p/14346935.html

 下面是我现在用的代码:

using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Media;
using Avalonia.Media.Fonts;
using Avalonia.Platform;
using Avalonia.Skia;
using SkiaSharp;

namespace CodeKeeper
{
    public class CustomFontManagerImpl : IFontManagerImpl
    {
        private readonly Typeface[] _customTypefaces;
        private readonly string _defaultFamilyName;

        //Load font resources in the project, you can load multiple font resources
        private readonly Typeface _defaultTypeface =
            new Typeface("resm:CodeKeeper.Assets.Fonts.msyh#微软雅黑");

        public CustomFontManagerImpl()
        {
            _customTypefaces = new[] { _defaultTypeface };
            _defaultFamilyName = _defaultTypeface.FontFamily.FamilyNames.PrimaryFamilyName;
        }

        public string GetDefaultFontFamilyName()
        {
            return _defaultFamilyName;
        }

        public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false)
        {
            return _customTypefaces.Select(x => x.FontFamily.Name);
        }

        private readonly string[] _bcp47 =
        {
            CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName
        };

        public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
            CultureInfo culture, out Typeface typeface)
        {
            foreach (var customTypeface in _customTypefaces)
            {
                if (customTypeface.GlyphTypeface.GetGlyph((uint)codepoint) == 0)
                {
                    continue;
                }

                typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight);

                return true;
            }

            var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
                SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);

            typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);

            return true;
        }

        public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
        {
            SKTypeface skTypeface;

            if (typeface == null || typeface.FontFamily == null)
            {
                skTypeface = SKTypeface.FromFamilyName(_defaultTypeface.FontFamily.Name);
            }
            else
            {
                switch (typeface.FontFamily.Name)
                {
                    case FontFamily.DefaultFontFamilyName:
                    case "微软雅黑": //font family name
                        skTypeface = SKTypeface.FromFamilyName(_defaultTypeface.FontFamily.Name);
                        break;
                    default:
                        skTypeface = SKTypeface.FromFamilyName(typeface.FontFamily.Name,
                            (SKFontStyleWeight)typeface.Weight, SKFontStyleWidth.Normal,
                            (SKFontStyleSlant)typeface.Style);
                        break;
                }
            }

            return new GlyphTypefaceImpl(skTypeface);
        }
    }
}

 

4、第三方的MessageBox.Avalonia控件有点问题,会导致应用崩溃退出。

初步判断是因为窗口的图标引起的,而且这个图标不设置也不行。

最新版本已经没有问题。

var msBoxStandardWindow = MessageBox.Avalonia.MessageBoxManager.GetMessageBoxStandardWindow(new MessageBoxStandardParams
{
    ButtonDefinitions = ButtonEnum.Ok,
    ContentTitle = "提示",
    ContentMessage = "请输入网址",
    Icon = Icon.Info,
    Style = Style.UbuntuLinux,
    WindowStartupLocation = WindowStartupLocation.CenterOwner,
    FontFamily = "Microsoft YaHei",
});
await msBoxStandardWindow.Show(App.GetActiveWindow());

不过这个对话框控件容易出现一些问题,类似中文显示乱码等等问题,其实自己写一个简单的对话框窗体也是不错的选择。

 目前对话框,一般我都是自己写。

5、Ubuntu系统下,从IDE上Debug可以正常运行,但是通过系统的桌面图标无法打开。

试了一下,用.desktop里的路径直接在终端里面运行,也是无法正常运行的。在终端上面输出了一些错误信息。看了一下,配置文件的路径,用的是相对路径。

把它改成下面的形式,应用就能正常打开了。

Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Formats/AppConfig")

 

6、在部分机子上,打开OpenFileDialog时,会卡UI,软件无响应。

在Main方法上加上 [STAThread] 的标签。如下所示:

    internal class Program
    {
        // Initialization code. Don't use any Avalonia, third-party APIs or any
        // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
        // yet and stuff might break.
        [STAThread]
        public static void Main(string[] args) => BuildAvaloniaApp()
            .StartWithClassicDesktopLifetime(args);

        // Avalonia configuration, don't remove; also used by visual designer.
        public static AppBuilder BuildAvaloniaApp()
            => AppBuilder.Configure<App>()
                .UsePlatformDetect()
                .LogToTrace();
    }

 

7、托盘图标的功能

示例代码:

public override void OnFrameworkInitializationCompleted()
{
    if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
    {
        desktop.MainWindow = new MainWindow
        {
            DataContext = new MainWindowViewModel(),
        };

        var notifyIcon = new TrayIcon();
        notifyIcon.Menu ??= new NativeMenu();
        notifyIcon.ToolTipText = "ToDoList";
        var assets = AvaloniaLocator.Current.GetService<IAssetLoader>();
        notifyIcon.Icon = new WindowIcon(assets.Open(new Uri("avares://TodoList/App.ico")));
        var exit = new NativeMenuItem() { Header = "退出" };
        exit.Click += (sender, args) => Environment.Exit(0);
        notifyIcon.Menu.Add(exit);
        notifyIcon.Clicked += (sender, args) => { desktop.MainWindow.WindowState = WindowState.Normal; };
    }

    base.OnFrameworkInitializationCompleted();
}

 

7、ValueConverter功能

实现类:

using Avalonia.Data.Converters;
using Jaya.Shared;
using Jaya.Ui.Services;
using System;
using System.Globalization;

namespace Jaya.Ui.Converters
{
    public class BooleanToTreeNodeVisibilityConverter : IValueConverter
    {
        readonly SharedService _shared;

        public BooleanToTreeNodeVisibilityConverter()
        {
            _shared = ServiceLocator.Instance.GetService<SharedService>();
        }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (_shared.ApplicationConfiguration.IsHiddenItemVisible)
                return true;

            return !(bool)value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

定义和使用:

<UserControl.Resources>
    <c:BooleanToTreeNodeVisibilityConverter x:Key="TreeNodeVisibilityConverter" />
    <j:BitmapValueConverter x:Key="BitmapValueConverter" />
</UserControl.Resources>

<UserControl.Styles>
    <Style Selector="TreeViewItem">
        <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=OneWayToSource}" />
        <Setter Property="IsVisible" Value="{Binding FileSystemObject.IsHidden, Mode=OneWay, Converter={StaticResource TreeNodeVisibilityConverter}, FallbackValue=True}" />
    </Style>
</UserControl.Styles>

 

posted @ 2021-05-13 16:23  wzwyc  阅读(8171)  评论(8编辑  收藏  举报