当前位置:   article > 正文

Avalonia学习实践(一)--示例

avalonia

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>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

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; }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

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

创建观测站点类

    /// <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; }
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

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

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 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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134

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

4.6成果示例

在这里插入图片描述


坑坑不息,多学有益

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/90628
推荐阅读
相关标签
  

闽ICP备14008679号