Avalonia Tutorial: Schrittweise Implementierung von MVVM mit Beispielen

Avalonia ist es?


Avalonia ist ein plattformübergreifendes XAML-Framework für die .Net-Plattform. Für viele Entwickler von WPF / UWP / Xamarin ist dieses Framework intuitiv und leicht zu erlernen. Avalonia unterstützt Windows, Linux, MacOS, experimentelle Unterstützung für Android und iOS wird ebenfalls angekündigt. Das Projekt wird mit Unterstützung der Community entwickelt und ist Open Source.


Weitere Informationen zum Framework finden Sie unter den folgenden Links:



Motivation für den Artikel.


Der Autor mag die Entwicklung mit WPF / UWP und hat Erfahrung in realen Projekten. Diese Plattformen haben ihre Vor- und Nachteile, aber der Hauptnachteil war, dass sie nicht plattformübergreifend sind. Der Fokus von Microsoft auf die Windows-Umgebung, auch mit Mono und der Veröffentlichung von Net Core, blieb für viele Entwickler außerhalb der .net-Welt lange Zeit das Stereotyp, dass C # nur für Windows gilt. Und wenn für das Back-End die .Net Core-Plattform wirklich eine Lösung war, dann gab es aus Sicht des plattformübergreifenden Desktops keine Änderungen. Das Portieren von WinForms und WPF zur Ausführung von .Net Core ist eine Optimierung des C # -Codes, es wurden jedoch keine Änderungen hinsichtlich der Linux / MacOS-Unterstützung vorgenommen.


. issue Github.


ReactiveUI issue MVVM . .


MVVM


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


MVVM

MVVM (Model-View-ViewModel) – , - . WPF/UWP/Xamarin.


“” “” ?


, :


  • , “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 .


Das ist alles, ich hoffe, dieser Artikel wird Ihnen nützlich sein und Sie werden darin etwas Neues für sich finden. Am Ende möchte ich mich bedanken bei:



All Articles