التنقل في تطبيق .NET Core متعدد الأنظمة مع توفير حالة على قرص باستخدام مثال ReactiveUI و Avalonia



تكون واجهات المستخدم للتطبيقات الحديثة معقدة - غالبًا ما يكون من الضروري تنفيذ دعم التنقل في الصفحة ومعالجة حقول الإدخال بأنواعها المختلفة وعرض المعلومات أو إخفائها استنادًا إلى المعلمات التي حددها المستخدم. في الوقت نفسه ، من أجل تحسين UX ، يجب على التطبيق حفظ حالة عناصر الواجهة على القرص أثناء التعليق أو إيقاف التشغيل ، واستعادة الحالة من القرص عند إعادة تشغيل البرنامج.


يقترح إطار ReactiveUI MVVM الحفاظ على حالة التطبيق من خلال إجراء تسلسل للرسم البياني لنماذج العروض التقديمية في الوقت الذي يتم فيه تعليق البرنامج ، في حين تختلف آليات تحديد وقت التعليق عن الأطر والأنظمة الأساسية. لذلك ، بالنسبة لـ WPF ، يتم استخدام حدث Exit ، من أجل Xamarin.Android - ActivityPaused ، من أجل Xamarin.iOS - DidEnterBackground ، لـ OnLaunched - OnLaunched overload.


في هذه المقالة ، سننظر في استخدام ReactiveUI لحفظ واستعادة حالة البرنامج باستخدام واجهة المستخدم الرسومية ، بما في ذلك حالة جهاز التوجيه ، باستخدام إطار عمل واجهة المستخدم الرسومية Avalonia عبر النظام الأساسي كمثال . تفترض المادة فهمًا أساسيًا لنمط تصميم MVVM والبرمجة التفاعلية في سياق لغة C # ومنصة .NET للقارئ. تنطبق الخطوات الواردة في هذه المقالة على نظامي التشغيل Windows 10 و Ubuntu 18.


إنشاء المشروع


لمحاولة التوجيه في العمل ، قم بإنشاء مشروع .NET Core جديد من قالب Avalonia ، قم بتثبيت حزمة Avalonia.ReactiveUI - طبقة رقيقة من تكامل Avalonia و ReactiveUI. تأكد من تثبيت .NET Core SDK و git قبل البدء.


 git clone https://github.com/AvaloniaUI/avalonia-dotnet-templates git --git-dir ./avalonia-dotnet-templates/.git checkout 9263c6b dotnet new --install ./avalonia-dotnet-templates dotnet new avalonia.app -o ReactiveUI.Samples.Suspension cd ./ReactiveUI.Samples.Suspension dotnet add package Avalonia.ReactiveUI dotnet add package Avalonia.Desktop dotnet add package Avalonia 

تأكد من أن التطبيق يبدأ ويعرض نافذة تقول مرحبا بكم في أفالونيا!


 # Use .NET Core version which you have installed. # It can be netcoreapp2.0, netcoreapp2.1 and so on. dotnet run --framework netcoreapp3.0 



قم بتوصيل Avalonia pre-builds من MyGet


للاتصال واستخدام أحدث إصدارات Avalonia التي يتم نشرها تلقائيًا إلى MyGet عندما يتغير الفرع master لمستودع Avalonia في GitHub ، نستخدم ملف التكوين المصدر لحزمة nuget.config . لكي ترى IDE و .NET Core CLI nuget.config ، تحتاج إلى إنشاء ملف sln للمشروع الذي تم إنشاؤه أعلاه. نستخدم أدوات .NET Core CLI:


 dotnet new sln # Ctrl+C dotnet sln ReactiveUI.Samples.Suspension.sln add ReactiveUI.Samples.Suspension.csproj 

قم nuget.config ملف nuget.config في مجلد بملف .sln للمحتوى التالي:


 <?xml version="1.0" encoding="utf-8"?> <configuration> <packageSources> <add key="AvaloniaCI" value="https://www.myget.org/F/avalonia-ci/api/v2" /> </packageSources> </configuration> 

قد تحتاج إلى إعادة تشغيل IDE ، أو إلغاء تحميل وتنزيل الحل بأكمله. سنقوم بتحديث حزم Avalonia إلى الإصدار المطلوب (على الأقل 0.9.1 ) باستخدام واجهة مدير حزمة NuGet الخاصة بك IDE ، أو باستخدام أدوات سطر أوامر Windows أو محطة Linux:


 dotnet add package Avalonia.ReactiveUI --version 0.9.1 dotnet add package Avalonia.Desktop --version 0.9.1 dotnet add package Avalonia --version 0.9.1 cat ReactiveUI.Samples.Suspension.csproj 

الآن يبدو ملف المشروع ReactiveUI.Samples.Suspension.csproj كما يلي:


 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> </PropertyGroup> <ItemGroup> <Compile Update="**\*.xaml.cs"> <DependentUpon>%(Filename)</DependentUpon> </Compile> <AvaloniaResource Include="**\*.xaml"> <SubType>Designer</SubType> </AvaloniaResource> </ItemGroup> <ItemGroup> <PackageReference Include="Avalonia" Version="0.9.1" /> <PackageReference Include="Avalonia.Desktop" Version="0.9.1" /> <PackageReference Include="Avalonia.ReactiveUI" Version="0.9.1" /> </ItemGroup> </Project> 

قم ViewModels/ Views/ و ViewModels/ المجلدات في جذر المشروع ، وقم بتغيير اسم فئة MainView إلى MainView للراحة ، MainView إلى الدليل Views/ ، وتغيير مساحات الأسماء وفقًا لـ ReactiveUI.Samples.Suspension.Views . App.xaml.cs محتويات App.xaml.cs Program.cs و App.xaml.cs - تطبيق استدعاء UseReactiveUI على منشئ تطبيق Avalonia ، ونقل التهيئة للعرض الرئيسي إلى OnFrameworkInitializationCompleted ليتوافق مع توصيات إدارة دورة حياة التطبيق:


Program.cs


 class Program { //  .   API  Avalonia  , //  SynchronizationContext,    // OnFrameworkInitializationCompleted  App.xaml.cs:  //     -    . public static void Main(string[] args) => BuildAvaloniaApp() .StartWithClassicDesktopLifetime(args); //   Avalonia.    // ,      . public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<App>() .UseReactiveUI() // ! .UsePlatformDetect() .LogToDebug(); } 

App.xaml.cs


 public class App : Application { public override void Initialize() => AvaloniaXamlLoader.Load(this); //    .     //  MVVM , DI    .    //     ApplicationLifetime,    //     . public override void OnFrameworkInitializationCompleted() { new Views.MainView().Show(); base.OnFrameworkInitializationCompleted(); } } 

ستحتاج إلى إضافة using Avalonia.ReactiveUI إلى Program.cs . تأكد من أنه بعد تحديث الحزم ، يبدأ المشروع ويعرض نافذة الترحيب الافتراضية.


 # Use .NET Core version which you have installed. # It can be netcoreapp2.0, netcoreapp2.1 and so on. dotnet run --framework netcoreapp3.0 



عبر منصة التوجيه ReactiveUI


كقاعدة عامة ، هناك طريقتان رئيسيتان لتطبيق التنقل بين صفحات تطبيق .NET - العرض أولاً وعرض الطراز أولاً. يتضمن أسلوب العرض الأول التحكم في مكدس التنقل والتنقل بين الصفحات على مستوى العرض في مصطلحات MVVM - على سبيل المثال ، باستخدام فئتي الإطار والصفحة في حالة UWP أو WPF ، وعند استخدام نهج طريقة العرض الأولى ، يتم تطبيق التنقل على مستوى نماذج العرض التقديمي. تركز أدوات ReactiveUI التي تنظم التوجيه في التطبيق على استخدام أسلوب العرض أولاً. يتكون توجيه ReactiveUI من تطبيق IScreen يحتوي على حالة جهاز التوجيه ، والعديد من IRoutableViewModel والتحكم XAML RoutedViewHost بالنظام الأساسي ، RoutedViewHost .




يتم تمثيل حالة جهاز التوجيه بواسطة كائن RoutingState ، والذي يتحكم في مكدس التنقل. IScreen هو أصل مكدس التنقل ، وقد يكون هناك العديد من جذور التنقل في التطبيق. يراقب RoutedViewHost حالة جهاز التوجيه RoutingState المطابق ، ويستجيب للتغييرات في مكدس التنقل عن طريق تضمين IRoutableViewModel المطابق IRoutableViewModel تحكم XAML. سيتم توضيح الوظيفة الموضحة بالأمثلة أدناه.


حفظ حالة عرض النماذج على القرص


النظر في مثال نموذجي لشاشة البحث عن المعلومات.




يجب أن نقرر ما هي عناصر نموذج تمثيل الشاشة المراد حفظها على القرص أثناء تعليق التطبيق أو إيقاف تشغيله ، وأي منها - لإعادة إنشائه في كل مرة يبدأ فيها. ليست هناك حاجة لحفظ حالة أوامر ReactiveUI التي تنفذ واجهة ICommand ReactiveCommand<TIn, TOut> يتم إنشاء وتهيئة ReactiveCommand<TIn, TOut> في المنشئ ، في حين تعتمد حالة مؤشر CanExecute على خصائص نموذج العرض ويتم إعادة حسابها عند تغييرها. تعتمد الحاجة إلى حفظ نتائج البحث - نقطة خلافية - على تفاصيل التطبيق ، ولكن سيكون من الحكمة حفظ واستعادة حالة حقل إدخال SearchQuery !


ViewModels / SearchViewModel.cs


 [DataContract] public class SearchViewModel : ReactiveObject, IRoutableViewModel { private readonly ReactiveCommand<Unit, Unit> _search; private string _searchQuery; //   IScreen  ,   NULL //  IScreen  Splat.Locator.    //      . public SearchViewModel(IScreen screen = null) { HostScreen = screen ?? Locator.Current.GetService<IScreen>(); //     SearchQuery , //     . var canSearch = this .WhenAnyValue(x => x.SearchQuery) .Select(query => !string.IsNullOrWhiteSpace(query)); //      ,  //     . _search = ReactiveCommand.CreateFromTask( () => Task.Delay(1000), //    canSearch); } public IScreen HostScreen { get; } public string UrlPathSegment => "/search"; public ICommand Search => _search; [DataMember] public string SearchQuery { get => _searchQuery; set => this.RaiseAndSetIfChanged(ref _searchQuery, value); } } 

يتم تمييز فئة طراز العرض بالسمة [DataContract] ، والخصائص التي تحتاج إلى إجراء تسلسل مع [DataMember] . هذا يكفي إذا كان المتسلسل المستخدم يستخدم أسلوب التقيد - يحفظ فقط الخصائص التي تم تحديدها صراحة بسمات على القرص ؛ في حالة نهج إلغاء الاشتراك ، من الضروري وضع علامة مع سمات [IgnoreDataMember] تلك الخصائص التي لا تحتاج إلى حفظها على القرص. بالإضافة إلى ذلك ، نقوم بتطبيق واجهة IRoutableViewModel في نموذج العرض الخاص بنا بحيث يصبح لاحقًا جزءًا من إطار التنقل IRoutableViewModel توجيه التطبيق.


وبالمثل ، ننفذ نموذج عرض صفحة التفويض

ViewModels / LoginViewModel.cs


 [DataContract] public class LoginViewModel : ReactiveObject, IRoutableViewModel { private readonly ReactiveCommand<Unit, Unit> _login; private string _password; private string _username; //   IScreen  ,   NULL //  IScreen  Splat.Locator.    //      . public LoginViewModel(IScreen screen = null) { HostScreen = screen ?? Locator.Current.GetService<IScreen>(); //     Username  Password // ,     . var canLogin = this .WhenAnyValue( x => x.Username, x => x.Password, (user, pass) => !string.IsNullOrWhiteSpace(user) && !string.IsNullOrWhiteSpace(pass)); //      ,  //    . _login = ReactiveCommand.CreateFromTask( () => Task.Delay(1000), //    canLogin); } public IScreen HostScreen { get; } public string UrlPathSegment => "/login"; public ICommand Login => _login; [DataMember] public string Username { get => _username; set => this.RaiseAndSetIfChanged(ref _username, value); } //        ! public string Password { get => _password; set => this.RaiseAndSetIfChanged(ref _password, value); } } 

نماذج العروض التقديمية لصفحتين من التطبيق جاهزة ، IRoutableViewModel بتنفيذ واجهة IRoutableViewModel ويمكن دمجها في جهاز توجيه IScreen . الآن ننفذ IScreen مباشرة. نحتفل بمساعدة من [DataContract] السمات التي خصائص نموذج العرض لتسلسل والتي يجب تجاهلها. انتبه إلى المحدد العام للخاصية المميزة بعلامة [DataMember] ، في المثال أدناه - الخاصية مفتوحة عن قصد للكتابة حتى يتسنى للمتسلسل تعديل مثيل تم إنشاؤه حديثًا للكائن أثناء إلغاء تسلسل النموذج.


ViewModels / MainViewModel.cs


 [DataContract] public class MainViewModel : ReactiveObject, IScreen { private readonly ReactiveCommand<Unit, Unit> _search; private readonly ReactiveCommand<Unit, Unit> _login; private RoutingState _router = new RoutingState(); public MainViewModel() { //       , //  ,   . var canLogin = this .WhenAnyObservable(x => x.Router.CurrentViewModel) .Select(current => !(current is LoginViewModel)); _login = ReactiveCommand.Create( () => { Router.Navigate.Execute(new LoginViewModel()); }, canLogin); //       , //  ,   . var canSearch = this .WhenAnyObservable(x => x.Router.CurrentViewModel) .Select(current => !(current is SearchViewModel)); _search = ReactiveCommand.Create( () => { Router.Navigate.Execute(new SearchViewModel()); }, canSearch); } [DataMember] public RoutingState Router { get => _router; set => this.RaiseAndSetIfChanged(ref _router, value); } public ICommand Search => _search; public ICommand Login => _login; } 

في تطبيقنا ، يحتاج فقط RoutingState إلى الحفظ على القرص ؛ لأسباب واضحة ، لا تحتاج إلى حفظ الأوامر على القرص - حالتها تعتمد كليا على جهاز التوجيه. يجب أن يتضمن الكائن المسلسل معلومات موسعة حول الأنواع التي تطبق IRoutableViewModel بحيث يمكن استعادة مكدس التنقل عند إلغاء التسلسل. نحن نصف منطق MainViewModel عرض MainViewModel ، ViewModels/MainViewModel.cs الفصل في ViewModels/MainViewModel.cs وفي مساحة اسم ReactiveUI.Samples.Suspension.ViewModels المقابلة.




التوجيه في تطبيق أفالونيا


يتم تطبيق منطق واجهة المستخدم على مستوى طبقة النموذج وطراز العرض التقديمي للتطبيق التجريبي ويمكن نقله إلى مجموعة منفصلة موجهة إلى .NET Standard ، لأنه لا يعرف أي شيء عن إطار عمل واجهة المستخدم الرسومية المستخدم. دعنا نلقي نظرة على طبقة العرض التقديمي. في مصطلحات MVVM ، تكون طبقة العرض التقديمي مسؤولة عن تقديم حالة نموذج العرض التقديمي على الشاشة ، ولتقديم الحالة الحالية RoutingState التوجيه RoutingState ، يتم RoutedViewHost التحكم XAML RoutedViewHost المتضمن في حزمة Avalonia.ReactiveUI . نحن نطبق واجهة المستخدم الرسومية لـ SearchViewModel - لهذا ، في Views/ الدليل ، ننشئ ملفين: SearchView.xaml و SearchView.xaml.cs .


من المحتمل أن يبدو وصف واجهة المستخدم باستخدام لهجة XAML المستخدمة في Avalonia مألوفًا للمطورين في Windows Presentation Foundation أو Universal Windows Platform أو Xamarin.Forms. في المثال أعلاه ، نقوم بإنشاء واجهة تافهة لنموذج البحث - فنحن نرسم مربع نص لإدخال استعلام البحث وزر يبدأ البحث ، بينما نربط عناصر التحكم بخصائص نموذج SearchViewModel المحدد أعلاه.


المشاهدات / SearchView.xaml


 <UserControl xmlns="https://github.com/avaloniaui" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DataContext="{d:DesignInstance viewModels:SearchViewModel}" xmlns:viewModels="clr-namespace:ReactiveUI.Samples.Suspension.ViewModels" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ReactiveUI.Samples.Suspension.Views.SearchView" xmlns:reactiveUi="http://reactiveui.net" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="48" /> <RowDefinition Height="48" /> <RowDefinition Height="48" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="Search view" Margin="5" /> <TextBox Grid.Row="1" Text="{Binding SearchQuery, Mode=TwoWay}" /> <Button Grid.Row="2" Content="Search" Command="{Binding Search}" /> </Grid> </UserControl> 

المشاهدات / SearchView.xaml.cs


 public sealed class SearchView : ReactiveUserControl<SearchViewModel> { public SearchView() { //  WhenActivated     //        . this.WhenActivated((CompositeDisposable disposable) => { }); AvaloniaXamlLoader.Load(this); } } 

ستظهر أيضًا التعليمات البرمجية الخلفية SearchView.xaml التحكم SearchView.xaml WPF و UWP و XF المألوفين. يتم استخدام استدعاء WhenActivated لتنفيذ بعض التعليمات البرمجية عند تنشيط نموذج العرض أو العرض وإلغاء تنشيطه. إذا كان التطبيق الخاص بك يستخدم الملاحظات الساخنة (الموقتات ، والموقع الجغرافي ، والاتصال DisposeWith الرسالة) ، فسيكون من الحكمة إرفاقها بـ CompositeDisposable الاتصال بـ DisposeWith بحيث عندما تقوم DisposeWith عنصر تحكم XAML وطراز العرض المقابل له من الشجرة المرئية ، تتوقف الملاحظات الساخنة عن نشر قيم جديدة ولا توجد أي تسربات الذاكرة.


وبالمثل ، نحن ننفذ تمثيل صفحة التفويض

المشاهدات / LoginView.xaml


 <UserControl xmlns="https://github.com/avaloniaui" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DataContext="{d:DesignInstance viewModels:LoginViewModel, IsDesignTimeCreatable=True}" xmlns:viewModels="clr-namespace:ReactiveUI.Samples.Suspension.ViewModels" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ReactiveUI.Samples.Suspension.Views.LoginView" xmlns:reactiveUi="http://reactiveui.net" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="48" /> <RowDefinition Height="48" /> <RowDefinition Height="48" /> <RowDefinition Height="48" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="Login view" Margin="5" /> <TextBox Grid.Row="1" Text="{Binding Username, Mode=TwoWay}" /> <TextBox Grid.Row="2" PasswordChar="*" Text="{Binding Password, Mode=TwoWay}" /> <Button Grid.Row="3" Content="Login" Command="{Binding Login}" /> </Grid> </UserControl> 

المشاهدات / LoginView.xaml.cs


 public sealed class LoginView : ReactiveUserControl<LoginViewModel> { public LoginView() { this.WhenActivated(disposables => { }); AvaloniaXamlLoader.Load(this); } } 

قم بتحرير Views/MainView.xaml Views/MainView.xaml.cs . RoutedViewHost XAML RoutedViewHost من مساحة الاسم RoutedViewHost على الشاشة الرئيسية ، قم بتعيين حالة جهاز التوجيه RoutingState إلى خاصية RoutingState . أضف أزرارًا للانتقال إلى صفحات البحث والترخيص ، وقم ViewModels/MainViewModel بخصائص ViewModels/MainViewModel الموضحة أعلاه.


المشاهدات / MainView.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="ReactiveUI.Samples.Suspension.Views.MainView" xmlns:reactiveUi="http://reactiveui.net" Title="ReactiveUI.Samples.Suspension"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="48" /> </Grid.RowDefinitions> <!--  ,   RoutingState,   View  ViewModel --> <reactiveUi:RoutedViewHost Grid.Row="0" Router="{Binding Router}"> <reactiveUi:RoutedViewHost.DefaultContent> <TextBlock Text="Default Content" /> </reactiveUi:RoutedViewHost.DefaultContent> </reactiveUi:RoutedViewHost> <Grid Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Button Grid.Column="0" Command="{Binding Search}" Content="Search" /> <Button Grid.Column="1" Command="{Binding Login}" Content="Login" /> <Button Grid.Column="2" Command="{Binding Router.NavigateBack}" Content="Back" /> </Grid> </Grid> </Window> 

المشاهدات / MainView.xaml.cs


 public sealed class MainView : ReactiveWindow<MainViewModel> { public MainView() { this.WhenActivated(disposables => { }); AvaloniaXamlLoader.Load(this); } } 

تطبيق بسيط يوضح قدرات التوجيه ReactiveUI و Avalonia جاهز. عند النقر فوق زري Search وتسجيل Login ، يتم استدعاء الأوامر المقابلة ، ويتم إنشاء مثيل جديد لطراز العرض RoutingState تحديث RoutingState . RoutedViewHost XAML ، RoutedViewHost ، الذي يشترك في التغييرات التي تم إجراؤها على RoutingState ، الحصول على نوع IViewFor<TViewModel> ، حيث يكون TViewModel هو نوع طراز العرض ، من Locator.Current . إذا تم العثور على تطبيق مسجل لـ IViewFor<TViewModel> ، فسيتم إنشاء مثيل جديد مدمج في RoutedViewHost في نافذة تطبيق Avalonia.




نقوم بتسجيل المكونات الضرورية IViewFor<TViewModel> و IScreen في طريقة Locator.CurrentMutable باستخدام Locator.CurrentMutable . IViewFor<TViewModel> ضروريًا RoutedViewHost يعمل RoutedViewHost ، IScreen تسجيل IScreen ضروريًا حتى يمكن تهيئة LoginViewModel و LoginViewModel بشكل صحيح عند إلغاء التهيئة باستخدام المنشئ بدون معلمات و Locator.Current .


App.xaml.cs


 public override void OnFrameworkInitializationCompleted() { //   . Locator.CurrentMutable.RegisterConstant<IScreen>(new MainViewModel()); Locator.CurrentMutable.Register<IViewFor<SearchViewModel>>(() => new SearchView()); Locator.CurrentMutable.Register<IViewFor<LoginViewModel>>(() => new LoginView()); //        . new MainView { DataContext = Locator.Current.GetService<IScreen>() }.Show(); base.OnFrameworkInitializationCompleted(); } 

قم بتشغيل التطبيق وتأكد من أن التوجيه يعمل بشكل صحيح. إذا كان هناك أي أخطاء في علامة XAML ، فإن برنامج التحويل البرمجي XamlIl المستخدم في Avalonia سيخبرنا أين بالضبط ، في مرحلة التجميع. XamlIl كما يدعم تصحيح الأخطاء XAML مباشرة في المصحح IDE !


 dotnet run --framework netcoreapp3.0 



حفظ واستعادة حالة التطبيق بأكملها


الآن وبعد تكوين التوجيه وتشغيله ، يبدأ الجزء الممتع - تحتاج إلى تطبيق حفظ البيانات على القرص عند إغلاق التطبيق وقراءة البيانات من القرص عند بدء تشغيله ، بالإضافة إلى حالة جهاز التوجيه. تتم معالجة تهيئة الخطافات التي تستمع إلى بدء التطبيق وإغلاق الأحداث بواسطة فئة AutoSuspendHelper خاصة ، AutoSuspendHelper بكل نظام أساسي تدعمه ReactiveUI . مهمة المطور هي تهيئة هذه الفئة في بداية جذر تكوين التطبيق. من الضروري أيضًا تهيئة خاصية RxApp.SuspensionHost.CreateNewAppState وظيفة تُرجع الحالة الافتراضية للتطبيق في حالة عدم وجود حالة محفوظة أو حدث خطأ غير متوقع ، أو في حالة تلف الملف المحفوظ.


بعد ذلك ، تحتاج إلى استدعاء الأسلوب RxApp.SuspensionHost.SetupDefaultSuspendResume ، بتمرير تطبيق ISuspensionDriver - برنامج التشغيل الذي ISuspensionDriver ويقرأ كائن الحالة. لتطبيق ISuspensionDriver ، نستخدم مكتبة Newtonsoft.Json ومساحة الاسم System.IO للعمل مع نظام الملفات. للقيام بذلك ، قم بتثبيت حزمة Newtonsoft.Json :


 dotnet add package Newtonsoft.Json 

Drivers / NewtonsoftJsonSuspensionDriver.cs


 public class NewtonsoftJsonSuspensionDriver : ISuspensionDriver { private readonly string _file; private readonly JsonSerializerSettings _settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.All }; public NewtonsoftJsonSuspensionDriver(string file) => _file = file; public IObservable<Unit> InvalidateState() { if (File.Exists(_file)) File.Delete(_file); return Observable.Return(Unit.Default); } public IObservable<object> LoadState() { var lines = File.ReadAllText(_file); var state = JsonConvert.DeserializeObject<object>(lines, _settings); return Observable.Return(state); } public IObservable<Unit> SaveState(object state) { var lines = JsonConvert.SerializeObject(state, _settings); File.WriteAllText(_file, lines); return Observable.Return(Unit.Default); } } 

System.IO Universal Winows Platform, — File Directory StorageFile StorageFolder . , IRoutableViewModel , Newtonsoft.Json TypeNameHandling.All . Avalonia — App.OnFrameworkInitializationCompleted :


 public override void OnFrameworkInitializationCompleted() { //   . var suspension = new AutoSuspendHelper(ApplicationLifetime); RxApp.SuspensionHost.CreateNewAppState = () => new MainViewModel(); RxApp.SuspensionHost.SetupDefaultSuspendResume(new NewtonsoftJsonSuspensionDriver("appstate.json")); suspension.OnFrameworkInitializationCompleted(); //  ,      . var state = RxApp.SuspensionHost.GetAppState<MainViewModel>(); Locator.CurrentMutable.RegisterConstant<IScreen>(state); Locator.CurrentMutable.Register<IViewFor<SearchViewModel>>(() => new SearchView()); Locator.CurrentMutable.Register<IViewFor<LoginViewModel>>(() => new LoginView()); //  . new MainView { DataContext = Locator.Current.GetService<IScreen>() }.Show(); base.OnFrameworkInitializationCompleted(); } 

AutoSuspendHelper Avalonia.ReactiveUI IApplicationLifetime — , ISuspensionDriver . ISuspensionDriver appstate.json :


appstate.json

$type , , .


 { "$type": "ReactiveUI.Samples.Suspension.ViewModels.MainViewModel, ReactiveUI.Samples.Suspension", "Router": { "$type": "ReactiveUI.RoutingState, ReactiveUI", "_navigationStack": { "$type": "System.Collections.ObjectModel.ObservableCollection`1[[ReactiveUI.IRoutableViewModel, ReactiveUI]], System.ObjectModel", "$values": [ { "$type": "ReactiveUI.Samples.Suspension.ViewModels.SearchViewModel, ReactiveUI.Samples.Suspension", "SearchQuery": "funny cats" }, { "$type": "ReactiveUI.Samples.Suspension.ViewModels.LoginViewModel, ReactiveUI.Samples.Suspension", "Username": "worldbeater" } ] } } } 

, , , , , , ! , , ReactiveUI — UWP WPF, Xamarin.Forms.




: ISuspensionDriver AkavacheUserAccount Secure iOS UWP , , Android BundleSuspensionDriver ReactiveUI.AndroidSupport . JSON Xamarin.Essentials SecureStorage . , — !


روابط مفيدة


Source: https://habr.com/ru/post/ar457164/


All Articles