Explorar o código

Update ReactiveUI Example

Chris Pulman hai 1 ano
pai
achega
a225b421c8

+ 0 - 3
ReactiveExample/FodyWeavers.xml

@@ -1,3 +0,0 @@
-<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
-  <ReactiveUI />
-</Weavers>

+ 0 - 26
ReactiveExample/FodyWeavers.xsd

@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
-  <!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
-  <xs:element name="Weavers">
-    <xs:complexType>
-      <xs:all>
-        <xs:element name="ReactiveUI" minOccurs="0" maxOccurs="1" type="xs:anyType" />
-      </xs:all>
-      <xs:attribute name="VerifyAssembly" type="xs:boolean">
-        <xs:annotation>
-          <xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
-        </xs:annotation>
-      </xs:attribute>
-      <xs:attribute name="VerifyIgnoreCodes" type="xs:string">
-        <xs:annotation>
-          <xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
-        </xs:annotation>
-      </xs:attribute>
-      <xs:attribute name="GenerateXsd" type="xs:boolean">
-        <xs:annotation>
-          <xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
-        </xs:annotation>
-      </xs:attribute>
-    </xs:complexType>
-  </xs:element>
-</xs:schema>

+ 126 - 174
ReactiveExample/LoginView.cs

@@ -8,20 +8,137 @@ namespace ReactiveExample;
 
 
 public class LoginView : Window, IViewFor<LoginViewModel>
 public class LoginView : Window, IViewFor<LoginViewModel>
 {
 {
-    private readonly CompositeDisposable _disposable = new ();
+    private const string SuccessMessage = "The input is valid!";
+    private const string ErrorMessage = "Please enter a valid user name and password.";
+    private const string ProgressMessage = "Logging in...";
+    private const string IdleMessage = "Press 'Login' to log in.";
+
+    private readonly CompositeDisposable _disposable = [];
 
 
     public LoginView (LoginViewModel viewModel)
     public LoginView (LoginViewModel viewModel)
     {
     {
         Title = $"Reactive Extensions Example - {Application.QuitKey} to Exit";
         Title = $"Reactive Extensions Example - {Application.QuitKey} to Exit";
         ViewModel = viewModel;
         ViewModel = viewModel;
-        Label usernameLengthLabel = UsernameLengthLabel (TitleLabel ());
-        TextField usernameInput = UsernameInput (usernameLengthLabel);
-        Label passwordLengthLabel = PasswordLengthLabel (usernameLengthLabel);
-        TextField passwordInput = PasswordInput (passwordLengthLabel);
-        Label validationLabel = ValidationLabel (passwordInput);
-        Button loginButton = LoginButton (validationLabel);
-        Button clearButton = ClearButton (loginButton);
-        LoginProgressLabel (clearButton);
+        var title = this.AddControl<Label> (x => x.Text = "Login Form");
+        var unLengthLabel = title.AddControlAfter<Label> ((previous, unLength) =>
+            {
+                unLength.X = Pos.Left (previous);
+                unLength.Y = Pos.Top (previous) + 1;
+
+                ViewModel
+                    .WhenAnyValue (x => x.UsernameLength)
+                    .Select (length => $"_Username ({length} characters):")
+                    .BindTo (unLength, x => x.Text)
+                    .DisposeWith (_disposable);
+            });
+        unLengthLabel.AddControlAfter<TextField> ((previous, unInput) =>
+            {
+                unInput.X = Pos.Right (previous) + 1;
+                unInput.Y = Pos.Top (previous);
+                unInput.Width = 40;
+                unInput.Text = ViewModel.Username;
+
+                ViewModel
+                    .WhenAnyValue (x => x.Username)
+                    .BindTo (unInput, x => x.Text)
+                    .DisposeWith (_disposable);
+
+                unInput
+                    .Events ()
+                    .TextChanged
+                    .Select (_ => unInput.Text)
+                    .DistinctUntilChanged ()
+                    .BindTo (ViewModel, x => x.Username)
+                    .DisposeWith (_disposable);
+            });
+        unLengthLabel.AddControlAfter<Label> ((previous, pwLength) =>
+            {
+                pwLength.X = Pos.Left (previous);
+                pwLength.Y = Pos.Top (previous) + 1;
+
+                ViewModel
+                    .WhenAnyValue (x => x.PasswordLength)
+                    .Select (length => $"_Password ({length} characters):")
+                    .BindTo (pwLength, x => x.Text)
+                    .DisposeWith (_disposable);
+            })
+            .AddControlAfter<TextField> ((previous, pwInput) =>
+            {
+                pwInput.X = Pos.Right (previous) + 1; 
+                pwInput.Y = Pos.Top (previous); 
+                pwInput.Width = 40;
+                pwInput.Text = ViewModel.Password;
+
+                ViewModel
+                    .WhenAnyValue (x => x.Password)
+                    .BindTo (pwInput, x => x.Text)
+                    .DisposeWith (_disposable);
+
+                pwInput
+                    .Events ()
+                    .TextChanged
+                    .Select (_ => pwInput.Text)
+                    .DistinctUntilChanged ()
+                    .BindTo (ViewModel, x => x.Password)
+                    .DisposeWith (_disposable);
+            })
+            .AddControlAfter<Label> ((previous, validation) =>
+            {
+                validation.X = Pos.Left (previous);
+                validation.Y = Pos.Top (previous) + 1;
+                validation.Text = ErrorMessage;
+
+                ViewModel
+                    .WhenAnyValue (x => x.IsValid)
+                    .Select (valid => valid ? SuccessMessage : ErrorMessage)
+                    .BindTo (validation, x => x.Text)
+                    .DisposeWith (_disposable);
+
+                ViewModel
+                    .WhenAnyValue (x => x.IsValid)
+                    .Select (valid => valid ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"])
+                    .BindTo (validation, x => x.ColorScheme)
+                    .DisposeWith (_disposable);
+            })
+            .AddControlAfter<Button> ((previous, login) =>
+            {
+                login.X = Pos.Left (previous);
+                login.Y = Pos.Top (previous) + 1;
+                login.Text = "_Login";
+
+                login
+                    .Events ()
+                    .Accept
+                    .InvokeCommand (ViewModel, x => x.Login)
+                    .DisposeWith (_disposable);
+            })
+            .AddControlAfter<Button> ((previous, clear) =>
+            {
+                clear.X = Pos.Left (previous);
+                clear.Y = Pos.Top (previous) + 1;
+                clear.Text = "_Clear";
+
+                clear
+                    .Events ()
+                    .Accept
+                    .InvokeCommand (ViewModel, x => x.ClearCommand)
+                    .DisposeWith (_disposable);
+            })
+            .AddControlAfter<Label> ((previous, progress) =>
+            {
+                progress.X = Pos.Left (previous);
+                progress.Y = Pos.Top (previous) + 1;
+                progress.Width = 40;
+                progress.Height = 1;
+                progress.Text = IdleMessage;
+
+                ViewModel
+                    .WhenAnyObservable (x => x.Login.IsExecuting)
+                    .Select (executing => executing ? ProgressMessage : IdleMessage)
+                    .ObserveOn (RxApp.MainThreadScheduler)
+                    .BindTo (progress, x => x.Text)
+                    .DisposeWith (_disposable);
+            });
     }
     }
 
 
     public LoginViewModel ViewModel { get; set; }
     public LoginViewModel ViewModel { get; set; }
@@ -37,169 +154,4 @@ public class LoginView : Window, IViewFor<LoginViewModel>
         _disposable.Dispose ();
         _disposable.Dispose ();
         base.Dispose (disposing);
         base.Dispose (disposing);
     }
     }
-
-    private Button ClearButton (View previous)
-    {
-        var clearButton = new Button
-        {
-            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Text = "_Clear"
-        };
-
-        clearButton
-            .Events ()
-            .Accept
-            .InvokeCommand (ViewModel, x => x.Clear)
-            .DisposeWith (_disposable);
-        Add (clearButton);
-
-        return clearButton;
-    }
-
-    private Button LoginButton (View previous)
-    {
-        var loginButton = new Button
-        {
-            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Text = "_Login"
-        };
-
-        loginButton
-            .Events ()
-            .Accept
-            .InvokeCommand (ViewModel, x => x.Login)
-            .DisposeWith (_disposable);
-        Add (loginButton);
-
-        return loginButton;
-    }
-
-    private Label LoginProgressLabel (View previous)
-    {
-        var progress = "Logging in...";
-        var idle = "Press 'Login' to log in.";
-
-        var loginProgressLabel = new Label
-        {
-            X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Width = 40, Height = 1, Text = idle
-        };
-
-        ViewModel
-            .WhenAnyObservable (x => x.Login.IsExecuting)
-            .Select (executing => executing ? progress : idle)
-            .ObserveOn (RxApp.MainThreadScheduler)
-            .BindTo (loginProgressLabel, x => x.Text)
-            .DisposeWith (_disposable);
-        Add (loginProgressLabel);
-
-        return loginProgressLabel;
-    }
-
-    private TextField PasswordInput (View previous)
-    {
-        var passwordInput = new TextField
-        {
-            X = Pos.Right (previous) + 1, Y = Pos.Top (previous), Width = 40, Text = ViewModel.Password
-        };
-
-        ViewModel
-            .WhenAnyValue (x => x.Password)
-            .BindTo (passwordInput, x => x.Text)
-            .DisposeWith (_disposable);
-
-        passwordInput
-            .Events ()
-            .TextChanged
-            .Select (old => passwordInput.Text)
-            .DistinctUntilChanged ()
-            .BindTo (ViewModel, x => x.Password)
-            .DisposeWith (_disposable);
-        Add (passwordInput);
-
-        return passwordInput;
-    }
-
-    private Label PasswordLengthLabel (View previous)
-    {
-        var passwordLengthLabel = new Label { X = Pos.Left (previous), Y = Pos.Top (previous) + 1, };
-
-        ViewModel
-            .WhenAnyValue (x => x.PasswordLength)
-            .Select (length => $"_Password ({length} characters):")
-            .BindTo (passwordLengthLabel, x => x.Text)
-            .DisposeWith (_disposable);
-        Add (passwordLengthLabel);
-
-        return passwordLengthLabel;
-    }
-
-    private Label TitleLabel ()
-    {
-        var label = new Label { Text = "Login Form" };
-        Add (label);
-
-        return label;
-    }
-
-    private TextField UsernameInput (View previous)
-    {
-        var usernameInput = new TextField
-        {
-            X = Pos.Right (previous) + 1, Y = Pos.Top (previous), Width = 40, Text = ViewModel.Username
-        };
-
-        ViewModel
-            .WhenAnyValue (x => x.Username)
-            .BindTo (usernameInput, x => x.Text)
-            .DisposeWith (_disposable);
-
-        usernameInput
-            .Events ()
-            .TextChanged
-            .Select (old => usernameInput.Text)
-            .DistinctUntilChanged ()
-            .BindTo (ViewModel, x => x.Username)
-            .DisposeWith (_disposable);
-        Add (usernameInput);
-
-        return usernameInput;
-    }
-
-    private Label UsernameLengthLabel (View previous)
-    {
-        var usernameLengthLabel = new Label { X = Pos.Left (previous), Y = Pos.Top (previous) + 1 };
-
-        ViewModel
-            .WhenAnyValue (x => x.UsernameLength)
-            .Select (length => $"_Username ({length} characters):")
-            .BindTo (usernameLengthLabel, x => x.Text)
-            .DisposeWith (_disposable);
-        Add (usernameLengthLabel);
-
-        return usernameLengthLabel;
-    }
-
-    private Label ValidationLabel (View previous)
-    {
-        var error = "Please enter a valid user name and password.";
-        var success = "The input is valid!";
-
-        var validationLabel = new Label
-        {
-           X = Pos.Left (previous), Y = Pos.Top (previous) + 1, Text = error
-        };
-
-        ViewModel
-            .WhenAnyValue (x => x.IsValid)
-            .Select (valid => valid ? success : error)
-            .BindTo (validationLabel, x => x.Text)
-            .DisposeWith (_disposable);
-
-        ViewModel
-            .WhenAnyValue (x => x.IsValid)
-            .Select (valid => valid ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"])
-            .BindTo (validationLabel, x => x.ColorScheme)
-            .DisposeWith (_disposable);
-        Add (validationLabel);
-
-        return validationLabel;
-    }
 }
 }

+ 38 - 43
ReactiveExample/LoginViewModel.cs

@@ -5,7 +5,7 @@ using System.Reactive.Linq;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using ReactiveUI;
 using ReactiveUI;
-using ReactiveUI.Fody.Helpers;
+using ReactiveUI.SourceGenerators;
 
 
 namespace ReactiveExample;
 namespace ReactiveExample;
 
 
@@ -21,41 +21,53 @@ namespace ReactiveExample;
 // See also: https://www.reactiveui.net../docs/handbook/data-persistence/
 // See also: https://www.reactiveui.net../docs/handbook/data-persistence/
 //
 //
 [DataContract]
 [DataContract]
-public class LoginViewModel : ReactiveObject
+public partial class LoginViewModel : ReactiveObject
 {
 {
-    private readonly ObservableAsPropertyHelper<bool> _isValid;
-    private readonly ObservableAsPropertyHelper<int> _passwordLength;
-    private readonly ObservableAsPropertyHelper<int> _usernameLength;
+    [IgnoreDataMember]
+    [ObservableAsProperty] private bool _isValid;
 
 
-    public LoginViewModel ()
-    {
-        IObservable<bool> canLogin = this.WhenAnyValue (
-                                                        x => x.Username,
-                                                        x => x.Password,
-                                                        (username, password) =>
-                                                            !string.IsNullOrEmpty (username) && !string.IsNullOrEmpty (password)
-                                                       );
+    [IgnoreDataMember]
+    [ObservableAsProperty] private int _passwordLength;
 
 
-        _isValid = canLogin.ToProperty (this, x => x.IsValid);
+    [IgnoreDataMember]
+    [ObservableAsProperty] private int _usernameLength;
 
 
-        Login = ReactiveCommand.CreateFromTask<CancelEventArgs> (
-                                                                 e => Task.Delay (TimeSpan.FromSeconds (1)),
-                                                                 canLogin
-                                                                );
+    [DataMember]
+    [Reactive] private string _password = string.Empty;
 
 
-        _usernameLength = this
+    [DataMember]
+    [Reactive] private string _username = string.Empty;
+
+    public LoginViewModel ()
+    {
+        InitializeCommands ();
+        IObservable<bool> canLogin = this.WhenAnyValue 
+            (
+                x => x.Username,
+                x => x.Password,
+                (username, password) =>
+                    !string.IsNullOrEmpty (username) && !string.IsNullOrEmpty (password)
+            );
+
+        _isValidHelper = canLogin.ToProperty (this, x => x.IsValid);
+
+        Login = ReactiveCommand.CreateFromTask<HandledEventArgs>
+            (
+                e => Task.Delay (TimeSpan.FromSeconds (1)),
+                canLogin
+            );
+
+        _usernameLengthHelper = this
                           .WhenAnyValue (x => x.Username)
                           .WhenAnyValue (x => x.Username)
                           .Select (name => name.Length)
                           .Select (name => name.Length)
                           .ToProperty (this, x => x.UsernameLength);
                           .ToProperty (this, x => x.UsernameLength);
 
 
-        _passwordLength = this
+        _passwordLengthHelper = this
                           .WhenAnyValue (x => x.Password)
                           .WhenAnyValue (x => x.Password)
                           .Select (password => password.Length)
                           .Select (password => password.Length)
                           .ToProperty (this, x => x.PasswordLength);
                           .ToProperty (this, x => x.PasswordLength);
 
 
-        Clear = ReactiveCommand.Create<CancelEventArgs> (e => { });
-
-        Clear.Subscribe (
+        ClearCommand.Subscribe (
                          unit =>
                          unit =>
                          {
                          {
                              Username = string.Empty;
                              Username = string.Empty;
@@ -64,26 +76,9 @@ public class LoginViewModel : ReactiveObject
                         );
                         );
     }
     }
 
 
-    [IgnoreDataMember]
-    public ReactiveCommand<CancelEventArgs, Unit> Clear { get; }
-
-    [IgnoreDataMember]
-    public bool IsValid => _isValid.Value;
-
-    [IgnoreDataMember]
-    public ReactiveCommand<CancelEventArgs, Unit> Login { get; }
-
-    [Reactive]
-    [DataMember]
-    public string Password { get; set; } = string.Empty;
-
-    [IgnoreDataMember]
-    public int PasswordLength => _passwordLength.Value;
-
-    [Reactive]
-    [DataMember]
-    public string Username { get; set; } = string.Empty;
+    [ReactiveCommand]
+    public void Clear (HandledEventArgs args) { }
 
 
     [IgnoreDataMember]
     [IgnoreDataMember]
-    public int UsernameLength => _usernameLength.Value;
+    public ReactiveCommand<HandledEventArgs, Unit> Login { get; }
 }
 }

+ 1 - 1
ReactiveExample/README.md

@@ -1,4 +1,4 @@
-This is a sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [Pharmacist](https://github.com/reactiveui/pharmacist) — a tool that converts all events in a NuGet package into observable wrappers.
+This is a sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [ObservableEvents](https://github.com/reactivemarbles/ObservableEvents) — a Source Generator that turns events into observable wrappers.
 
 
 <img src="https://user-images.githubusercontent.com/6759207/94748621-646a7280-038a-11eb-8ea0-34629dc799b3.gif" width="450">
 <img src="https://user-images.githubusercontent.com/6759207/94748621-646a7280-038a-11eb-8ea0-34629dc799b3.gif" width="450">
 
 

+ 1 - 1
ReactiveExample/ReactiveExample.csproj

@@ -11,9 +11,9 @@
     <InformationalVersion>2.0</InformationalVersion>
     <InformationalVersion>2.0</InformationalVersion>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="ReactiveUI.Fody" Version="[19.5.41,21)" />
     <PackageReference Include="ReactiveUI" Version="[20.1.1,21)" />
     <PackageReference Include="ReactiveUI" Version="[20.1.1,21)" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="[1.3.1,2)" PrivateAssets="all" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="[1.3.1,2)" PrivateAssets="all" />
+    <PackageReference Include="ReactiveUI.SourceGenerators" Version="[1.0.3,2)" PrivateAssets="all" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />

+ 24 - 0
ReactiveExample/ViewExtensions.cs

@@ -0,0 +1,24 @@
+using System;
+using Terminal.Gui;
+
+namespace ReactiveExample;
+public static class ViewExtensions
+{
+    public static (Window MainView, TOut LastControl) AddControl<TOut> (this Window view, Action<TOut> action)
+        where TOut : View, new()
+    {
+        TOut result = new ();
+        action (result);
+        view.Add (result);
+        return (view, result);
+    }
+
+    public static (Window MainView, TOut LastControl) AddControlAfter<TOut> (this (Window MainView, View LastControl) view, Action<View, TOut> action)
+        where TOut : View, new()
+    {
+        TOut result = new ();
+        action (view.LastControl, result);
+        view.MainView.Add (result);
+        return (view.MainView, result);
+    }
+}