mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 07:47:54 +01:00
added recommendations; fixed .sln
This commit is contained in:
@@ -8,13 +8,15 @@ namespace CommunityToolkitExample;
|
|||||||
internal partial class LoginViewModel : ObservableObject
|
internal partial class LoginViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
private const string DEFAULT_LOGIN_PROGRESS_MESSAGE = "Press 'Login' to log in.";
|
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 LOGGING_IN_PROGRESS_MESSAGE = "Logging in...";
|
||||||
private const string VALID_LOGIN_MESSAGE = "The input is valid!";
|
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]
|
[ObservableProperty]
|
||||||
private bool _canLogin;
|
private bool _canLogin;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _loginProgressMessage;
|
||||||
|
|
||||||
private string _password;
|
private string _password;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -24,16 +26,11 @@ internal partial class LoginViewModel : ObservableObject
|
|||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _usernameLengthMessage;
|
private string _usernameLengthMessage;
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string _loginProgressMessage;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
|
||||||
private string _validationMessage;
|
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ColorScheme? _validationColorScheme;
|
private ColorScheme? _validationColorScheme;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _validationMessage;
|
||||||
public LoginViewModel ()
|
public LoginViewModel ()
|
||||||
{
|
{
|
||||||
Username = string.Empty;
|
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
|
public string Username
|
||||||
{
|
{
|
||||||
get => _username;
|
get => _username;
|
||||||
@@ -81,6 +72,11 @@ internal partial class LoginViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Initialized ()
|
||||||
|
{
|
||||||
|
Clear ();
|
||||||
|
}
|
||||||
|
|
||||||
private void Clear ()
|
private void Clear ()
|
||||||
{
|
{
|
||||||
Username = string.Empty;
|
Username = string.Empty;
|
||||||
@@ -111,8 +107,9 @@ internal partial class LoginViewModel : ObservableObject
|
|||||||
WeakReferenceMessenger.Default.Send (new Message<LoginAction> { Value = loginAction });
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public static class Program
|
|||||||
Services = ConfigureServices ();
|
Services = ConfigureServices ();
|
||||||
Application.Init ();
|
Application.Init ();
|
||||||
Application.Run (Services.GetRequiredService<LoginView> ());
|
Application.Run (Services.GetRequiredService<LoginView> ());
|
||||||
|
Application.Top.Dispose();
|
||||||
Application.Shutdown ();
|
Application.Shutdown ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
180
CommunityToolkitExample/README.md
Normal file
180
CommunityToolkitExample/README.md
Normal file
@@ -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.
|
||||||
@@ -12,6 +12,7 @@ public static class Program
|
|||||||
RxApp.MainThreadScheduler = TerminalScheduler.Default;
|
RxApp.MainThreadScheduler = TerminalScheduler.Default;
|
||||||
RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
|
RxApp.TaskpoolScheduler = TaskPoolScheduler.Default;
|
||||||
Application.Run (new LoginView (new LoginViewModel ()));
|
Application.Run (new LoginView (new LoginViewModel ()));
|
||||||
|
Application.Top.Dispose();
|
||||||
Application.Shutdown ();
|
Application.Shutdown ();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
Terminal.sln
26
Terminal.sln
@@ -41,6 +41,11 @@ EndProject
|
|||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkitExample", "CommunityToolkitExample\CommunityToolkitExample.csproj", "{58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkitExample", "CommunityToolkitExample\CommunityToolkitExample.csproj", "{58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
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
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Release|Any CPU = Release|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}.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.ActiveCfg = Release|Any CPU
|
||||||
{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.Build.0 = 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
|
{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}.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.ActiveCfg = Release|Any CPU
|
||||||
{B0A602CD-E176-449D-8663-64238D54F857}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
|
||||||
{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Debug|Any CPU.Build.0 = 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
|
{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@@ -86,11 +91,6 @@ Global
|
|||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
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
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03}
|
SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|||||||
Reference in New Issue
Block a user