Avalonia Tutorial: Implementing MVVM Step by Step with Examples

Avalonia is it?


Avalonia is a cross-platform XAML framework for the .Net platform. For many developers on WPF / UWP / Xamarin, this framework will be intuitive and easy to learn. Avalonia supports Windows, Linux, macOS, experimental support for Android and iOS is also announced. The project is being developed with the support of the community and is open-source.


You can read more about the framework at the following links:



Motivation for the article.


The author likes the development using WPF / UWP and has experience in real projects. These platforms have their pros and cons, but the main drawback was that they are not cross-platform. For a long time, Microsoft's focus on the Windows environment, even with Mono and the release of Net Core, for many developers outside the .net world, retained the stereotype that C # is only for Windows. And if for the back-end the .Net Core platform was really a solution, then from the point of view of the cross-platform desktop there were no changes. Porting WinForms and WPF to run from .Net Core is an optimization of C # code, but there were no changes in terms of Linux / MacOS support.


. issue Github.


ReactiveUI issue MVVM . .


MVVM


(1,2) Xamarin.Forms, Avalonia, .


MVVM

MVVM (Model-View-ViewModel) - a design template that focuses on the separation of business logic and program interface. This template is widely used in applications on the WPF / UWP / Xamarin platforms.


What separates from what what?


When using the above frameworks, the application is divided into two layers:


  • , “M” (Model). . , , API, .. , - (Services).
  • – V (View) XAML.

– (ViewModel).


ViewModel — Model View (Binding). Binding, (Property) – ViewModel. , binding, property, ViewModel View. , property – , View Model "" ViewModel , ( , API .)
:



: , (View. ViewModel, Model, binding, property). , , , .


?


. MVVM, , Model View, ViewModel. :


  • / , View. , Android / iOS Xamarin . MVVM, View, WPF/UWP. MVVM.
  • Unit-. Model ViewModel Unit-, .
  • , – .


. :


  1. ;
  2. ;
  3. MVVM.

, WPF/UWP/Xamarin . , , .



Visual Studio Avalonia .
, , Avalonia .Net Framework Net Core 2.0+. Net Core. Visual Studio 2019, Net Core 3.0. Avalonia Visual Studio 2019:



,


Avalonia :



Program.cs – Main . Application App. :
App.xaml – Application.
App.xaml.cs – code-behind , xaml , MainWindow.
MainWindow Window, code-behind ( , .) MainWindow.xaml.


, “Welcome to Avalonia”.



, , () . , MainWindow.xaml :


<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="Moviewer.MainWindow"
        Title="Moviewer">
  <Window.Styles>
    <StyleInclude Source="avares://Moviewer.Styling/AppStyle.xaml"/>
  </Window.Styles>
   Welcome to Avalonia!
</Window>

, :


<Window xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
        x:Class="Moviewer.MainWindow"
        Title="Moviewer">
  <Window.Styles>
    <StyleInclude Source="avares://Moviewer.Styling/AppStyle.xaml"/>
  </Window.Styles>

  <Grid RowDefinitions="Auto, *" Classes="mainContainer">
    <Border Classes="header" Grid.Row="0">
      <StackPanel Classes="title">
        <TextBlock Classes="title"> MO </TextBlock>
        <TextBlock Classes="titleYellow"> VIEW </TextBlock>
        <TextBlock Classes="title"> ER </TextBlock>
      </StackPanel>
    </Border>
  </Grid>
</Window>

:




GitHub. Moviewer Moviewer.csproj :


  <ItemGroup>
    <None Update="Data\**\*\*">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>

, . , : project-folder/bin/netcoreapp3.0/Data.



:


MVVM


ViewModels ViewModelBase, MainWindowViewModel.cs ViewModel INotifyPropertyChanged. Property, ViewModel. ViewModel, , ViewModelBase, ViewModel ViewModelBase.


: (MVVMLight, Prism, ReactiveUI, MVVM Cross), . . ReactiveUI.


    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

MainWindowViewModel.cs , binding , View ViewModel.


    public class MainWindowViewModel : ViewModelBase
    {
        public string Text => "Welcome to Avalonia";
    }

, MainWindow.xaml Grid.


  <StackPanel Grid.Row="1">
      <TextBlock Text="{Binding Text, Mode=OneWay}"/>
  </StackPanel>

, ViewModel. App.xaml.cs, DataContext MainWindow.


        public override void OnFrameworkInitializationCompleted()
        {
            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
            {
                desktop.MainWindow = new MainWindow()
                {
                    DataContext = new MainWindowViewModel()
                };
            }

            base.OnFrameworkInitializationCompleted();
        }

DataContext – , MainWindowViewModel. , : “property MainWindowViewModel, MainWindow.xaml”
MainWindow.xaml binding MainWindowViewModel. , “Welcome to Avalonia” .
, DataContext .


Model


:


    public class Movie
    {
        public int Id { get; set; }
        public int VoteCount { get; set; }
        public string PosterPath { get; set; }
        public string Title { get; set; }
        public double VoteAverage { get; set; }
        public string Overview { get; set; }
        public string ReleaseDate { get; set; }

        [JsonIgnore]
        public Bitmap Poster { get; set; }
    }

. Services . json Newtonsoft.Json


    public class MovieService
    {
        readonly string _workingDirectory = Environment.CurrentDirectory;
        public async Task<IEnumerable<Movie>> GetMovies(int pageIndex)
        {
            var folderPath = $"{_workingDirectory}\\Data\\Page{pageIndex}";
            var dataFile = $"page{pageIndex}.json";
            var imageFolder = Path.Combine(folderPath, "Images");
            List<Movie> items;

            //read data
            using (var r = new StreamReader(Path.Combine(folderPath, dataFile)))
            {
                var json = r.ReadToEnd();
                items = JsonConvert.DeserializeObject<List<Movie>>(json);
            }

            //load images
            foreach (var item in items)
            {
                var imagePath = Path.Combine(imageFolder, $"{item.Title}.png");
                item.Poster = await GetPoster(imagePath);
            }
            return items;
        }

       private Task<Bitmap> GetPoster(string posterUrl)
        {
            return Task.Run(() =>
            {
                using var fileStream = new FileStream(posterUrl, FileMode.Open, FileAccess.Read) {Position = 0};
                var bitmap = new Bitmap(fileStream);
                return bitmap;
            });
        }

    } 

GetMovies , GetPoster. , ViewModel View .


ViewModel View


GetMovie Task, NotifyTaskCompletion Nito.AsyncEx 3.0.1 ( 3.0.1). ViewModel .
: ProgressBar, .
: , , ( 1 ) Task.Delay(1000).


    public class MainWindowViewModel : ViewModelBase
    {
        private MovieService _movieService;
        public MainWindowViewModel()
        {
            InitializationNotifier = NotifyTaskCompletion.Create(InitializeAsync());
        }

        public INotifyTaskCompletion InitializationNotifier { get; private set; }

        private async Task InitializeAsync()
        {
            _movieService = new MovieService();
            var data = await _movieService.GetMovies(1);
        await Task.Delay(1000);
            MyItems = new ObservableCollection<Movie>(data);
        }

        private ObservableCollection<Movie> _myItems;
        public ObservableCollection<Movie> MyItems
        {
            get => _myItems;
            set
            {
                if (value != null)
                {
                    _myItems = value;
                    OnPropertyChanged();
                }
            }
        }
    }
}

, StackPanel MainWindows.xaml :



    <Grid Classes="contentContainer" Grid.Row="1">
      <ProgressBar
          VerticalAlignment="Center"
          HorizontalAlignment="Center"
          IsVisible="{Binding InitializationNotifier.IsNotCompleted}"
          Classes="progressBar" IsIndeterminate="True"/>

      <ListBox Classes="movies"
        Grid.Column="1" Grid.Row="1"
        IsVisible="{Binding InitializationNotifier.IsCompleted, Mode=TwoWay}"
        ScrollViewer.HorizontalScrollBarVisibility="Disabled"
        Items="{Binding MyItems}">
        <ListBox.ItemTemplate>
          <DataTemplate>
            <Grid>
              <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto" MinHeight="48"/>
              </Grid.RowDefinitions>
              <Image Grid.Row="0" Stretch="UniformToFill" Source="{Binding Poster}"/>
              <Border Grid.Row="1" Classes="title">
                <Grid ColumnDefinitions="*, 0.4*" Margin="4">
                  <TextBlock FontSize="18" Text="{Binding Title}" />
                  <TextBlock FontSize="24" Grid.Column="1" Text="{Binding VoteAverage}"/>
                </Grid>
              </Border>
            </Grid>
          </DataTemplate>
        </ListBox.ItemTemplate>
        <ListBox.ItemsPanel>
          <ItemsPanelTemplate>
            <WrapPanel ItemWidth="340" ItemHeight="480" Orientation="Horizontal"/>
          </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
      </ListBox>
    </Grid>

, ProgressBar, .




ViewModel Page, . PropertyChange “” property:


        private int _page;
        public int Page
        {
            get => _page;
            set
            {
                _page = value;
                OnPropertyChanged();
            }
        }

: , (Fody, ReactiveUI) .


: , . GetMovie . , , . , .


         public async Task LoadData(int page)
        {
            var data = await _movieService.GetMovies(page);
            await Task.Delay(1000);
            MyItems = new ObservableCollection<Movie>(data);
        }

ViewModel :


        private async Task InitializeAsync()
        {
            Page = 1;
            _movieService = new MovieService();
            await LoadData(Page);
        }

:


        public void NextPage()
        {
            if (_page+1 > 10)
                Page = 1;  
            else
                Page = _page + 1;

            InitializationNotifier = NotifyTaskCompletion.Create(LoadData(Page));
        }

        public void PrevPage()
        {
            if (1 > _page - 1)
                Page = 10;
            else
                Page = _page - 1;

            InitializationNotifier = NotifyTaskCompletion.Create(LoadData(Page));
        }

. , 1 . Prev , . , MVVM (Commands). XAML , ( ), , .



, , ViewModel, NotifyTaskCompletion. , ProgressBar , InitializationNotifier property, OnPropertyChange():


        private INotifyTaskCompletion _initializationNotifier;
        public INotifyTaskCompletion InitializationNotifier
        {
            get => _initializationNotifier;
            private set
            {
                _initializationNotifier = value;
                OnPropertyChanged();
            }
        }

Grid 2- 3- . Grid.Row = “1” (, > ):


ColumnDefinitions="Auto, *, Auto"
RowDefinitions="Auto, *" >

ProgressBar


 Grid.Column="1" Grid.Row="1" 

MainWindow .


      <TextBlock Grid.Column="1"
                 HorizontalAlignment="Center"
                 Margin="4"
                 FontSize="18"
                 FontWeight="Bold"
                 Foreground="#819FFF"
                 Text="{Binding Path=Page}"/>

      <Button Command="{Binding PrevPage}"
        Grid.Row="1" Grid.Column="0"
        Content="PREV"
        Classes="navigation">
      </Button>

      <Button Command="{Binding NextPage}"
            Grid.Row="1" Grid.Column="2"
            Content="NEXT"
            Classes="navigation">
      </Button>

! Windows 10, Mint.




Linux


Net Core, dotnet Moviewer.dll ( , )


Avalonia


, «» - «, ». , “ UI Net Windows”. , . , – : , , , .


Avalonia css. . , WPF/UWP. :



, Visual Studio, . Linux, . (ForNeVeR) Rider .


That's all, I hope this article will be useful to you, and you will find something new for yourself in it. In the end, I want to thank:



All Articles