Fixes #4046 - Moves examples into ./Examples and fixes ./Tests (#4047)

* touching publish.yml

* Moved Examples into ./Examples

* Moved Benchmarks into ./Tests

* Moved Benchmarks into ./Tests

* Moved UICatalog into ./Examples

* Moved UICatalog into ./Examples 2

* Moved tests into ./Tests

* Updated nuget
This commit is contained in:
Tig
2025-04-25 09:49:33 -06:00
committed by GitHub
parent dca3923491
commit 0baa881dc5
199 changed files with 149 additions and 142 deletions

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<ProjectReference Include="..\..\Terminal.Gui\Terminal.Gui.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
namespace CommunityToolkitExample;
internal enum LoginActions
{
Clear,
Validation,
LoginProgress
}

View File

@@ -0,0 +1,62 @@
using Terminal.Gui;
namespace CommunityToolkitExample;
internal partial class LoginView : Window
{
private Label titleLabel;
private Label usernameLengthLabel;
private TextField usernameInput;
private Label passwordLengthLabel;
private TextField passwordInput;
private Label validationLabel;
private Button loginButton;
private Button clearButton;
private Label loginProgressLabel;
private void InitializeComponent ()
{
titleLabel = new Label ();
titleLabel.Text = "Login Form";
Add (titleLabel);
usernameLengthLabel = new Label ();
usernameLengthLabel.X = Pos.Left (titleLabel);
usernameLengthLabel.Y = Pos.Top (titleLabel) + 1;
Add (usernameLengthLabel);
usernameInput = new TextField ();
usernameInput.X = Pos.Right (usernameLengthLabel) + 1;
usernameInput.Y = Pos.Top (usernameLengthLabel);
usernameInput.Width = 40;
Add (usernameInput);
passwordLengthLabel = new Label ();
passwordLengthLabel.X = Pos.Left (usernameLengthLabel);
passwordLengthLabel.Y = Pos.Top (usernameLengthLabel) + 1;
Add (passwordLengthLabel);
passwordInput = new TextField ();
passwordInput.X = Pos.Right (passwordLengthLabel) + 1;
passwordInput.Y = Pos.Top (passwordLengthLabel);
passwordInput.Width = 40;
passwordInput.Secret = true;
Add (passwordInput);
validationLabel = new Label ();
validationLabel.X = Pos.Left (passwordInput);
validationLabel.Y = Pos.Top (passwordInput) + 1;
Add (validationLabel);
loginButton = new Button ();
loginButton.X = Pos.Left (validationLabel);
loginButton.Y = Pos.Top (validationLabel) + 1;
loginButton.Text = "_Login";
Add (loginButton);
clearButton = new Button ();
clearButton.X = Pos.Left (loginButton);
clearButton.Y = Pos.Top (loginButton) + 1;
clearButton.Text = "_Clear";
Add (clearButton);
loginProgressLabel = new Label ();
loginProgressLabel.X = Pos.Left (clearButton);
loginProgressLabel.Y = Pos.Top (clearButton) + 1;
loginProgressLabel.Width = 40;
loginProgressLabel.Height = 1;
Add (loginProgressLabel);
}
}

View File

@@ -0,0 +1,77 @@
using CommunityToolkit.Mvvm.Messaging;
using Terminal.Gui;
namespace CommunityToolkitExample;
internal partial class LoginView : IRecipient<Message<LoginActions>>
{
public LoginView (LoginViewModel viewModel)
{
WeakReferenceMessenger.Default.Register (this);
Title = $"Community Toolkit MVVM Example - {Application.QuitKey} to Exit";
ViewModel = viewModel;
InitializeComponent ();
usernameInput.TextChanged += (_, _) =>
{
ViewModel.Username = usernameInput.Text;
};
passwordInput.TextChanged += (_, _) =>
{
ViewModel.Password = passwordInput.Text;
};
loginButton.Accepting += (_, e) =>
{
if (!ViewModel.CanLogin) { return; }
ViewModel.LoginCommand.Execute (null);
// Anytime Accepting is handled, make sure to set e.Cancel to false.
e.Cancel = false;
};
clearButton.Accepting += (_, e) =>
{
ViewModel.ClearCommand.Execute (null);
// Anytime Accepting is handled, make sure to set e.Cancel to false.
e.Cancel = false;
};
Initialized += (_, _) => { ViewModel.Initialized (); };
}
public LoginViewModel ViewModel { get; set; }
public void Receive (Message<LoginActions> message)
{
switch (message.Value)
{
case LoginActions.Clear:
{
loginProgressLabel.Text = ViewModel.LoginProgressMessage;
validationLabel.Text = ViewModel.ValidationMessage;
validationLabel.ColorScheme = ViewModel.ValidationColorScheme;
break;
}
case LoginActions.LoginProgress:
{
loginProgressLabel.Text = ViewModel.LoginProgressMessage;
break;
}
case LoginActions.Validation:
{
validationLabel.Text = ViewModel.ValidationMessage;
validationLabel.ColorScheme = ViewModel.ValidationColorScheme;
break;
}
}
SetText();
// BUGBUG: This should not be needed:
Application.LayoutAndDraw ();
}
private void SetText ()
{
usernameInput.Text = ViewModel.Username;
usernameLengthLabel.Text = ViewModel.UsernameLengthMessage;
passwordInput.Text = ViewModel.Password;
passwordLengthLabel.Text = ViewModel.PasswordLengthMessage;
}
}

View File

@@ -0,0 +1,128 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Terminal.Gui;
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!";
[ObservableProperty]
private bool _canLogin;
[ObservableProperty]
private string _loginProgressMessage;
private string _password;
[ObservableProperty]
private string _passwordLengthMessage;
private string _username;
[ObservableProperty]
private string _usernameLengthMessage;
[ObservableProperty]
private ColorScheme? _validationColorScheme;
[ObservableProperty]
private string _validationMessage;
public LoginViewModel ()
{
_loginProgressMessage = string.Empty;
_password = string.Empty;
_passwordLengthMessage = string.Empty;
_username = string.Empty;
_usernameLengthMessage = string.Empty;
_validationMessage = string.Empty;
Username = string.Empty;
Password = string.Empty;
ClearCommand = new (Clear);
LoginCommand = new (Execute);
Clear ();
return;
async void Execute () { await Login (); }
}
public RelayCommand ClearCommand { get; }
public RelayCommand LoginCommand { get; }
public string Password
{
get => _password;
set
{
SetProperty (ref _password, value);
PasswordLengthMessage = $"_Password ({_password.Length} characters):";
ValidateLogin ();
}
}
public string Username
{
get => _username;
set
{
SetProperty (ref _username, value);
UsernameLengthMessage = $"_Username ({_username.Length} characters):";
ValidateLogin ();
}
}
public void Initialized ()
{
Clear ();
}
private void Clear ()
{
Username = string.Empty;
Password = string.Empty;
SendMessage (LoginActions.Clear, DEFAULT_LOGIN_PROGRESS_MESSAGE);
}
private async Task Login ()
{
SendMessage (LoginActions.LoginProgress, LOGGING_IN_PROGRESS_MESSAGE);
await Task.Delay (TimeSpan.FromSeconds (1));
Clear ();
}
private void SendMessage (LoginActions loginAction, string message = "")
{
switch (loginAction)
{
case LoginActions.Clear:
LoginProgressMessage = message;
ValidationMessage = INVALID_LOGIN_MESSAGE;
ValidationColorScheme = Colors.ColorSchemes ["Error"];
break;
case LoginActions.LoginProgress:
LoginProgressMessage = message;
break;
case LoginActions.Validation:
ValidationMessage = CanLogin ? VALID_LOGIN_MESSAGE : INVALID_LOGIN_MESSAGE;
ValidationColorScheme = CanLogin ? Colors.ColorSchemes ["Base"] : Colors.ColorSchemes ["Error"];
break;
}
WeakReferenceMessenger.Default.Send (new Message<LoginActions> { Value = loginAction });
}
private void ValidateLogin ()
{
CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password);
SendMessage (LoginActions.Validation);
}
}

View File

@@ -0,0 +1,6 @@
namespace CommunityToolkitExample;
internal class Message<T>
{
public T? Value { get; set; }
}

View File

@@ -0,0 +1,26 @@
using Microsoft.Extensions.DependencyInjection;
using Terminal.Gui;
namespace CommunityToolkitExample;
public static class Program
{
public static IServiceProvider? Services { get; private set; }
private static void Main (string [] args)
{
Services = ConfigureServices ();
Application.Init ();
Application.Run (Services.GetRequiredService<LoginView> ());
Application.Top?.Dispose();
Application.Shutdown ();
}
private static IServiceProvider ConfigureServices ()
{
var services = new ServiceCollection ();
services.AddTransient<LoginView> ();
services.AddTransient<LoginViewModel> ();
return services.BuildServiceProvider ();
}
}

View File

@@ -0,0 +1,154 @@
# 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`.
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);
};
...
// Let the view model know the view is intialized.
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. The use of `ObservableProperty` generates the code for handling `INotifyPropertyChanged` and `INotifyPropertyChanging`.
``` 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 ();
}
```