Skip to content

Commit

Permalink
Merge pull request #4 from kristoffer-tungland/2-rename-command-to-fl…
Browse files Browse the repository at this point in the history
…uentcommand-and-asynccommand-to-fluentasynccommand

Rename Command to FluentCommand and update references
  • Loading branch information
kristoffer-tungland authored Nov 4, 2024
2 parents 3c616ab + 5357937 commit 154a9ab
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 154a9ab

Please sign in to comment.