Avalonia Tutorial: Menerapkan MVVM Langkah demi Langkah dengan Contoh

Avalonia kan?


Avalonia adalah kerangka kerja XAML lintas-platform untuk platform .Net. Bagi banyak pengembang di WPF / UWP / Xamarin, kerangka kerja ini akan intuitif dan mudah dipelajari. Avalonia mendukung Windows, Linux, macOS, dukungan eksperimental untuk Android dan iOS juga diumumkan. Proyek ini sedang dikembangkan dengan dukungan dari masyarakat dan bersifat open source.


Anda dapat membaca lebih lanjut tentang kerangka kerja di tautan berikut:



Motivasi untuk artikel.


Penulis menyukai pengembangan menggunakan WPF / UWP dan memiliki pengalaman dalam proyek nyata. Platform ini memiliki pro dan kontra, tetapi kelemahan utamanya adalah bahwa mereka tidak cross-platform. Untuk waktu yang lama, fokus Microsoft pada lingkungan Windows, bahkan dengan Mono dan rilis Net Core, untuk banyak pengembang di luar dunia .net, telah mempertahankan stereotip bahwa C # hanya untuk Windows. Dan jika untuk back-end platform .Net Core benar-benar solusi, maka dari sudut pandang desktop lintas-platform tidak ada perubahan. Porting WinForms dan WPF untuk dijalankan dari .Net Core adalah optimalisasi kode C #, tetapi tidak ada perubahan dalam hal dukungan Linux / MacOS.


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


Itu saja, saya harap artikel ini akan bermanfaat bagi Anda, dan Anda akan menemukan sesuatu yang baru untuk diri Anda sendiri di dalamnya. Pada akhirnya, saya ingin mengucapkan terima kasih:



All Articles