ReactiveUI और Avalonia के उदाहरण का उपयोग करके डिस्क को सहेजने की स्थिति के साथ एक क्रॉस-प्लेटफॉर्म .NET कोर एप्लीकेशन में नेविगेशन



आधुनिक अनुप्रयोगों के उपयोगकर्ता इंटरफ़ेस आमतौर पर जटिल होते हैं - यह अक्सर पृष्ठ नेविगेशन समर्थन को लागू करने, विभिन्न इनपुट क्षेत्रों को संसाधित करने और उपयोगकर्ता द्वारा चुने गए मापदंडों के आधार पर जानकारी को प्रदर्शित करने या छिपाने के लिए आवश्यक होता है। उसी समय, UX को बेहतर बनाने के लिए, एप्लिकेशन को निलंबन या शटडाउन के दौरान इंटरफ़ेस तत्वों की स्थिति को डिस्क पर सहेजना होगा, प्रोग्राम को पुनरारंभ होने पर डिस्क से राज्य को पुनर्स्थापित करें।


रिएक्टिविवई एमवीवीएम फ्रेमवर्क एक कार्यक्रम की स्थिति को प्रस्तुत करने के लिए प्रस्तुति मॉडल के ग्राफ को क्रमबद्ध करके एक आवेदन की स्थिति को संरक्षित करने का प्रस्ताव रखता है, जबकि निलंबन के क्षण को निर्धारित करने के तंत्र चौखटे और प्लेटफार्मों के लिए अलग हैं। तो, WPF के लिए, Exit इवेंट का उपयोग किया जाता है, Xamarin.Android के लिए - ActivityPaused DidEnterBackground , Xamarin.iOS के लिए - DidEnterBackground , UWP के लिए - OnLaunched अधिभार।


इस लेख में, हम एक उदाहरण के रूप में एवलोनिया क्रॉस-प्लेटफ़ॉर्म जीयूआई फ्रेमवर्क का उपयोग करके एक राउटर की स्थिति सहित जीयूआई के साथ सॉफ्टवेयर की स्थिति को बचाने और पुनर्स्थापित करने के लिए रिएक्टिवयूआई के उपयोग पर विचार करेंगे। सामग्री MVVM डिजाइन पैटर्न और प्रतिक्रियाशील प्रोग्रामिंग की मूल समझ को C # भाषा और रीडर के लिए .NET प्लेटफ़ॉर्म के रूप में मानती है। इस आलेख के चरण Windows 10 और Ubuntu 18 पर लागू होते हैं।


परियोजना निर्माण


कार्रवाई में रूटिंग का प्रयास करने के लिए, Avalonia टेम्पलेट से एक नया .NET कोर प्रोजेक्ट बनाएं, Avalonia.ReactiveUI पैकेज - Avalonia और ReactiveUI एकीकरण की एक पतली परत स्थापित करें। सुनिश्चित करें कि आपके पास शुरू होने से पहले .NET कोर 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 को MyGet से प्री-बिल्ड कनेक्ट करें


कनेक्ट करने के लिए और नवीनतम एवलोनिया बिल्ड का उपयोग करने के लिए जो स्वचालित रूप से MyGet पर प्रकाशित होते हैं जब GitHub में एवलोनिया रिपॉजिटरी master शाखा बदलती है, हम nuget.config पैकेज स्रोत कॉन्फ़िगरेशन फ़ाइल का उपयोग करते हैं। IDE और .NET कोर CLI के लिए nuget.config देखने के लिए , आपको ऊपर बनाई गई परियोजना के लिए एक sln फ़ाइल जनरेट करनी होगी। हम .NET कोर CLI के टूल का उपयोग करते हैं:


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

निम्नलिखित सामग्री की .sln फ़ाइल के साथ एक फ़ोल्डर में एक nuget.config फ़ाइल बनाएँ:


 <?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 को पुनरारंभ करने, या अनलोड करने और संपूर्ण समाधान डाउनलोड करने की आवश्यकता हो सकती है। हम आपके IDE के NuGet पैकेज प्रबंधक इंटरफ़ेस का उपयोग करके या विंडोज कमांड लाइन टूल या लिनक्स टर्मिनल का उपयोग करके आवश्यक संस्करण (कम से कम 0.9.1 ) में एवलोनिया पैकेज को अपडेट करेंगे:


 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> 

प्रोजेक्ट रूट में Views/ और ViewModels/ फ़ोल्डर बनाएँ, सुविधा के लिए MainWindow वर्ग का नाम MainView , इसे Views/ निर्देशिका में ले जाएं, ReactiveUI.Samples.Suspension.Views अनुसार नामस्थान बदल रहे हैं। Program.cs और App.xaml.cs की सामग्री को संपादित करें - Avalonia एप्लिकेशन बिल्डर को UseReactiveUI कॉल लागू करें, अनुप्रयोग जीवनचक्र प्रबंधन अनुशंसाओं का पालन करने के लिए, 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 का 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 



क्रॉस-प्लेटफ़ॉर्म रूटिंग रिएक्टिवुई


एक नियम के रूप में, .NET अनुप्रयोग के पृष्ठों के बीच नेविगेशन को लागू करने के लिए दो मुख्य दृष्टिकोण हैं - दृश्य-प्रथम और दृश्य मॉडल-प्रथम। व्यू-प्रथम दृष्टिकोण में MVVM शब्दावली में व्यू स्तर पर पृष्ठों के बीच नेविगेशन स्टैक और नेविगेशन को नियंत्रित करना शामिल है - उदाहरण के लिए, UWP या WPF के मामले में फ़्रेम और पेज कक्षाओं का उपयोग करना, और व्यू मॉडल-प्रथम दृष्टिकोण का उपयोग करते समय, नेविगेशन को प्रस्तुति मॉडल के स्तर पर लागू किया जाता है। ReactiveUI टूल जो एप्लिकेशन में रूटिंग को व्यवस्थित करते हैं, वे दृश्य मॉडल-प्रथम दृष्टिकोण का उपयोग करने पर केंद्रित हैं। ReactiveUI रूटिंग में एक IScreen कार्यान्वयन होता है जिसमें राउटर की IRoutableViewModel कई कार्यान्वयन और प्लेटफ़ॉर्म- RoutedViewHost XAML नियंत्रण, RoutedViewHost




राउटर की स्थिति को RoutingState ऑब्जेक्ट द्वारा दर्शाया जाता है जो नेविगेशन स्टैक को नियंत्रित करता है। IScreen नेविगेशन स्टैक की जड़ है, और एप्लिकेशन में कई नेविगेशन रूट हो सकते हैं। RoutedViewHost XAML नियंत्रण के संबंधित IRoutableViewModel को एम्बेड करके नेविगेशन स्टैक में परिवर्तन का जवाब देते हुए, इसके संबंधित RoutingState राउटर की स्थिति की निगरानी करता है। वर्णित कार्यक्षमता नीचे दिए गए उदाहरणों द्वारा चित्रित की जाएगी।


डिस्क पर दृश्य मॉडल की स्थिति को सहेजना


सूचना खोज स्क्रीन के एक विशिष्ट उदाहरण पर विचार करें।




हमें यह निर्धारित करना चाहिए कि एप्लिकेशन के निलंबन या शटडाउन के दौरान डिस्क को सहेजने के लिए स्क्रीन प्रतिनिधित्व मॉडल के कौन से तत्व हैं, और जो शुरू होने पर प्रत्येक बार पुनः बनाने के लिए -। 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 इंटरफ़ेस लागू करते हैं ताकि बाद में यह एप्लिकेशन राउटर के नेविगेशन फ्रेम का हिस्सा बन सके।


इसी तरह, हम प्राधिकरण पृष्ठ प्रस्तुति मॉडल को लागू करते हैं

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 इंटरफ़ेस को लागू करें और इसे 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 लागू करने वाले प्रकारों के बारे में विस्तारित जानकारी शामिल होनी चाहिए ताकि नेविगेशन स्टैक को IRoutableViewModel पर पुनर्स्थापित किया जा सके। हम MainViewModel व्यू MainViewModel के तर्क का वर्णन करते हैं, क्लास को ViewModels/MainViewModel.cs और संबंधित ReactiveUI.Samples.Suspension.ViewModels नाम स्थान में रखें।




Avalonia ऐप में रूटिंग


मॉडल के लेयर लेवल पर यूजर इंटरफेस लॉजिक और डेमो एप्लिकेशन के प्रेजेंटेशन मॉडल को कार्यान्वित किया जाता है और इसे .NET मानक के उद्देश्य से एक अलग असेंबली में ले जाया जा सकता है, क्योंकि यह GUI फ्रेमवर्क के बारे में कुछ भी नहीं जानता है। आइए प्रेजेंटेशन लेयर पर एक नजर डालते हैं। MVVM शब्दावली में, प्रस्तुति परत स्क्रीन पर प्रस्तुति मॉडल की स्थिति प्रदान करने के लिए जिम्मेदार है। RoutingState राउटर की वर्तमान स्थिति को प्रस्तुत करने के लिए, Avalonia.ReactiveUI पैकेज में निहित XAML RoutedViewHost नियंत्रण का Avalonia.ReactiveUI । हम SearchViewModel लिए GUI लागू करते हैं - इसके लिए, Views/ निर्देशिका में, दो फ़ाइलें बनाएँ: SearchView.xaml और SearchView.xaml.cs


एवलोनिया में उपयोग की जाने वाली XAML बोली का उपयोग करने वाले उपयोगकर्ता इंटरफ़ेस का विवरण डेवलपर्स के लिए विंडोज प्रेजेंटेशन फाउंडेशन, यूनिवर्सल विंडोज प्लेटफॉर्म या 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 SearchView.xaml को भी दिखाई देगा। जब किसी दृश्य या प्रस्तुति मॉडल को सक्रिय और निष्क्रिय करने के समय कुछ कोड को निष्पादित करने के लिए व्हॉट्सएप किए गए कॉल का उपयोग किया जाता है। यदि आपका एप्लिकेशन हॉट ऑब्ज़र्वबल्स (टाइमर, जियोलोकेशन, मैसेज बस से कनेक्शन) का उपयोग करता है, तो DisposeWith CompositeDisposable कॉल CompositeDisposable उन्हें DisposeWith संलग्न करना बुद्धिमान होगा ताकि जब आप DisposeWith कंट्रोल और इसके संबंधित व्यू मॉडल को DisposeWith कर DisposeWith तो DisposeWith नए मान प्रकाशित करना बंद कर दें और कोई लीक न हो। स्मृति।


इसी तरह, हम प्राधिकरण पृष्ठ के प्रतिनिधित्व को लागू करते हैं।

दृश्य / 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 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 अपडेट किया जाता है। XAML नियंत्रण RoutedViewHost , जो RoutedViewHost में परिवर्तन की सदस्यता RoutingState , IViewFor<TViewModel> प्राप्त करने की कोशिश करता है, जहां TViewModel से व्यू मॉडल प्रकार है। यदि IViewFor<TViewModel> एक पंजीकृत कार्यान्वयन पाया IViewFor<TViewModel> है, तो एक नया उदाहरण बनाया जाएगा, RoutedViewHost में बनाया गया और Avalonia एप्लिकेशन विंडो में प्रदर्शित किया जाएगा।




हम आवश्यक घटकों को रजिस्टर करते हैं IViewFor<TViewModel> और IScreen को App.OnFrameworkInitializationCompleted विधि में Locator.CurrentMutable का उपयोग करके पंजीकृत करते हैं। IViewFor<TViewModel> RoutedViewHost काम RoutedViewHost लिए आवश्यक RoutedViewHost , और IScreen पंजीकरण आवश्यक है ताकि SearchViewModel , SearchViewModel और LoginViewModel को SearchViewModel और LoginViewModel बिना कंस्ट्रक्टर का उपयोग करके सही ढंग से आरंभ किया जा सके।


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 संकलक हमें बताएगा कि संकलन के स्तर पर कहां है। XamlIl IDE डिबगर में XAML डिबगिंग का भी समर्थन करता है !


 dotnet run --framework netcoreapp3.0 



संपूर्ण एप्लिकेशन स्थिति को सहेजना और पुनर्स्थापित करना


अब जब राउटिंग कॉन्फ़िगर किया गया है और काम कर रहा है, तो सबसे दिलचस्प हिस्सा शुरू होता है - आपको राउटर की स्थिति के साथ, डिस्क को सहेजने और डिस्क से डेटा को पढ़ने पर डिस्क को सहेजने के डेटा को लागू करने की आवश्यकता होती है। हुक की शुरूआत जो अनुप्रयोग प्रारंभ और करीबी घटनाओं को AutoSuspendHelper , एक विशेष AutoSuspendHelper वर्ग द्वारा संभाला जाता है, जो प्रत्येक प्लेटफॉर्म के लिए AutoSuspendHelper है जो रिएक्टिवुई का समर्थन करता है। डेवलपर का कार्य इस वर्ग को आवेदन की संरचना के मूल में बहुत पहले से शुरू करना है। RxApp.SuspensionHost.CreateNewAppState फ़ंक्शन के RxApp.SuspensionHost.CreateNewAppState को प्रारंभ करने के लिए भी आवश्यक है जो अनुप्रयोग की डिफ़ॉल्ट स्थिति को वापस कर देगा यदि कोई सहेजे गए राज्य नहीं है या एक अप्रत्याशित त्रुटि हुई है, या यदि सहेजी गई फ़ाइल क्षतिग्रस्त है।


इसके बाद, आपको RxApp.SuspensionHost.SetupDefaultSuspendResume विधि को कॉल करने की आवश्यकता है, इसे ISuspensionDriver कार्यान्वयन से ISuspensionDriver , ड्राइवर जो राज्य ऑब्जेक्ट ISuspensionDriver और पढ़ता है। ISuspensionDriver को लागू करने के लिए, ISuspensionDriver फाइल सिस्टम के साथ काम करने के लिए Newtonsoft.Json लाइब्रेरी और System.IO नामस्थान का उपयोग करते हैं। ऐसा करने के लिए, Newtonsoft.Json पैकेज स्थापित करें:


 dotnet add package Newtonsoft.Json 

ड्राइवर / 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/hi457164/


All Articles