diff --git a/CommunityToolkitExample/CommunityToolkitExample.csproj b/CommunityToolkitExample/CommunityToolkitExample.csproj
new file mode 100644
index 000000000..fdb41dfcd
--- /dev/null
+++ b/CommunityToolkitExample/CommunityToolkitExample.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CommunityToolkitExample/LoginAction.cs b/CommunityToolkitExample/LoginAction.cs
new file mode 100644
index 000000000..2fd8d4a45
--- /dev/null
+++ b/CommunityToolkitExample/LoginAction.cs
@@ -0,0 +1,7 @@
+namespace CommunityToolkitExample;
+
+internal enum LoginAction
+{
+ Validation,
+ LoginProgress
+}
diff --git a/CommunityToolkitExample/LoginView.Designer.cs b/CommunityToolkitExample/LoginView.Designer.cs
new file mode 100644
index 000000000..e1bddff45
--- /dev/null
+++ b/CommunityToolkitExample/LoginView.Designer.cs
@@ -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);
+ }
+}
diff --git a/CommunityToolkitExample/LoginView.cs b/CommunityToolkitExample/LoginView.cs
new file mode 100644
index 000000000..262928e35
--- /dev/null
+++ b/CommunityToolkitExample/LoginView.cs
@@ -0,0 +1,68 @@
+using CommunityToolkit.Mvvm.Messaging;
+using Terminal.Gui;
+
+namespace CommunityToolkitExample;
+
+internal partial class LoginView : IRecipient>
+{
+ 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;
+ SetText ();
+ };
+ passwordInput.TextChanged += (_, _) =>
+ {
+ ViewModel.Password = passwordInput.Text;
+ SetText ();
+ };
+ loginButton.Accept += (_, _) =>
+ {
+ if (!ViewModel.CanLogin) { return; }
+ ViewModel.LoginCommand.Execute (null);
+ };
+
+ clearButton.Accept += (_, _) =>
+ {
+ ViewModel.ClearCommand.Execute (null);
+ SetText ();
+ };
+
+ Initialized += (_, _) => { ViewModel.Initialized (); };
+ }
+
+ public LoginViewModel ViewModel { get; set; }
+
+ public void Receive (Message 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 ();
+ }
+
+ private void SetText ()
+ {
+ usernameInput.Text = ViewModel.Username;
+ usernameLengthLabel.Text = ViewModel.UsernameLengthMessage;
+ passwordInput.Text = ViewModel.Password;
+ passwordLengthLabel.Text = ViewModel.PasswordLengthMessage;
+ }
+}
\ No newline at end of file
diff --git a/CommunityToolkitExample/LoginViewModel.cs b/CommunityToolkitExample/LoginViewModel.cs
new file mode 100644
index 000000000..b5fbfa32f
--- /dev/null
+++ b/CommunityToolkitExample/LoginViewModel.cs
@@ -0,0 +1,118 @@
+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 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;
+
+ private string _password;
+
+ [ObservableProperty]
+ private string _passwordLengthMessage;
+
+ private string _username;
+
+ [ObservableProperty]
+ private string _usernameLengthMessage;
+
+ [ObservableProperty]
+ private string _loginProgressMessage;
+
+ [ObservableProperty]
+ private string _validationMessage;
+
+ [ObservableProperty]
+ private ColorScheme? _validationColorScheme;
+
+ public LoginViewModel ()
+ {
+ 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 ();
+ }
+ }
+
+ private void ValidateLogin ()
+ {
+ CanLogin = !string.IsNullOrEmpty (Username) && !string.IsNullOrEmpty (Password);
+ SendMessage (LoginAction.Validation);
+ }
+
+ public string Username
+ {
+ get => _username;
+ set
+ {
+ SetProperty (ref _username, value);
+ UsernameLengthMessage = $"_Username ({_username.Length} characters):";
+ ValidateLogin ();
+ }
+ }
+
+ private void Clear ()
+ {
+ Username = string.Empty;
+ Password = string.Empty;
+ SendMessage (LoginAction.Validation);
+ SendMessage (LoginAction.LoginProgress, DEFAULT_LOGIN_PROGRESS_MESSAGE);
+ }
+
+ 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 { Value = loginAction });
+ }
+
+ public void Initialized ()
+ {
+ Clear ();
+ }
+}
diff --git a/CommunityToolkitExample/Message.cs b/CommunityToolkitExample/Message.cs
new file mode 100644
index 000000000..a1081512e
--- /dev/null
+++ b/CommunityToolkitExample/Message.cs
@@ -0,0 +1,6 @@
+namespace CommunityToolkitExample;
+
+internal class Message
+{
+ public T Value { get; set; }
+}
diff --git a/CommunityToolkitExample/Program.cs b/CommunityToolkitExample/Program.cs
new file mode 100644
index 000000000..0a557fc47
--- /dev/null
+++ b/CommunityToolkitExample/Program.cs
@@ -0,0 +1,25 @@
+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 ());
+ Application.Shutdown ();
+ }
+
+ private static IServiceProvider ConfigureServices ()
+ {
+ var services = new ServiceCollection ();
+ services.AddTransient ();
+ services.AddTransient ();
+ return services.BuildServiceProvider ();
+ }
+}
\ No newline at end of file
diff --git a/Terminal.sln b/Terminal.sln
index 98de83b41..7dbb5432a 100644
--- a/Terminal.sln
+++ b/Terminal.sln
@@ -38,12 +38,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui.Analyzers.Inte
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui.Analyzers.Internal.Debugging", "Analyzers\Terminal.Gui.Analyzers.Internal.Debugging\Terminal.Gui.Analyzers.Internal.Debugging.csproj", "{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}"
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
@@ -53,14 +50,6 @@ 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
@@ -77,14 +66,31 @@ 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
{C2AD09BD-D579-45A7-ACA3-E4EF3BC027D2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {58FDCA8F-08F7-4D80-9DA3-6A9AED01E163}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
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