是阿瓦隆吗?
Avalonia是用于.Net平台的跨平台XAML框架。对于使用WPF / UWP / Xamarin的许多开发人员而言,此框架将直观易学。Avalonia支持Windows,Linux,macOS,还宣布了对Android和iOS的实验性支持。该项目是在社区的支持下开发的,并且是开源的。
有关该框架的更多信息,请参见以下链接:
本文的动机。
作者喜欢使用WPF / UWP进行开发,并具有实际项目的经验。这些平台各有利弊,但主要缺点是它们不是跨平台的。长期以来,对于.net以外的许多开发人员,Microsoft一直将重点放在Windows环境上,即使使用Mono和Net Core发行版,也保留了C#仅适用于Windows的构造型。如果对于后端.Net Core平台确实是一个解决方案,那么从跨平台桌面的角度来看,没有任何变化。移植WinForms和WPF以从.Net Core运行是对C#代码的优化,但是在Linux / MacOS支持方面没有任何变化。
空闲时间一出现,我便决定尝试了解该框架。具体而言,对于文章的原因是发行阿瓦隆尼亚的Github上的官方文档。
现在,在文档中直接使用ReactiveUI库制作了一个示例,这个问题引起了一个问题,即需要在干净的项目模板上实现MVVM模式而不连接第三方库的问题。这是本文追求的目标。
理论上的MVVM
关于这个模板,它是足够写上哈布雷(1,2)和文档中Xamarin.Forms,阿瓦隆尼亚,因此这部分被提交到扰流板。
关于MVVMMVVM(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-, .
- , – .
实践
为了实现该应用程序,我们采用了展示热门电影的想法。我们形成以下任务:
- ;
- ;
- 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;
using (var r = new StreamReader(Path.Combine(folderPath, dataFile)))
{
var json = r.ReadToEnd();
items = JsonConvert.DeserializeObject<List<Movie>>(json);
}
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 .
仅此而已,我希望本文对您有所帮助,并且您会在本文中找到适合自己的新东西。最后,我要感谢: