Browse Source

added recommendations; fixed .sln

John Baughman 1 year ago
parent
commit
dea0e5696f

+ 13 - 16
CommunityToolkitExample/LoginViewModel.cs

@@ -8,13 +8,15 @@ namespace CommunityToolkitExample;
 internal partial class LoginViewModel : ObservableObject
 {
     private const string DEFAULT_LOGIN_PROGRESS_MESSAGE = "Press 'Login' to log in.";
+    private const string INVALID_LOGIN_MESSAGE = "Please enter a valid user name and password.";
     private const string LOGGING_IN_PROGRESS_MESSAGE = "Logging in...";
     private const string VALID_LOGIN_MESSAGE = "The input is valid!";
-    private const string INVALID_LOGIN_MESSAGE = "Please enter a valid user name and password.";
-
     [ObservableProperty]
     private bool _canLogin;
 
+    [ObservableProperty]
+    private string _loginProgressMessage;
+
     private string _password;
 
     [ObservableProperty]
@@ -24,16 +26,11 @@ internal partial class LoginViewModel : ObservableObject
 
     [ObservableProperty]
     private string _usernameLengthMessage;
-
     [ObservableProperty]
-    private string _loginProgressMessage;
+    private ColorScheme? _validationColorScheme;
 
     [ObservableProperty]
     private string _validationMessage;
-
-    [ObservableProperty]
-    private ColorScheme? _validationColorScheme;
-
     public LoginViewModel ()
     {
         Username = string.Empty;
@@ -64,12 +61,6 @@ internal partial class LoginViewModel : ObservableObject
         }
     }
 
-    private void ValidateLogin ()
-    {
-        CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password);
-        SendMessage (LoginAction.Validation);
-    }
-
     public string Username
     {
         get => _username;
@@ -81,6 +72,11 @@ internal partial class LoginViewModel : ObservableObject
         }
     }
 
+    public void Initialized ()
+    {
+        Clear ();
+    }
+
     private void Clear ()
     {
         Username = string.Empty;
@@ -111,8 +107,9 @@ internal partial class LoginViewModel : ObservableObject
         WeakReferenceMessenger.Default.Send (new Message<LoginAction> { Value = loginAction });
     }
 
-    public void Initialized ()
+    private void ValidateLogin ()
     {
-        Clear ();
+        CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password);
+        SendMessage (LoginAction.Validation);
     }
 }

+ 1 - 0
CommunityToolkitExample/Program.cs

@@ -12,6 +12,7 @@ public static class Program
         Services = ConfigureServices ();
         Application.Init ();
         Application.Run (Services.GetRequiredService<LoginView> ());
+        Application.Top.Dispose();
         Application.Shutdown ();
     }
 

+ 180 - 0
CommunityToolkitExample/README.md

@@ -0,0 +1,180 @@
+# CommunityToolkit.MVVM Example
+
+This small demo gives an example of using the `CommunityToolkit.MVVM` framework's `ObservableObject`, `ObservableProperty`, and `IRecipient<T>` in conjunction with `Microsoft.Extensions.DependencyInjection`. 
+
+### Startup
+
+Right away we use IoC to load our views and view models.
+
+``` csharp
+// As a public property for access further in the application if needed. 
+public static IServiceProvider Services { get; private set; }
+
+. . .
+
+// In Main
+Services = ConfigureServices ();
+
+. . .
+
+private static IServiceProvider ConfigureServices ()
+{
+    var services = new ServiceCollection ();
+    services.AddTransient<LoginView> ();
+    services.AddTransient<LoginViewModel> ();
+    return services.BuildServiceProvider ();
+}
+```
+
+Now, we start the app and get our main view.
+
+``` csharp
+Application.Run (Services.GetRequiredService<LoginView> ());
+```
+
+Our view implements `IRecipient<T>` to demonstrate the use of the `WeakReferenceMessenger`. The binding of the view events is then created.
+
+``` csharp
+internal partial class LoginView : IRecipient<Message<LoginAction>>
+{
+    public LoginView (LoginViewModel viewModel)
+    {
+        // Initialize our Receive method
+        WeakReferenceMessenger.Default.Register (this);
+        
+        ...
+
+        ViewModel = viewModel;
+       
+        ...
+
+        passwordInput.TextChanged += (_, _) =>
+                                     {
+                                         ViewModel.Password = passwordInput.Text;
+                                         SetText ();
+                                     };
+        loginButton.Accept += (_, _) =>
+                              {
+                                  if (!ViewModel.CanLogin) { return; }
+                                  ViewModel.LoginCommand.Execute (null);
+                              };
+
+        ...
+
+        Initialized += (_, _) => { ViewModel.Initialized (); };
+    }
+
+    ...
+
+}
+```
+
+Momentarily slipping over to the view model, all bindable properties use some form of `ObservableProperty` with the class deriving from `ObservableObject`. Commands are of the `RelayCommand` type.
+
+``` csharp
+
+internal partial class LoginViewModel : ObservableObject
+{
+    ...
+
+    [ObservableProperty]
+    private bool _canLogin;
+
+    private string _password;
+
+    ...
+
+    public LoginViewModel ()
+    {
+        ...
+
+        Password = string.Empty;
+
+        ...
+        
+        LoginCommand = new (Execute);
+
+        Clear ();
+
+        return;
+
+        async void Execute () { await Login (); }
+    }
+
+    ...
+
+    public RelayCommand LoginCommand { get; }
+
+    public string Password
+    {
+        get => _password;
+        set
+        {
+            SetProperty (ref _password, value);
+            PasswordLengthMessage = $"_Password ({_password.Length} characters):";
+            ValidateLogin ();
+        }
+    }
+```
+
+The use of `WeakReferenceMessenger` provides one method of signaling the view from the view model. It's just one way to handle cross-thread messaging in this framework.
+
+``` csharp
+...
+
+private async Task Login ()
+{
+    SendMessage (LoginAction.LoginProgress, LOGGING_IN_PROGRESS_MESSAGE);
+    await Task.Delay (TimeSpan.FromSeconds (1));
+    Clear ();
+}
+
+private void SendMessage (LoginAction loginAction, string message = "")
+{
+    switch (loginAction)
+    {
+        case LoginAction.LoginProgress:
+            LoginProgressMessage = message;
+            break;
+        case LoginAction.Validation:
+            ValidationMessage = CanLogin ? VALID_LOGIN_MESSAGE : INVALID_LOGIN_MESSAGE;
+            ValidationColorScheme = CanLogin ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"];
+            break;
+    }
+    WeakReferenceMessenger.Default.Send (new Message<LoginAction> { Value = loginAction });
+}
+
+private void ValidateLogin ()
+{
+    CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password);
+    SendMessage (LoginAction.Validation);
+}
+
+...
+```
+
+And the view's `Receive` function which provides an `Application.Refresh()` call to update the UI immediately.
+
+``` csharp
+public void Receive (Message<LoginAction> message)
+{
+    switch (message.Value)
+    {
+        case LoginAction.LoginProgress:
+            {
+                loginProgressLabel.Text = ViewModel.LoginProgressMessage;
+                break;
+            }
+        case LoginAction.Validation:
+            {
+                validationLabel.Text = ViewModel.ValidationMessage;
+                validationLabel.ColorScheme = ViewModel.ValidationColorScheme;
+                break;
+            }
+    }
+    SetText();
+    Application.Refresh ();
+}
+```
+
+There are other ways of handling cross-thread communication, this gives just one example. 

+ 1 - 0
ReactiveExample/Program.cs

@@ -12,6 +12,7 @@ public static class Program
         RxApp.MainThreadScheduler = TerminalScheduler.Default;
         RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
         Application.Run (new LoginView (new LoginViewModel ()));
+        Application.Top.Dispose();
         Application.Shutdown ();
     }
 }

+ 13 - 13
Terminal.sln

@@ -41,6 +41,11 @@ EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkitExample", "CommunityToolkitExample\CommunityToolkitExample.csproj", "{58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}"
 EndProject
 Global
+	GlobalSection(NestedProjects) = preSolution
+		{5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D}
+		{715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D}
+		{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D}
+	EndGlobalSection
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Release|Any CPU = Release|Any CPU
@@ -50,6 +55,14 @@ Global
 		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU
+		{715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU
 		{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -66,14 +79,6 @@ Global
 		{B0A602CD-E176-449D-8663-64238D54F857}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.Build.0 = Release|Any CPU
-		{5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{5DE91722-8765-4E2B-97E4-2A18010B5CED}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{5DE91722-8765-4E2B-97E4-2A18010B5CED}.Release|Any CPU.Build.0 = Release|Any CPU
-		{715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{715DB4BA-F989-4DF6-B46F-5ED26A32B2DD}.Release|Any CPU.Build.0 = Release|Any CPU
 		{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -86,11 +91,6 @@ Global
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
-	GlobalSection(NestedProjects) = preSolution
-		{5DE91722-8765-4E2B-97E4-2A18010B5CED} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D}
-		{715DB4BA-F989-4DF6-B46F-5ED26A32B2DD} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D}
-		{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2} = {CCADA0BC-61CF-4B4B-96BA-A3B0C0A7F54D}
-	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03}
 	EndGlobalSection