From d89333519516eedee6f592c94016f8ddc4f248af Mon Sep 17 00:00:00 2001 From: Dan Clark Date: Sun, 24 Nov 2024 16:35:00 +0000 Subject: [PATCH] wip --- Toolkit.Foundation/Activate.cs | 8 ++ Toolkit.Foundation/ActivateEventArgs.cs | 16 +++ .../ConfigurationValueViewModel.cs | 101 ----------------- .../ContentTemplateDescriptor.cs | 4 +- Toolkit.Foundation/Deactivate.cs | 8 ++ Toolkit.Foundation/DeactivateEventArgs.cs | 16 +++ Toolkit.Foundation/DeactivatedEventArgs.cs | 2 +- Toolkit.Foundation/IValidation.cs | 5 +- Toolkit.Foundation/Observable.cs | 2 +- Toolkit.Foundation/ObservableCollection.cs | 2 +- Toolkit.Foundation/ObservableConfiguration.cs | 34 ++++++ .../ObservableConfigurationCollection.cs | 76 +++++++++++++ Toolkit.Foundation/Result.cs | 26 +++-- Toolkit.Foundation/Validation.cs | 107 ++++++------------ .../IsStringNotNullOrEmptyConverter.cs | 10 ++ Toolkit.UI.WinUI/StringFormatConverter.cs | 15 +-- .../ValidationBooleanConverter.cs | 27 +++++ .../ValidationMessageConverter.cs | 23 ++++ 18 files changed, 282 insertions(+), 200 deletions(-) create mode 100644 Toolkit.Foundation/Activate.cs create mode 100644 Toolkit.Foundation/ActivateEventArgs.cs create mode 100644 Toolkit.Foundation/Deactivate.cs create mode 100644 Toolkit.Foundation/DeactivateEventArgs.cs create mode 100644 Toolkit.Foundation/ObservableConfiguration.cs create mode 100644 Toolkit.Foundation/ObservableConfigurationCollection.cs create mode 100644 Toolkit.UI.WinUI/IsStringNotNullOrEmptyConverter.cs create mode 100644 Toolkit.UI.WinUI/ValidationBooleanConverter.cs create mode 100644 Toolkit.UI.WinUI/ValidationMessageConverter.cs diff --git a/Toolkit.Foundation/Activate.cs b/Toolkit.Foundation/Activate.cs new file mode 100644 index 0000000..15a20be --- /dev/null +++ b/Toolkit.Foundation/Activate.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public record Activate +{ + public static ActivateEventArgs As(TSender sender) => new(sender); + + public static ActivateEventArgs As() where TSender : new() => new(new TSender()); +} diff --git a/Toolkit.Foundation/ActivateEventArgs.cs b/Toolkit.Foundation/ActivateEventArgs.cs new file mode 100644 index 0000000..5401a06 --- /dev/null +++ b/Toolkit.Foundation/ActivateEventArgs.cs @@ -0,0 +1,16 @@ +namespace Toolkit.Foundation; + +public record ActivateEventArgs +{ + public TSender? Sender { get; } + + public ActivateEventArgs(TSender sender) + { + Sender = sender; + } + + public ActivateEventArgs() + { + + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ConfigurationValueViewModel.cs b/Toolkit.Foundation/ConfigurationValueViewModel.cs index f3b3cc5..337a698 100644 --- a/Toolkit.Foundation/ConfigurationValueViewModel.cs +++ b/Toolkit.Foundation/ConfigurationValueViewModel.cs @@ -3,106 +3,5 @@ using CommunityToolkit.Mvvm.Messaging; namespace Toolkit.Foundation; -public partial class ConfigurationValueViewModel(IServiceProvider provider, - IServiceFactory factory, - IMessenger messenger, - IDisposer disposer, - TConfiguration configuration, - IWritableConfiguration writer, - Func read, - Action write) : - Observable(provider, factory, messenger, disposer), - IHandler> - where TConfiguration : class -{ - public void Handle(ChangedEventArgs args) - { - if (args.Sender is TConfiguration configuration) - { - Value = read(configuration); - } - } - protected override void Activated() => Value = read(configuration); - protected override void Changed(TValue? value) - { - if (IsActive) - { - writer.Write(args => write(value, args)); - } - } -} - -public partial class ConfigurationValueViewModel : - ObservableCollection, - IHandler> - where TConfiguration : class - where TItem : notnull, - IDisposable -{ - private readonly TConfiguration configuration; - private readonly Func read; - private readonly Action write; - private readonly IWritableConfiguration writer; - - public ConfigurationValueViewModel(IServiceProvider provider, - IServiceFactory factory, - IMessenger messenger, - IDisposer disposer, - TConfiguration configuration, - IWritableConfiguration writer, - Func read, - Action write, - TValue? value = default) : base(provider, factory, messenger, disposer, value) - { - this.configuration = configuration; - this.writer = writer; - this.read = read; - this.write = write; - - Value = value; - } - - public ConfigurationValueViewModel(IServiceProvider provider, - IServiceFactory factory, - IMessenger messenger, - IDisposer disposer, - IEnumerable items, - TConfiguration configuration, - IWritableConfiguration writer, - Func read, - Action write, - TValue? value = default) : base(provider, factory, messenger, disposer, items, value) - { - this.configuration = configuration; - this.writer = writer; - this.read = read; - this.write = write; - - Value = value; - } - - public void Handle(ChangedEventArgs args) - { - if (args.Sender is TConfiguration configuration) - { - Value = read(configuration); - } - } - - protected override void Activated() - { - Value = read(configuration); - } - - protected override void OnChanged(TValue? value) - { - if (IsActive) - { - writer.Write(args => write(value, args)); - } - - base.OnChanged(value); - } -} \ No newline at end of file diff --git a/Toolkit.Foundation/ContentTemplateDescriptor.cs b/Toolkit.Foundation/ContentTemplateDescriptor.cs index 565fd18..22865b7 100644 --- a/Toolkit.Foundation/ContentTemplateDescriptor.cs +++ b/Toolkit.Foundation/ContentTemplateDescriptor.cs @@ -3,12 +3,12 @@ public class ContentTemplateDescriptor(object key, Type viewModelType, Type viewType, - params object[]? parameters) : + params object?[]? parameters) : IContentTemplateDescriptor { public object Key => key; - public object[]? Parameters => parameters; + public object?[]? Parameters => parameters; public Type ContentType => viewModelType; diff --git a/Toolkit.Foundation/Deactivate.cs b/Toolkit.Foundation/Deactivate.cs new file mode 100644 index 0000000..1444801 --- /dev/null +++ b/Toolkit.Foundation/Deactivate.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public record Deactivate +{ + public static DeactivateEventArgs As(TSender sender) => new(sender); + + public static DeactivateEventArgs As() where TSender : new() => new(new TSender()); +} \ No newline at end of file diff --git a/Toolkit.Foundation/DeactivateEventArgs.cs b/Toolkit.Foundation/DeactivateEventArgs.cs new file mode 100644 index 0000000..06cbc25 --- /dev/null +++ b/Toolkit.Foundation/DeactivateEventArgs.cs @@ -0,0 +1,16 @@ +namespace Toolkit.Foundation; + +public record DeactivateEventArgs +{ + public TSender? Sender { get; } + + public DeactivateEventArgs(TSender sender) + { + Sender = sender; + } + + public DeactivateEventArgs() + { + + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/DeactivatedEventArgs.cs b/Toolkit.Foundation/DeactivatedEventArgs.cs index 1798aa2..dca5663 100644 --- a/Toolkit.Foundation/DeactivatedEventArgs.cs +++ b/Toolkit.Foundation/DeactivatedEventArgs.cs @@ -13,4 +13,4 @@ public record DeactivatedEventArgs { } -} \ No newline at end of file +} diff --git a/Toolkit.Foundation/IValidation.cs b/Toolkit.Foundation/IValidation.cs index 94cb1e8..8d6d13c 100644 --- a/Toolkit.Foundation/IValidation.cs +++ b/Toolkit.Foundation/IValidation.cs @@ -17,7 +17,10 @@ public interface IValidation : void Clear(); Task Validate(Expression> property, - ValidationRule[] rules); + ValidationRule[] rules); + + Task Validate(string name, + ValidationRule[] rules); Task Validate(); diff --git a/Toolkit.Foundation/Observable.cs b/Toolkit.Foundation/Observable.cs index b7e6846..301005e 100644 --- a/Toolkit.Foundation/Observable.cs +++ b/Toolkit.Foundation/Observable.cs @@ -127,4 +127,4 @@ public partial class Observable : } partial void OnValueChanged(TValue? value) => ValueChanged(); -} \ No newline at end of file +} diff --git a/Toolkit.Foundation/ObservableCollection.cs b/Toolkit.Foundation/ObservableCollection.cs index fb4d382..2ab01a9 100644 --- a/Toolkit.Foundation/ObservableCollection.cs +++ b/Toolkit.Foundation/ObservableCollection.cs @@ -619,7 +619,7 @@ public partial class ObservableCollection : partial void OnValueChanged(TValue? value) => OnChanged(value); } -public partial class ObservableCollection : +public partial class ObservableCollection : ObservableCollection where TViewModel : IDisposable { diff --git a/Toolkit.Foundation/ObservableConfiguration.cs b/Toolkit.Foundation/ObservableConfiguration.cs new file mode 100644 index 0000000..792e2a2 --- /dev/null +++ b/Toolkit.Foundation/ObservableConfiguration.cs @@ -0,0 +1,34 @@ +using CommunityToolkit.Mvvm.Messaging; + +namespace Toolkit.Foundation; + +public partial class ObservableConfiguration(IServiceProvider provider, + IServiceFactory factory, + IMessenger messenger, + IDisposer disposer, + TConfiguration configuration, + IWritableConfiguration writer, + Func read, + Action write) : + Observable(provider, factory, messenger, disposer), + IHandler> + where TConfiguration : class +{ + public void Handle(ChangedEventArgs args) + { + if (args.Sender is TConfiguration configuration) + { + Value = read(configuration); + } + } + + protected override void Activated() => Value = read(configuration); + + protected override void Changed(TValue? value) + { + if (IsActive) + { + writer.Write(args => write(value, args)); + } + } +} \ No newline at end of file diff --git a/Toolkit.Foundation/ObservableConfigurationCollection.cs b/Toolkit.Foundation/ObservableConfigurationCollection.cs new file mode 100644 index 0000000..55f8978 --- /dev/null +++ b/Toolkit.Foundation/ObservableConfigurationCollection.cs @@ -0,0 +1,76 @@ +using CommunityToolkit.Mvvm.Messaging; + +namespace Toolkit.Foundation; + +public partial class ObservableConfigurationCollection : + ObservableCollection, + IHandler> + where TConfiguration : class + where TViewModel : notnull, + IDisposable +{ + private readonly TConfiguration configuration; + private readonly Func read; + private readonly Action write; + private readonly IWritableConfiguration writer; + + public ObservableConfigurationCollection(IServiceProvider provider, + IServiceFactory factory, + IMessenger messenger, + IDisposer disposer, + TConfiguration configuration, + IWritableConfiguration writer, + Func read, + Action write, + TValue? value = default) : base(provider, factory, messenger, disposer, value) + { + this.configuration = configuration; + this.writer = writer; + this.read = read; + this.write = write; + + Value = value; + } + + public ObservableConfigurationCollection(IServiceProvider provider, + IServiceFactory factory, + IMessenger messenger, + IDisposer disposer, + IEnumerable items, + TConfiguration configuration, + IWritableConfiguration writer, + Func read, + Action write, + TValue? value = default) : base(provider, factory, messenger, disposer, items, value) + { + this.configuration = configuration; + this.writer = writer; + this.read = read; + this.write = write; + + Value = value; + } + + public void Handle(ChangedEventArgs args) + { + if (args.Sender is TConfiguration configuration) + { + Value = read(configuration); + } + } + + protected override void Activated() + { + Value = read(configuration); + } + + protected override void OnChanged(TValue? value) + { + if (IsActive) + { + writer.Write(args => write(value, args)); + } + + base.OnChanged(value); + } +} diff --git a/Toolkit.Foundation/Result.cs b/Toolkit.Foundation/Result.cs index 8b3dd5f..d826867 100644 --- a/Toolkit.Foundation/Result.cs +++ b/Toolkit.Foundation/Result.cs @@ -8,26 +8,32 @@ public record Result : protected internal Result(TValue? value, bool isSuccess, Error error) : base(isSuccess, error) => this.value = value; - public TValue? Value => IsSuccess ? value! : default; + public TValue? Value => + IsSuccess ? value! : default; - public static implicit operator Result(TValue? value) => Create(value); + public static implicit operator Result(TValue? value) => + Create(value); } - -public record Result(bool IsSuccess, Error Error) +public record Result(bool IsSuccess, + Error Error) { public bool IsFailure => !IsSuccess; public static Result Success() => new(true, Error.None); - public static Result Success(TValue value) => new(value, true, Error.None); + public static Result Success(TValue value) => + new(value, true, Error.None); - public static Result Failure(Error error) => new(false, error); + public static Result Failure(Error error) => + new(false, error); - public static Result Failure(Error error) => new(default, false, error); + public static Result Failure(Error error) => + new(default, false, error); - public static Result Create(bool condition) => condition ? Success() : Failure(Error.ConditionNotMet); - - public static Result Create(TValue? value) => value is not null ? Success(value) : Failure(Error.Null); + public static Result Create(bool condition) => + condition ? Success() : Failure(Error.ConditionNotMet); + public static Result Create(TValue? value) => + value is not null ? Success(value) : Failure(Error.Null); } \ No newline at end of file diff --git a/Toolkit.Foundation/Validation.cs b/Toolkit.Foundation/Validation.cs index 778c17e..e943fd4 100644 --- a/Toolkit.Foundation/Validation.cs +++ b/Toolkit.Foundation/Validation.cs @@ -3,32 +3,24 @@ using System.Linq.Expressions; namespace Toolkit.Foundation; -public class Validation(IValidatorCollection validators) : - IValidation +public class Validation(IValidatorCollection validators) : IValidation { private readonly ValidationErrorCollection errors = []; public event PropertyChangedEventHandler? PropertyChanged; - public IReadOnlyIndexDictionary Errors => + public IReadOnlyIndexDictionary Errors => new ReadOnlyIndexDictionary(errors); - public bool HasErrors => - Errors.Count > 0; + public bool HasErrors => Errors.Count > 0; internal IValidatorCollection Validators { get; } = validators; - public void Add(Expression> property, - ValidationRule[] rules, - ValidationTrigger trigger = ValidationTrigger.Deferred) + public void Add(Expression> property, ValidationRule[] rules, ValidationTrigger trigger = ValidationTrigger.Deferred) { - string? name = GetPropertyName(property); + string name = GetPropertyName(property); Validators.Add(name, new Validator(name, rules)); - - if (trigger is ValidationTrigger.Immediate) - { - _ = Validate(name); - } + if (trigger == ValidationTrigger.Immediate) _ = Validate(name); } public void Clear() @@ -37,93 +29,64 @@ public class Validation(IValidatorCollection validators) : OnPropertyChanged(nameof(Errors), null, null); } - public virtual void OnPropertyChanged(string propertyName, - object? before, object? after) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } + public Task Validate(Expression> property, ValidationRule[] rules) => + ValidateInternal(new Validator(GetPropertyName(property), rules), GetPropertyName(property)); - public async Task Validate(Expression> property, - ValidationRule[] rules) - { - string? name = GetPropertyName(property); - Validator validator = new(name, rules); + public Task Validate(string name, ValidationRule[] rules) => + ValidateInternal(new Validator(name, rules), name); - Clear(name); - - (bool isValid, string? message) = await validator.TryValidate(); - - if (!isValid) - { - errors[name] = message ?? ""; - } - - OnPropertyChanged(nameof(Errors), null, null); - OnPropertyChanged(nameof(HasErrors), null, null); - - return !HasErrors; - } - - public async Task Validate(string name) - { - Clear(name); - - if (Validators.TryGet(name, out Validator? validator)) - { - if (validator is not null) - { - (bool isValid, string? message) = await validator.TryValidate(); - if (!isValid) - { - errors[name] = message ?? ""; - } - } - } - - OnPropertyChanged(nameof(Errors), null, null); - OnPropertyChanged(nameof(HasErrors), null, null); - - return !HasErrors; - } + public Task Validate(string name) => + Validators.TryGet(name, out var validator) && validator != null + ? ValidateInternal(validator, name) + : Task.FromResult(true); public async Task Validate() { Clear(); - foreach (Validator? validator in Validators) + foreach (var validator in Validators) { if (validator.PropertyName is string name) { (bool isValid, string? message) = await validator.TryValidate(); - if (!isValid) - { - errors[name] = message ?? ""; - } + if (!isValid) errors[name] = message ?? string.Empty; } } OnPropertyChanged(nameof(Errors), null, null); OnPropertyChanged(nameof(HasErrors), null, null); - return !HasErrors; } + private async Task ValidateInternal(Validator validator, string name) + { + Clear(name); + + (bool isValid, string? message) = await validator.TryValidate(); + if (!isValid) errors[name] = message ?? string.Empty; + + OnPropertyChanged(nameof(Errors), null, null); + OnPropertyChanged(nameof(HasErrors), null, null); + return isValid; + } + private void Clear(string name) { - if (Errors.ContainsKey(name)) + if (errors is not null && errors.ContainsKey(name)) { errors.Remove(name); OnPropertyChanged(nameof(Errors), null, null); } } - private string GetPropertyName(Expression> expression) - { - return expression.Body switch + private static string GetPropertyName(Expression> expression) => + expression.Body switch { MemberExpression memberExpression => memberExpression.Member.Name, UnaryExpression unaryExpression when unaryExpression.Operand is MemberExpression operand => operand.Member.Name, _ => string.Empty }; - } -} \ No newline at end of file + + public virtual void OnPropertyChanged(string propertyName, object? before, object? after) => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); +} diff --git a/Toolkit.UI.WinUI/IsStringNotNullOrEmptyConverter.cs b/Toolkit.UI.WinUI/IsStringNotNullOrEmptyConverter.cs new file mode 100644 index 0000000..2e36186 --- /dev/null +++ b/Toolkit.UI.WinUI/IsStringNotNullOrEmptyConverter.cs @@ -0,0 +1,10 @@ +using System; + +namespace Toolkit.UI.WinUI; + +public class IsStringNotNullOrEmptyConverter : + ValueConverter +{ + protected override bool ConvertTo(string value, Type? targetType, object? parameter, string? language) => + !string.IsNullOrEmpty(value); +} \ No newline at end of file diff --git a/Toolkit.UI.WinUI/StringFormatConverter.cs b/Toolkit.UI.WinUI/StringFormatConverter.cs index 07e6a6f..ccc77ee 100644 --- a/Toolkit.UI.WinUI/StringFormatConverter.cs +++ b/Toolkit.UI.WinUI/StringFormatConverter.cs @@ -4,24 +4,17 @@ using System.Globalization; namespace Toolkit.UI.WinUI; public class StringFormatConverter : - ValueConverter + ValueConverter { public string? StringFormat { get; set; } - protected override object? ConvertTo(object value, + protected override object? ConvertTo(object? value, Type? targetType, object? parameter, string? language) { - if (value is null) - { - return null!; - } - - if (string.IsNullOrEmpty(StringFormat)) - { - return value.ToString()!; - } + if (value is null) return null!; + if (StringFormat is not { Length: > 0}) return $"{value}"!; try { diff --git a/Toolkit.UI.WinUI/ValidationBooleanConverter.cs b/Toolkit.UI.WinUI/ValidationBooleanConverter.cs new file mode 100644 index 0000000..91979e8 --- /dev/null +++ b/Toolkit.UI.WinUI/ValidationBooleanConverter.cs @@ -0,0 +1,27 @@ +using System; +using Toolkit.Foundation; + +namespace Toolkit.UI.WinUI; + +public class ValidationBooleanConverter : + ValueConverter, bool> +{ + public string? Property { get; set; } + + public bool TrueValue { get; set; } = true; + + public bool FalseValue { get; set; } = false; + + protected override bool ConvertTo(IReadOnlyIndexDictionary value, + Type? targetType, + object? parameter, + string? language) + { + if (Property is { Length: > 0 } && value.ContainsKey(Property)) + { + return TrueValue; + } + + return FalseValue; + } +} diff --git a/Toolkit.UI.WinUI/ValidationMessageConverter.cs b/Toolkit.UI.WinUI/ValidationMessageConverter.cs new file mode 100644 index 0000000..4f634d2 --- /dev/null +++ b/Toolkit.UI.WinUI/ValidationMessageConverter.cs @@ -0,0 +1,23 @@ +using System; +using Toolkit.Foundation; + +namespace Toolkit.UI.WinUI; + +public class ValidationMessageConverter : + ValueConverter, string?> +{ + public string? Property { get; set; } + + protected override string? ConvertTo(IReadOnlyIndexDictionary value, + Type? targetType, + object? parameter, + string? language) + { + if (Property is { Length: > 0 } && value.TryGetValue(Property, out string? message)) + { + return message; + } + + return default; + } +}