Avalonia开发小结
停止更新,整理中,参考另一个帖子。
Avalonia开发笔记(2023/12/7更新) - wzwyc - 博客园
https://www.cnblogs.com/wzwyc/p/17578218.html
官网:
源码:
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>