赞
踩
Avalonia是基于.NET的跨平台UI框架,能够支持在Windows、Linux、MacOS等操作系统中运行客户端。在官方的MAUI没有发布最新稳定版,对于客户端程序的跨平台开发仍然是不错的选择,尤其是已经有WPF基础的,能够很快上手。
开发工具:VisualStudio 2019
处理架构:x86架构(AMD系列)
操作系统:Windows10,统信UOS家庭版
打开VS“扩展>管理扩展”,搜索“Avalonia”,安装“Avalonia for Visual Studio 2019,2017”。
插件安装完成时,在创建新项目界面,可以看到Avalonia的模板,有常规模式应用和MVVM模式应用。有些WPF基础的可以选择MVVM模式应用。选择“Avalonia MVVM Application”。
如果VS刚刚更新过NET6,可能会提示错误,不能正常编译。将项目文件中的目标框架从NET6.0改为NET5.0。如果能够正常编译则忽略。
这是典型的MVVM模式结构,主要代码文件及目录用途如下:
Program.cs:系统入口
App.axaml:应用样式及初始化等
ViewLocator:View层与ViewModel层的映射
实现一个简单功能,从文件中读取气象数据,包括观测站点、时间、温度、湿度、降水量、气压、风速等,以列表形式进行展示。
原始数据是从国家气象数据中心下载的公开数据,用于做示例。由于只是简单示例,只考虑了txt文件的读取,没有Excel表格操作,也不涉及到数据库和ORM。
气象数据为txt文件,数据间以空格间隔。
另外还有一份观测站点的数据,与气象数据中的站点编号能够进行对应。
观测站点的原始文件是PDF,导出成Excel后又导出成txt
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>
创建气象数据类
/// <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; } }
原始数据中的经纬度为度分秒格式,增加一个字段用来表示十进制的经纬度,同时与气象数据中的站点位置对应。
在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 Command public ReactiveCommand<Unit,Unit> SelectClimateFileCommand { get; }//选择气象数据文件 public ReactiveCommand<Unit,Unit> GetClimateCommand { get; }//获取气象数据 public ReactiveCommand<Unit, Unit> SelectStationFileCommand { get; }//选择站点数据文件 public ReactiveCommand<Unit,Unit> GetStationCommand { get; }//获取站点数据 #endregion
在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环境下程序直接卡死。
坑坑不息,多学有益
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。