Avalonia Learning Practice (1)--Example

Avalonia is a .NET-based cross-platform UI framework that can support running clients in Windows, Linux, MacOS and other operating systems. The latest stable version has not been released on the official MAUI. It is still a good choice for cross-platform development of client programs, especially those who already have a WPF foundation and can get started quickly.


0. Development environment

Development tools: VisualStudio 2019
Processing architecture: x86 architecture (AMD series)
Operating system: Windows10, Tongxin UOS Home Edition


1. Install the VS plugin

Open VS "Extensions > Manage Extensions", search for "Avalonia", and install "Avalonia for Visual Studio 2019,2017".
insert image description here

2. Create a new application

When the plug-in installation is complete, you can see the template of Avalonia in the interface of creating a new project, including regular mode application and MVVM mode application. Some WPF-based applications can choose the MVVM pattern. Select "Avalonia MVVM Application".
insert image description here

If VS has just updated NET6, it may prompt an error and cannot compile normally. Change the target framework in the project file from NET6.0 to NET5.0. Ignored if it compiles normally.
insert image description here

3. Code project structure

This is a typical MVVM pattern structure. The main code files and directories are used as follows:
Program.cs: System entry
App.axaml: Application style and initialization, etc.
ViewLocator: Mapping between View layer and ViewModel layer
insert image description here

4. Application examples

4.1 Application Requirements

Implement a simple function to read meteorological data from a file, including observation sites, time, temperature, humidity, precipitation, air pressure, wind speed, etc., and display them in a list form.
The raw data are publicly available data downloaded from the National Weather Data Center for example purposes. Since it is just a simple example, only the reading of txt files is considered, there is no Excel table operation, and no database and ORM are involved.
The meteorological data is a txt file, and the data are separated by spaces.
insert image description here
In addition, there is a data of observation stations, which can correspond to the station numbers in the meteorological data.

The original file of the observation site is PDF, which is exported to Excel and then exported to txt

insert image description here

4.2 Interface Layout

Avaloina's axaml file has the same rules as WPF's xaml file, so there is no difficulty in switching.

The DataGrid control needs to be installed separately, and it is not included in the default control library. Open the NuGet manager, search for "Avalonia.Controls.DataGrid" to install
insert image description here

The page layout code is as follows:

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

Create weather data class

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

Added two fields of site name and geographic location information

Create an Observation Site Class

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

The latitude and longitude in the original data are in the format of degrees, minutes and seconds, and a field is added to represent the latitude and longitude in decimal, which corresponds to the station location in the meteorological data.

4.4 View Binding

Create property objects and bind commands in 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

4.5 Data processing

Initialize data in MainWindowViewModel and define functions to process data.

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 does not include MessageBox by default. It needs to be installed separately. You can install the third-party library MessageBox from Nuget. Avalonia
2. Avalonia comes with OpenFileDialog and SaveFileDialog, etc. The reason for the Task task call is that it is not in the same thread as the main UI thread. After selecting the file, the control bound to the file path is not updated, and the data has indeed changed.
3. When using the dialog box that comes with Avalonia, you need to specify the parent form. If you refer to the method in App.axaml.cs to obtain the example of the current form through the application life cycle ApplicationLifeTime, the dialog box can pop up normally under Windows, but under Linux Under the environment, the program is directly stuck.

4.6 Examples of Achievements

insert image description here


There are endless pits and pits, and it is beneficial to learn more

Guess you like

Origin blog.csdn.net/lordwish/article/details/124741729