Skip to content

Commit

Permalink
Rename Command to FluentCommand and update references
Browse files Browse the repository at this point in the history
Replaced Command and Command<T> with FluentCommand and FluentCommand<T> across the project. Updated README.md, test cases, and example code to reflect the new naming convention. Incremented project version to 0.0.2. Added new readme reference to the NuGet package specification. Introduced MVVMFluent namespace with IFluentCommand interface and FluentCommand classes.
  • Loading branch information
ktuatcowi committed Nov 4, 2024
1 parent 3c616ab commit 5357937
Show file tree
Hide file tree
Showing 15 changed files with 127 additions and 118 deletions.
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ MVVMFluent is a lightweight, source-only .NET library designed to simplify the M

## Features
- Fluent Property Setters: Easily chain property setters with `Changed`, `Changing`, command reevaluation, and property change notifications.
- Fluent Command Setup: Define commands with `Do` methods and conditional execution via `If`.
- Generic `Command<T>`: Define commands with parameterized actions using a strongly-typed approach.
- Implements `ICommand`: Both `Command` and `Command<T>` implement the `ICommand` interface, enabling full compatibility with WPF and other MVVM frameworks.
- Fluent FluentCommand Setup: Define commands with `Do` methods and conditional execution via `If`.
- Generic `FluentCommand<T>`: Define commands with parameterized actions using a strongly-typed approach.
- Implements `ICommand`: Both `FluentCommand` and `FluentCommand<T>` implement the `ICommand` interface, enabling full compatibility with WPF and other MVVM frameworks.
- Source-Only Package: Integrates directly into your project as source code, minimizing dependencies.
- Global Namespace Integration: Fully qualified `global::` namespaces for all system types and dependencies.
- Disposal Management: Manage and clean up resources with built-in `Dispose` functionality.
Expand Down Expand Up @@ -51,13 +51,13 @@ public class MyViewModel : ViewModelBase
```
Here, the `Set` method is used directly to assign the new value to the property without needing any additional configuration like `Changing` or `Changed`.

### Fluent `Command` Setup
### Fluent `FluentCommand` Setup
Commands in MVVMFluent can be easily defined with `Do` methods, and you can add conditional execution using `If`.

```csharp
public class MyViewModel : ViewModelBase
{
public Command SaveCommand => Do(Save)
public FluentCommand SaveCommand => Do(Save)
.If(CanSave);

private void Save()
Expand All @@ -69,13 +69,13 @@ public class MyViewModel : ViewModelBase
}
```

### Using `Command<T>` for Generic Commands
In cases where you need commands that accept a parameter, you can use the generic `Command<T>`. Both `Command` and `Command<T>` implement the `ICommand` interface, making them compatible with WPF or any other MVVM framework that supports `ICommand`.
### Using `FluentCommand<T>` for Generic Commands
In cases where you need commands that accept a parameter, you can use the generic `FluentCommand<T>`. Both `FluentCommand` and `FluentCommand<T>` implement the `ICommand` interface, making them compatible with WPF or any other MVVM framework that supports `ICommand`.

```csharp
public class MyViewModel : ViewModelBase
{
public Command<string> SaveWithMessageCommand => Do<string>(message => Save(message))
public FluentCommand<string> SaveWithMessageCommand => Do<string>(message => Save(message))
.If(message => !string.IsNullOrEmpty(message));

private void Save(string message)
Expand All @@ -85,7 +85,7 @@ public class MyViewModel : ViewModelBase
}
}
```
This example demonstrates how `Command<T>` can be used to handle parameterized commands with strongly-typed arguments.
This example demonstrates how `FluentCommand<T>` can be used to handle parameterized commands with strongly-typed arguments.

### Using `Notify(nameof(...))` for Property Change Notifications
You can also notify multiple properties when setting a value. This is useful when other properties are derived from or depend on the value being set.
Expand Down Expand Up @@ -129,7 +129,7 @@ public class MyViewModel : ViewModelBase
```
In this example, if the `Counter` property hasn’t been set before, the Get(10) will return 10 as the default value.

### Asynchronous Command (`AsyncCommand`)
### Asynchronous FluentCommand (`AsyncCommand`)
The AsyncCommand in the MVVMFluent library is designed to simplify asynchronous command execution with built-in support for cancellation, progress reporting, and exception handling. It allows you to execute long-running tasks without blocking the UI thread, while maintaining full control over the command's lifecycle.

**Key Features:**
Expand Down Expand Up @@ -168,14 +168,14 @@ public bool CanLoad { get => Get(true); set => Set(value); }

In XAML:
```xml
<Button Content="Load" Command="{Binding LoadCommand}" />
<Button Content="Load" FluentCommand="{Binding LoadCommand}" />
<ProgressBar Visibility="{Binding LoadCommand.IsRunning, Converter={StaticResource BoolToVisibilityConverter}}" />
<Button Content="Cancel" Command="{Binding LoadCommand.CancelCommand}" />
<Button Content="Cancel" FluentCommand="{Binding LoadCommand.CancelCommand}" />
```

This example demonstrates how AsyncCommand integrates with both your view model and XAML, providing a responsive and user-friendly interface for long-running or cancellable operations.

### Asynchronous Command with Parameter (`AsyncCommand<T>`)
### Asynchronous FluentCommand with Parameter (`AsyncCommand<T>`)
The `AsyncCommand<T>` extends the functionality of AsyncCommand by allowing you to pass a parameter of type `T` to the asynchronous execution logic. This makes it ideal for scenarios where the command needs to act on dynamic data or context-specific parameters. Like `AsyncCommand`, it supports cancellation, progress reporting, and exception handling, while maintaining the same fluent API for configuration.

### Validation Fluent Setter (`ValidationFluentSetter<TValue>`)
Expand Down Expand Up @@ -211,7 +211,7 @@ private bool IsCommentValid(string? value)
**Conditional Validation** allows for validation checks to occur only when certain conditions are met, using the `When(value)` method. Additionally, the command can be set to execute only if the validation passes by using the `IfValid()` method, ensuring that the command is invoked only with valid input. For example:

```csharp
// Remember to use .Notify(Command) on the setter
// Remember to use .Notify(FluentCommand) on the setter
public string? Comments
{
get => Get<string?>();
Expand All @@ -227,7 +227,7 @@ private bool IsCommentValid(string? value)
}

// Example command that executes only if the Comments property is valid
public Command OkCommand => Do(() => ShowDialog(Comments)).IfValid(nameof(Comments));
public FluentCommand OkCommand => Do(() => ShowDialog(Comments)).IfValid(nameof(Comments));
```
Together, these methods enhance the user experience by providing responsive, context-sensitive validation feedback while ensuring adherence to application requirements.

Expand Down Expand Up @@ -298,7 +298,7 @@ public class MyDialogViewModel : IClosableViewModel, IResultViewModel<string>

public Action? RequestCloseView { get; set; }
public bool CanCloseView() => !string.IsNullOrEmpty(Result);
public Command CloseCommand => Do(Close);
public FluentCommand CloseCommand => Do(Close);

public void Close()
{
Expand Down Expand Up @@ -361,12 +361,12 @@ namespace MVVMFluent.Demo
.If(() => Comments?.Contains("Load") == true)
.Handle(HandleError);

public Command SaveCommand =>
public FluentCommand SaveCommand =>
Do(Save)
.IfValid(nameof(Name))
.If(() => !string.IsNullOrEmpty(Name));

public Command CloseCommand => Do(() => RequestCloseView?.Invoke()).If(CanCloseView);
public FluentCommand CloseCommand => Do(() => RequestCloseView?.Invoke()).If(CanCloseView);
public Action? RequestCloseView { get; set; }
public bool CanCloseView()
{
Expand Down Expand Up @@ -413,7 +413,7 @@ namespace MVVMFluent.Demo
In this complete example:
- The `Name` property triggers the `SaveCommand` and notifies the `FullName` property on change.
- The `Counter` property has a default value of `10`.
- The `SaveWithMessageCommand` is a generic command (`Command<string>`) that accepts a string parameter.
- The `SaveWithMessageCommand` is a generic command (`FluentCommand<string>`) that accepts a string parameter.
- The `Close()` method is provided to dispose of resources when the view model is no longer needed.

## License
Expand Down
4 changes: 2 additions & 2 deletions src/MVVMFluent.Demo/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public string? Input
set => When(value).Required().Notify(AsyncCommand, OkCommand).Set();
}

public Command OkCommand => Do(() => ShowDialog(Input)).IfValid(nameof(Input));
public FluentCommand OkCommand => Do(() => ShowDialog(Input)).IfValid(nameof(Input));

private bool CanExecute()
{
Expand Down Expand Up @@ -54,7 +54,7 @@ private async Task ShowDialogAsync(CancellationToken cancellationToken)
ShowDialog(Input);
}

public Command<string> HelpCommand => Do<string>(ShowDialog);
public FluentCommand<string> HelpCommand => Do<string>(ShowDialog);

private void ShowDialog(string? input)
{
Expand Down
38 changes: 19 additions & 19 deletions src/MVVMFluent.Tests/ViewModelBaseCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ public class ViewModelBaseCommandTests
{
class CommandTestViewModel(Action execute) : ViewModelBase
{
public ICommand Command => Do(execute);
public ICommand FluentCommand => Do(execute);
}

[Fact]
internal void Do_CreatesCommand()
{
var viewModel = new CommandTestViewModel(() => { });
var command = viewModel.Command;
var command = viewModel.FluentCommand;
Assert.NotNull(command);
}

Expand All @@ -23,13 +23,13 @@ internal void Do()
var didExecute = false;
void execute() => didExecute = true;
var viewModel = new CommandTestViewModel(execute);
viewModel.Command.Execute(null);
viewModel.FluentCommand.Execute(null);
Assert.True(didExecute);
}

class CommandParameterTestViewModel(Action<object?> execute) : ViewModelBase
{
public ICommand Command => Do(execute);
public ICommand FluentCommand => Do(execute);
}

[Fact]
Expand All @@ -41,13 +41,13 @@ void execute(object? obj)
parameter = obj;
}
var viewModel = new CommandParameterTestViewModel(execute);
viewModel.Command.Execute("Test");
viewModel.FluentCommand.Execute("Test");
Assert.Equal("Test", parameter);
}

class CommandIfTestViewModel(Action execute, Func<bool> canExecute) : ViewModelBase
{
public ICommand Command => Do(execute).If(canExecute);
public ICommand FluentCommand => Do(execute).If(canExecute);
}

[Fact]
Expand All @@ -57,7 +57,7 @@ internal void DoIf()
void execute() => didExecute = true;
var viewModel = new CommandIfTestViewModel(execute, () => true);

viewModel.Command.Execute(null);
viewModel.FluentCommand.Execute(null);

Assert.True(didExecute);
}
Expand All @@ -69,7 +69,7 @@ internal void DoIf_WhenCanExecuteIsFalse_CannotExecute()
void execute() { }
var viewModel = new CommandIfTestViewModel(execute, canExecute);

var result = viewModel.Command.CanExecute(null);
var result = viewModel.FluentCommand.CanExecute(null);

Assert.False(result);
}
Expand All @@ -82,14 +82,14 @@ internal void DoIf_WhenCanExecuteIsFalse_DoesNotExecute()
void execute() => didExecute = true;
var viewModel = new CommandIfTestViewModel(execute, canExecute);

viewModel.Command.Execute(null);
viewModel.FluentCommand.Execute(null);

Assert.False(didExecute);
}

class CommandIfParameterTestViewModel(Action<object?> execute, Func<object, bool> canExecute) : ViewModelBase
{
public ICommand Command => Do(execute).If(canExecute);
public ICommand FluentCommand => Do(execute).If(canExecute);
}

[Fact]
Expand All @@ -98,7 +98,7 @@ internal void DoIfWithParameter()
object? parameter = null;
void execute(object? obj) => parameter = obj;
var viewModel = new CommandIfParameterTestViewModel(execute, _ => true);
viewModel.Command.Execute("Test");
viewModel.FluentCommand.Execute("Test");
Assert.Equal("Test", parameter);
}

Expand All @@ -108,7 +108,7 @@ internal void DoIfWithParameter_WhenCanExecuteIsFalse_CannotExecute()
bool canExecute(object obj) => false;
void execute(object? obj) { }
var viewModel = new CommandIfParameterTestViewModel(execute, canExecute);
var result = viewModel.Command.CanExecute(null);
var result = viewModel.FluentCommand.CanExecute(null);
Assert.False(result);
}

Expand All @@ -119,7 +119,7 @@ internal void DoIfWithParameter_WhenCanExecuteIsFalse_DoesNotExecute()
var didExecute = false;
void execute(object? obj) => didExecute = true;
var viewModel = new CommandIfParameterTestViewModel(execute, canExecute);
viewModel.Command.Execute("Test");
viewModel.FluentCommand.Execute("Test");
Assert.False(didExecute);
}

Expand All @@ -128,9 +128,9 @@ private class CommandPropertyNotifyTestViewModel(Func<bool> canExecute) : ViewMo
public int Property
{
get => Get<int>();
set => When(value).Notify(Command).Set();
set => When(value).Notify(FluentCommand).Set();
}
public IFluentCommand Command => Do(() => { }).If(canExecute);
public IFluentCommand FluentCommand => Do(() => { }).If(canExecute);
}

[Fact]
Expand All @@ -144,9 +144,9 @@ bool canExecute()

var viewModel = new CommandPropertyNotifyTestViewModel(canExecute);

viewModel.Command.CanExecuteChanged += (sender, args) =>
viewModel.FluentCommand.CanExecuteChanged += (sender, args) =>
{
viewModel.Command.CanExecute(null);
viewModel.FluentCommand.CanExecute(null);
};

viewModel.Property = 1;
Expand All @@ -158,7 +158,7 @@ class CommandIfCanUseLocalVariableTestViewModel(Action execute) : ViewModelBase
{
private readonly Action _execute = execute;

public ICommand Command => Do(_execute);
public ICommand FluentCommand => Do(_execute);
}

[Fact]
Expand All @@ -167,7 +167,7 @@ internal void DoIfCanUseLocalVariable()
var didExecute = false;
void execute() => didExecute = true;
var viewModel = new CommandIfCanUseLocalVariableTestViewModel(execute);
viewModel.Command.Execute(null);
viewModel.FluentCommand.Execute(null);
Assert.True(didExecute);
}
}
16 changes: 11 additions & 5 deletions src/MVVMFluent.Tests/ViewModelBaseGenericsCommandTests.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
namespace MVVMFluent.Tests
{
public class CommandViewModel : ViewModelBase
{
}

public class ViewModelBaseGenericsCommandTests
{
private readonly CommandViewModel _viewModel = new();

[Fact]
public void Execute_WhenCanExecuteIsTrue_CallsExecuteAction()
{
// Arrange
var executeCalled = false;
var command = Command<string>.Do(param => executeCalled = true)
var command = FluentCommand<string>.Do(param => executeCalled = true, _viewModel)
.If(param => true); // CanExecute always returns true

// Act
Expand All @@ -22,7 +28,7 @@ public void Execute_WhenCanExecuteIsFalse_DoesNotCallExecuteAction()
{
// Arrange
var executeCalled = false;
var command = Command<string>.Do(param => executeCalled = true)
var command = FluentCommand<string>.Do(param => executeCalled = true, _viewModel)
.If(param => false); // CanExecute always returns false

// Act
Expand All @@ -36,7 +42,7 @@ public void Execute_WhenCanExecuteIsFalse_DoesNotCallExecuteAction()
public void CanExecute_WhenConditionIsTrue_ReturnsTrue()
{
// Arrange
var command = Command<string>.Do(param => { })
var command = FluentCommand<string>.Do(param => { }, _viewModel)
.If(param => true); // CanExecute always returns true

// Act
Expand All @@ -50,7 +56,7 @@ public void CanExecute_WhenConditionIsTrue_ReturnsTrue()
public void CanExecute_WhenConditionIsFalse_ReturnsFalse()
{
// Arrange
var command = Command<string>.Do(param => { })
var command = FluentCommand<string>.Do(param => { }, _viewModel)
.If(param => false); // CanExecute always returns false

// Act
Expand All @@ -64,7 +70,7 @@ public void CanExecute_WhenConditionIsFalse_ReturnsFalse()
public void CanExecute_WhenNotSet_ReturnsTrue()
{
// Arrange
var command = Command<string>.Do(param => { });
var command = FluentCommand<string>.Do(param => { }, _viewModel);

// Act
var result = command.CanExecute("test");
Expand Down
4 changes: 2 additions & 2 deletions src/MVVMFluent.Tests/ViewModelBasePropertiesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,15 @@ public string? PropertyWithOnChanged
{
get => Get<string?>();
set => When(value)
.OnChanged(newValue => OnChangedAction?.Invoke(newValue))
.Changed(newValue => OnChangedAction?.Invoke(newValue))
.Set();
}

public string? PropertyWithOnChanging
{
get => Get<string?>();
set => When(value)
.OnChanging(newValue => OnChangingAction?.Invoke(newValue))
.Changing(newValue => OnChangingAction?.Invoke(newValue))
.Set();
}

Expand Down
4 changes: 2 additions & 2 deletions src/MVVMFluent.WPF/CommandValidationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static class CommandValidationExtensions
/// <param name="propertyName">The name of the property to check for errors.</param>
/// <returns>The command with the condition added.</returns>
/// <exception cref="global::System.ArgumentNullException">Thrown when the property name is null or empty.</exception>
public static Command IfValid(this Command command, params string[] propertyName)
public static FluentCommand IfValid(this FluentCommand command, params string[] propertyName)
{
if (command.IsBuilt)
return command;
Expand All @@ -28,7 +28,7 @@ public static Command IfValid(this Command command, params string[] propertyName
/// <param name="propertyName">The name of the property to check for errors.</param>
/// <returns>The command with the condition added.</returns>
/// <exception cref="global::System.ArgumentNullException">Thrown when the property name is null or empty.</exception>
public static Command<T> IfValid<T>(this Command<T> command, params string[] propertyName)
public static FluentCommand<T> IfValid<T>(this FluentCommand<T> command, params string[] propertyName)
{
if (command.IsBuilt)
return command;
Expand Down
Loading

0 comments on commit 5357937

Please sign in to comment.