Avalonia是基于.NET的跨平台UI框架,能够支持在Windows、Linux、MacOS等操作系统中运行客户端。在官方的MAUI没有发布最新稳定版,对于客户端程序的跨平台开发仍然是不错的选择,尤其是已经有WPF基础的,能够很快上手。


0.开发环境

开发工具:VisualStudio 2019
处理架构:x86架构(AMD系列)
操作系统:Windows10,统信UOS家庭版


1.安装VS插件

打开VS“扩展>管理扩展”,搜索“Avalonia”,安装“Avalonia for Visual Studio 2019,2017”。

2.创建新应用

插件安装完成时,在创建新项目界面,可以看到Avalonia的模板,有常规模式应用和MVVM模式应用。有些WPF基础的可以选择MVVM模式应用。选择“Avalonia MVVM Application”。

如果VS刚刚更新过NET6,可能会提示错误,不能正常编译。将项目文件中的目标框架从NET6.0改为NET5.0。如果能够正常编译则忽略。

3.代码项目结构

这是典型的MVVM模式结构,主要代码文件及目录用途如下:
Program.cs:系统入口
App.axaml:应用样式及初始化等
ViewLocator:View层与ViewModel层的映射

4.应用示例

4.1应用需求

实现一个简单功能,从文件中读取气象数据,包括观测站点、时间、温度、湿度、降水量、气压、风速等,以列表形式进行展示。
原始数据是从国家气象数据中心下载的公开数据,用于做示例。由于只是简单示例,只考虑了txt文件的读取,没有Excel表格操作,也不涉及到数据库和ORM。
气象数据为txt文件,数据间以空格间隔。

另外还有一份观测站点的数据,与气象数据中的站点编号能够进行对应。

观测站点的原始文件是PDF,导出成Excel后又导出成txt

4.2界面布局

Avaloina的axaml文件与WPF的xaml文件规则一致,切换起来没有什么困难。

DataGrid控件需要单独安装,默认的控件库中是没有的。打开NuGet管理器,搜索“Avalonia.Controls.DataGrid”进行安装

页面布局代码如下:

<Grid><Grid.RowDefinitions><RowDefinition Height="80"></RowDefinition><RowDefinition></RowDefinition></Grid.RowDefinitions><WrapPanel Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox Width="180" Height="30" Margin="0,0,5,0" Text="{Binding ClimateSourcePath,Mode=TwoWay}"></TextBox><Button Height="30" Width="110" Margin="0,0,5,0" HorizontalContentAlignment="Center" Command="{Binding SelectClimateFileCommand}">选择气象文件</Button><Button Height="30" Width="110" HorizontalContentAlignment="Center" Command="{Binding GetClimateCommand}">加载气象数据</Button></WrapPanel><DataGrid Items="{Binding ClimateModels}" Grid.Row="1" AutoGenerateColumns="False"><DataGrid.Columns><DataGridTextColumn Width="*" Header="区站号" Binding="{Binding Station_Id_C}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="站名" Binding="{Binding StationName}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="年"  Binding="{Binding Year}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="月"  Binding="{Binding Mon}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="日" Binding="{Binding Day}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="时次" Binding="{Binding Hour}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="气压" Binding="{Binding PRS}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="海平面气压" Binding="{Binding PRS_Sea}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="最大风速" Binding="{Binding WIN_S_Max}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="温度" Binding="{Binding TEM}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="湿度" Binding="{Binding RHU}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="降水量" Binding="{Binding PRE_1h}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="风力" Binding="{Binding windpower}"></DataGridTextColumn><DataGridTextColumn Width="*" Header="体感温度" Binding="{Binding tigan}"></DataGridTextColumn><DataGridTextColumn Width="2*" Header="地理位置信息" Binding="{Binding GeoLocation}"></DataGridTextColumn></DataGrid.Columns></DataGrid>
</Grid>

4.3数据对象

创建气象数据类

    /// <summary>/// 原始气象数据模型/// </summary>public partial class ClimateModel{/// <summary>/// 区站号/观测平台标识/// </summary>public string Station_Id_C { get; set; }/// <summary>/// 站名/// </summary>public string StationName { get; set; }/// <summary>/// 年/// </summary>public int Year { get; set; }/// <summary>/// 月/// </summary>public int Mon { get; set; }/// <summary>/// 日/// </summary>public int Day { get; set; }/// <summary>/// 时次/// </summary>public int Hour { get; set; }/// <summary>/// 气压(百帕)/// </summary>public double PRS { get; set; }/// <summary>/// 海平面气压(百帕)/// </summary>public double PRS_Sea { get; set; }/// <summary>/// 最大风速(米/秒)/// </summary>public double WIN_S_Max { get; set; }/// <summary>/// 温度(摄氏度)/// </summary>public double TEM { get; set; }/// <summary>/// 相对湿度(%)/// </summary>public double RHU { get; set; }/// <summary>/// 降水量(毫米)/// </summary>public double PRE_1h { get; set; }/// <summary>/// 风力/// </summary>public int windpower { get; set; }/// <summary>/// 体感温度(摄氏度)/// </summary>public double tigan { get; set; }/// <summary>/// 地理位置信息/// </summary>public string GeoLocation { get; set; }}

增加了站点名称和地理位置信息两个字段

创建观测站点类

    /// <summary>/// 原始站点数据模型/// </summary>public partial class StationModel{/// <summary>/// 省份/// </summary>public string Province { get; set; }/// <summary>/// 区站号/观测平台标识/// </summary>public string Station_Id_C { get; set; }/// <summary>/// 站名/// </summary>public string StationName { get; set; }/// <summary>/// 纬度(度分)/// </summary>public int LatitudeDF { get; set; }/// <summary>/// 经度(度分)/// </summary>public int LongitudeDF { get; set; }/// <summary>/// 气压传感器海拔高度(米)/// </summary>public double MonitorHeight { get; set; }/// <summary>/// 观测场海拔高度(米)/// </summary>public double StationHeight { get; set; }/// <summary>/// 地理位置信息/// </summary>public string GeoLocation { get; set; }}

原始数据中的经纬度为度分秒格式,增加一个字段用来表示十进制的经纬度,同时与气象数据中的站点位置对应。

4.4视图绑定

在MainWindowViewModel中创建属性对象和绑定命令

        #region Data//气象数据集合private ObservableCollection<ClimateModel> _climateModels;public ObservableCollection<ClimateModel> ClimateModels{get => _climateModels;set => this.RaiseAndSetIfChanged(ref _climateModels, value);}//站点数据集合private ObservableCollection<StationModel> _stationModels;public ObservableCollection<StationModel> StationModels{get => _stationModels;set => this.RaiseAndSetIfChanged(ref _stationModels, value);}//气象数据文件路径private string _climateSourcePath ;public string ClimateSourcePath{get => _climateSourcePath;set => this.RaiseAndSetIfChanged(ref _climateSourcePath, value);}//站点数据文件路径private string _stationSourcePath;public string StationSourcePath{get => _stationSourcePath;set => this.RaiseAndSetIfChanged(ref _stationSourcePath, value);}#endregion#region Commandpublic ReactiveCommand<Unit,Unit> SelectClimateFileCommand { get; }//选择气象数据文件public ReactiveCommand<Unit,Unit> GetClimateCommand { get; }//获取气象数据public ReactiveCommand<Unit, Unit> SelectStationFileCommand { get; }//选择站点数据文件public ReactiveCommand<Unit,Unit> GetStationCommand { get; }//获取站点数据#endregion

4.5数据处理

在MainWindowViewModel中初始化数据并定义处理数据的函数。

public MainWindowViewModel()
{_climateModels = new ObservableCollection<ClimateModel>();_climateSourcePath = string.Empty;SelectClimateFileCommand = ReactiveCommand.Create(SelectClimateFile);GetClimateCommand = ReactiveCommand.Create(GetClimateData);_stationModels = new ObservableCollection<StationModel>();_stationSourcePath = string.Empty;SelectStationFileCommand = ReactiveCommand.Create(SelectStationFile);GetStationCommand = ReactiveCommand.Create(GetStationData);
}//选择气象数据文件
private void SelectClimateFile()
{var dialog = new OpenFileDialog{Title = "请选择文件"};var result= dialog.ShowAsync(Views.MainWindow.Instance);if (result.Result != null){_climateSourcePath = result.Result[0];}
}
//获取气象数据
private void GetClimateData()
{if (!string.IsNullOrEmpty(_climateSourcePath)){_climateModels.Clear();using (StreamReader reader = new StreamReader(_climateSourcePath)){string line;while ((line=reader.ReadLine())!=null){string[] arrys = line.TrimEnd().Split();ClimateModel item = new ClimateModel();item.Station_Id_C = arrys[0];item.Year = Int32.Parse(arrys[1]);item.Mon = Int32.Parse(arrys[2]);item.Day = Int32.Parse(arrys[3]);item.Hour = Int32.Parse(arrys[4]);item.PRS = Double.Parse(arrys[5]);item.PRS_Sea = Double.Parse(arrys[6]);item.WIN_S_Max = Double.Parse(arrys[7]);item.TEM = Double.Parse(arrys[8]);item.RHU = Double.Parse(arrys[9]);item.PRE_1h = Double.Parse(arrys[10]);item.windpower = Int32.Parse(arrys[11]);item.tigan = Double.Parse(arrys[12]);if (_stationModels.Any(t=>t.Station_Id_C==item.Station_Id_C)){var station = _stationModels.First(t => t.Station_Id_C == item.Station_Id_C);item.StationName = station.StationName;item.GeoLocation = station.GeoLocation;}_climateModels.Add(item);}}}else{var message = MessageBox.Avalonia.MessageBoxManager.GetMessageBoxStandardWindow("提示", "No File Selected");message.Show();}
}
//选择站点数据文件
private void SelectStationFile()
{var dialog = new OpenFileDialog{Title = "请选择文件"};var result = dialog.ShowAsync(Views.MainWindow.Instance);if (result.Result != null){_stationSourcePath = result.Result[0];}
}//获取站点数据
private void GetStationData()
{if (!string.IsNullOrEmpty(_stationSourcePath)){_stationModels.Clear();using (StreamReader reader=new StreamReader(_stationSourcePath,Encoding.UTF8)){string line;while ((line=reader.ReadLine())!=null){string[] arrys = line.TrimEnd().Split();StationModel item = new StationModel();item.Province = arrys[0];item.Station_Id_C = arrys[1];item.StationName = arrys[2];item.LatitudeDF = Int32.Parse(arrys[3]);item.LongitudeDF = Int32.Parse(arrys[4]);item.MonitorHeight = double.Parse(arrys[5]);item.StationHeight = double.Parse(arrys[6]);item.GeoLocation = ConvertGeoLocation(arrys[3], arrys[4]);_stationModels.Add(item);}}}else{var message = MessageBox.Avalonia.MessageBoxManager.GetMessageBoxStandardWindow("提示", "No File Selected");message.Show();}
}#region 经纬度转换
/// <summary>
/// 经纬度转换-度分转换为数字
/// </summary>
/// <param name="latitude">纬度(度分)</param>
/// <param name="longitude">经度(度分)</param>
/// <returns></returns>
private string ConvertGeoLocation(string latitude, string longitude)
{string result = "";string dgreeW = latitude.Substring(0, latitude.Length - 2);string minW = latitude.Substring(latitude.Length - 2, 2);double digtalW = double.Parse(dgreeW) + double.Parse(minW) / 60;string dgreeJ = longitude.Substring(0, longitude.Length - 2);string minJ = longitude.Substring(longitude.Length - 2, 2);double digtalJ = double.Parse(dgreeJ) + double.Parse(minJ) / 60;result = string.Format("{0},{1}", digtalW, digtalJ);return result;
}
#endregion

1.Avalonia默认是不包含的MessageBox的,需要单独安装,可以从Nuget中安装第三方库MessageBox.Avalonia
2.Avalonia自带了打开文件选择对话框OpenFileDialog和保存文件对话框SaveFileDialog等,但可能是使用Task任务调用的原因,与主UI线程不在一个线程内,选择文件后绑定文件路径的控件并没有更新,数据是确实已经改变了。
3.使用Avalonia自带的对话框时需要指定父窗体,如果参照App.axaml.cs中的方法通过应用生命周期ApplicationLifeTime获取当前窗体的示例,在Windows下可以正常弹出对话框,但在Linux环境下程序直接卡死。

4.6成果示例


坑坑不息,多学有益

Avalonia学习实践(一)--示例相关推荐

  1. Avalonia学习实践(二)--跨平台支持及发布

    Avalonia主打跨平台,号称一套代码支持Windows, macOS, Linux, iOS, Android操作系统,其基础是基于.NET Standard 2.0的一系列库,也就是只要平台能支 ...

  2. 【赠书】掌握人工智能重要主题,深度强化学习实践书籍推荐

    ‍‍ 今天要给大家介绍的书是深度强化学习实践的第二版,本书的主题是强化学习(Reinforcement Learning,RL),它是机器学习(Machine Learning,ML)的一个分支,强调 ...

  3. 深度学习实践:计算机视觉_深度学习与传统计算机视觉技术:您应该选择哪个?

    深度学习实践:计算机视觉 计算机视觉 (Computer Vision) Deep Learning(DL) is undeniably one of the most popular tools u ...

  4. PyTorch深度学习实践

    根据学习情况随时更新. 2020.08.14更新完成. 参考课程-刘二大人<PyTorch深度学习实践> 文章目录 (一)课程概述 (二)线性模型 (三)梯度下降算法 (四)反向传播 (五 ...

  5. 创建设计模式 - Singleton设计模式(最佳实践与示例)

    Java Singleton设计模式最佳实践与示例 Java Singleton Pattern是四种帮派设计模式之一,属于创建设计模式类别.从定义来看,它似乎是一个非常简单的设计模式,但是当涉及到实 ...

  6. 我写了一份初学者的学习实践教程!

    Datawhale干货 作者:牧小熊,Datawhale成员 上周在Datawhale分享了一篇关于数据挖掘赛事的baseline方案,有老师把它作为学习资料给学生实践学习后,有挺多同学反应学习实践中 ...

  7. 【网络安全学习实践】Windows系统密码破解防护及用户和组管理

    halo~我是bay_Tong桐小白 本文内容是桐小白个人对所学知识进行的总结和分享,知识点会不定期进行编辑更新和完善,了解最近更新内容可参看更新日志,欢迎各位大神留言.指点 [学习网络安全知识,维护 ...

  8. 【Pytorch深度学习实践】B站up刘二大人之BasicCNN Advanced CNN -代码理解与实现(9/9)

    这是刘二大人系列课程笔记的 最后一个笔记了,介绍的是 BasicCNN 和 AdvancedCNN ,我做图像,所以后面的RNN我可能暂时不会花时间去了解了: 写在前面: 本节把基础个高级CNN放在一 ...

  9. 安装gym库_强化学习Gym库学习实践(一)

    最近看了一篇研究方向相关的文章,介绍了一种DQN的应用,感觉还挺新鲜的.想着把这篇文章复现出来,就开始学习强化学习的相关知识,作为一名小白,这一路走的可是真的十分艰难(我太菜了啊!) 看了莫烦Pyth ...

最新文章

  1. Judge Judy
  2. Java引用类型有哪些
  3. 如何获取用户当前详细的地理位置
  4. c语言字符串英文,C语言字符串函数大全(国外英文资料).doc
  5. 大哥你需求里说只要工作流引擎组件,怎么真正需要的东西这么.悲剧了,客户需求无止境...
  6. 远程连接服务器出现 SQL Error (1130): Host IP is not allowed to connect to this MySQL server 错误...
  7. 你所不知道的Quartz特性
  8. IE漏洞被黑客利用,导致全球上万个网站受害
  9. 具体化和实例化的应用
  10. matlab定积分上界求解,定积分问题的数值求解及Matlab实现
  11. web使用js调用摄像头扫码、拍照、录像
  12. 阿里图标库iconfont下载和在旧有的iconfont中添加新的图标
  13. ireport java 变量_iReport —— 使用 JavaBean 作为数据源
  14. docker学习总结X - 碰到的问题
  15. 机器人论文(1)-下肢外骨骼的平衡与稳定性问题:系统综述
  16. DTI及MRI数据预处理
  17. 吊打 CLIP 平均10个点,Meta 多模态通用模型 FLAVA真香啊
  18. 身份证OCR识别接口的优点
  19. 二叉树探究之非叶子结点和叶子结点对半分且最多差一个
  20. 讲几个关于程序员笑话!

热门文章

  1. 2020最新eclipse安装教程,配有每一步的安装过程和细节!
  2. DuiLib学习笔记1.编译运行demo
  3. IT知名公司工资一览
  4. 张驰咨询:为什么六西格玛项目完成后容易反弹?
  5. 语音识别的基础知识与CMUsphinx介绍
  6. 三feng云,免费主机
  7. 第二次创客沙龙暨平台联合探讨会PPT-18.5.12
  8. 10-1 18 19
  9. android隐藏软键盘方法,Android显示和隐藏软键盘方法
  10. Java Label