рдПрд╡рд▓реЛрдирд┐рдпрд╛ рдЯреНрдпреВрдЯреЛрд░рд┐рдпрд▓: рдПрдорд╡реАрд╡реАрдПрдо рд╕реНрдЯреЗрдк рдмрд╛рдИ рд╕реНрдЯреЗрдк рдЙрджрд╛рд╣рд░рдгреЛрдВ рдХреЗ рд╕рд╛рде рд▓рд╛рдЧреВ рдХрд░рдирд╛

рдПрд╡рд▓реЛрдирд┐рдпрд╛ рдХреНрдпрд╛ рд╣реИ?


Avalonia .net рдкреНрд▓реЗрдЯрдлреЙрд░реНрдо рдХреЗ рд▓рд┐рдП рдПрдХ рдХреНрд░реЙрд╕-рдкреНрд▓реЗрдЯрдлреЙрд░реНрдо XAML рдлреНрд░реЗрдорд╡рд░реНрдХ рд╣реИред WPF / UWP / Xamarin рдкрд░ рдХрдИ рдбреЗрд╡рд▓рдкрд░реНрд╕ рдХреЗ рд▓рд┐рдП, рдпрд╣ рдврд╛рдВрдЪрд╛ рд╕рд╣рдЬ рдФрд░ рд╕реАрдЦрдирд╛ рдЖрд╕рд╛рди рд╣реЛрдЧрд╛ред рдПрд╡рд▓реЛрдирд┐рдпрд╛ рд╡рд┐рдВрдбреЛрдЬ, рд▓рд┐рдирдХреНрд╕, рдореИрдХрдУрдПрд╕ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ, рдПрдВрдбреНрд░реЙрдЗрдб рдФрд░ рдЖрдИрдУрдПрд╕ рдХреЗ рд▓рд┐рдП рдкреНрд░рдпреЛрдЧрд╛рддреНрдордХ рд╕рдорд░реНрдерди рдХреА рднреА рдШреЛрд╖рдгрд╛ рдХреА рдЧрдИ рд╣реИред рдкрд░рд┐рдпреЛрдЬрдирд╛ рд╕рдореБрджрд╛рдп рдХреЗ рд╕рдорд░реНрдерди рд╕реЗ рд╡рд┐рдХрд╕рд┐рдд рдХреА рдЬрд╛ рд░рд╣реА рд╣реИ рдФрд░ рдпрд╣ рдУрдкрди-рд╕реЛрд░реНрд╕ рд╣реИред


рдЖрдк рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд▓рд┐рдВрдХ рдкрд░ рд░реВрдкрд░реЗрдЦрд╛ рдХреЗ рдмрд╛рд░реЗ рдореЗрдВ рдЕрдзрд┐рдХ рдкрдврд╝ рд╕рдХрддреЗ рд╣реИрдВ:



рд▓реЗрдЦ рдХреЗ рд▓рд┐рдП рдкреНрд░реЗрд░рдгрд╛ред


рд▓реЗрдЦрдХ WPF / UWP рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рд╡рд┐рдХрд╛рд╕ рдкрд╕рдВрдж рдХрд░рддрд╛ рд╣реИ рдФрд░ рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдкрд░рд┐рдпреЛрдЬрдирд╛рдУрдВ рдореЗрдВ рдЕрдиреБрднрд╡ рд░рдЦрддрд╛ рд╣реИред рдЗрди рдкреНрд▓реЗрдЯрдлрд╛рд░реНрдореЛрдВ рдореЗрдВ рдЙрдирдХреЗ рдкреЗрд╢реЗрд╡рд░реЛрдВ рдФрд░ рд╡рд┐рдкрдХреНрд╖ рд╣реИрдВ, рд▓реЗрдХрд┐рди рдореБрдЦреНрдп рджреЛрд╖ рдпрд╣ рдерд╛ рдХрд┐ рд╡реЗ рдХреНрд░реЙрд╕-рдкреНрд▓реЗрдЯрдлреЙрд░реНрдо рдирд╣реАрдВ рд╣реИрдВред рдПрдХ рд▓рдВрдмреЗ рд╕рдордп рдХреЗ рд▓рд┐рдП, рдорд╛рдЗрдХреНрд░реЛрд╕реЙрдлреНрдЯ рдХреЗ рд╡рд┐рдВрдбреЛрдЬ рдкрд░реНрдпрд╛рд╡рд░рдг рдкрд░ рдзреНрдпрд╛рди рдХреЗрдВрджреНрд░рд┐рдд, рдпрд╣рд╛рдВ рддрдХ тАЛтАЛрдХрд┐ рдореЛрдиреЛ рдХреЗ рд╕рд╛рде рдФрд░ рдиреЗрдЯ рдХреЛрд░ рдХреА рд░рд┐рд╣рд╛рдИ, .net рджреБрдирд┐рдпрд╛ рдХреЗ рдмрд╛рд╣рд░ рдХрдИ рдбреЗрд╡рд▓рдкрд░реНрд╕ рдХреЗ рд▓рд┐рдП, рд╕реНрдЯреАрд░рд┐рдпреЛрдЯрд╛рдЗрдк рдХреЛ рдмрд░рдХрд░рд╛рд░ рд░рдЦрд╛ рд╣реИ рдХрд┐ рд╕реА # рдХреЗрд╡рд▓ рд╡рд┐рдВрдбреЛрдЬ рдХреЗ рд▓рд┐рдП рд╣реИред рдФрд░ рдЕрдЧрд░ рдмреИрдХ-рдПрдВрдб .Net рдХреЛрд░ рдкреНрд▓реЗрдЯрдлрд╝реЙрд░реНрдо рдХреЗ рд▓рд┐рдП рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рдПрдХ рд╕рдорд╛рдзрд╛рди рдерд╛, рддреЛ рдХреНрд░реЙрд╕-рдкреНрд▓реЗрдЯрдлрд╝реЙрд░реНрдо рдбреЗрд╕реНрдХрдЯреЙрдк рдХреЗ рджреГрд╖реНрдЯрд┐рдХреЛрдг рд╕реЗ рдЗрд╕рдореЗрдВ рдХреЛрдИ рдмрджрд▓рд╛рд╡ рдирд╣реАрдВ рд╣реБрдЖ рдерд╛ред WinForms рдФрд░ WPF рдХреЛ рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП .net Core рд╕реЗ рдкреЛрд░реНрдЯ рдХрд░рдирд╛ C # рдХреЛрдб рдХрд╛ рдПрдХ рдЕрдиреБрдХреВрд▓рди рд╣реИ, рд▓реЗрдХрд┐рди рд▓рд┐рдирдХреНрд╕ / 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 .


рдпрд╣ рд╕рдм, рдореБрдЭреЗ рдЙрдореНрдореАрдж рд╣реИ рдХрд┐ рдпрд╣ рд▓реЗрдЦ рдЖрдкрдХреЗ рд▓рд┐рдП рдЙрдкрдпреЛрдЧреА рд╣реЛрдЧрд╛, рдФрд░ рдЖрдк рдЗрд╕рдореЗрдВ рдЕрдкрдиреЗ рд▓рд┐рдП рдХреБрдЫ рдирдпрд╛ рдкрд╛рдПрдВрдЧреЗред рдЕрдВрдд рдореЗрдВ, рдореИрдВ рдзрдиреНрдпрд╡рд╛рдж рджреЗрдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ:



All Articles