Tig b0f32811eb Fixes #3930 - Splits tests to `Tests/UnitTests`, `Tests/IntegrationTests`, `Tests/StressTests` (#3954) 4 bulan lalu
..
CommunityToolkitExample.csproj b0f32811eb Fixes #3930 - Splits tests to `Tests/UnitTests`, `Tests/IntegrationTests`, `Tests/StressTests` (#3954) 4 bulan lalu
LoginActions.cs 7b60527446 fix warnings; tidy up a bit more 1 tahun lalu
LoginView.Designer.cs 12adcf3bf4 Add Community Toolkit example 1 tahun lalu
LoginView.cs 7ba6d638bc Fixes #3918 and #3913 - `Accepting` behavior (#3921) 5 bulan lalu
LoginViewModel.cs 6a89686bfa Merge branch 'v2_communitytoolkit' of https://github.com/johnmbaughman/Terminal.Gui into v2_communitytoolkit 1 tahun lalu
Message.cs 7b60527446 fix warnings; tidy up a bit more 1 tahun lalu
Program.cs 022050db73 Fixed nullable warnings 7 1 tahun lalu
README.md 5709843d8f Update README.md 1 tahun lalu

README.md

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.

// 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.

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.

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.

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.

...
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.

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 ();
}