Updated to NetStandard and improved code.
This commit is contained in:
Родитель
33535cf0cc
Коммит
7e9278e0e4
|
@ -4,6 +4,7 @@
|
|||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
|
@ -19,6 +20,8 @@
|
|||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
|
@ -52,7 +55,6 @@ BenchmarkDotNet.Artifacts/
|
|||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
@ -60,7 +62,7 @@ StyleCopReport.xml
|
|||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
|
@ -77,6 +79,7 @@ StyleCopReport.xml
|
|||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
|
@ -208,7 +211,7 @@ _pkginfo.txt
|
|||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
|
@ -221,13 +224,15 @@ ClientBin/
|
|||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true
|
||||
**/wwwroot/lib/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
@ -252,6 +257,7 @@ ServiceFabricBackup/
|
|||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- Backup*.rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
@ -291,8 +297,8 @@ paket-files/
|
|||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
|
@ -317,7 +323,7 @@ __pycache__/
|
|||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
|
@ -326,5 +332,14 @@ ASALocalRun/
|
|||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
|
@ -0,0 +1,15 @@
|
|||
using ReactiveUI.Validation.Contexts;
|
||||
|
||||
namespace ReactiveUI.Validation.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface used by view models to indicate they have a validation context.
|
||||
/// </summary>
|
||||
public interface ISupportsValidation
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the validation context
|
||||
/// </summary>
|
||||
ValidationContext ValidationContext { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ReactiveUI.Validation.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Container for validation text
|
||||
/// </summary>
|
||||
public class ValidationText : IEnumerable<string>
|
||||
{
|
||||
private readonly List<string> _texts = new List<string>();
|
||||
|
||||
public ValidationText()
|
||||
{
|
||||
}
|
||||
|
||||
public ValidationText(string text)
|
||||
{
|
||||
_texts.Add(text);
|
||||
}
|
||||
|
||||
public ValidationText(IEnumerable<ValidationText> validationTexts)
|
||||
{
|
||||
foreach (var vt in validationTexts) _texts.AddRange(vt._texts);
|
||||
}
|
||||
|
||||
public string this[int index] => _texts[index];
|
||||
|
||||
public int Count => _texts.Count;
|
||||
|
||||
public IEnumerator<string> GetEnumerator()
|
||||
{
|
||||
return _texts.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public void Add(string text)
|
||||
{
|
||||
_texts.Add(text);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_texts.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert representation to a single line using a specified separator
|
||||
/// </summary>
|
||||
/// <param name="separator"></param>
|
||||
/// <returns></returns>
|
||||
public string ToSingleLine(string separator = ",")
|
||||
{
|
||||
return string.Join(separator, _texts);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ReactiveUI.Validation.States;
|
||||
|
||||
namespace ReactiveUI.Validation.Comparators
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Utility class used to compare <see cref="T:ReactiveUI.Validation.States.ValidationState" /> instances.
|
||||
/// </summary>
|
||||
public class ValidationStateComparer : EqualityComparer<ValidationState>
|
||||
{
|
||||
public override bool Equals(ValidationState x, ValidationState y)
|
||||
{
|
||||
if (x == null && y == null) return true;
|
||||
|
||||
if (x == null || y == null) return false;
|
||||
|
||||
return x.IsValid == y.IsValid && x.Text.ToSingleLine() == y.Text.ToSingleLine() &&
|
||||
x.Component == y.Component;
|
||||
}
|
||||
|
||||
public override int GetHashCode(ValidationState obj)
|
||||
{
|
||||
if (obj == null) throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
return obj.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using ReactiveUI.Validation.Collections;
|
||||
using ReactiveUI.Validation.States;
|
||||
|
||||
namespace ReactiveUI.Validation.Components.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// Core interface which all validation components must implement.
|
||||
/// </summary>
|
||||
public interface IValidationComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the Current,optional validation message
|
||||
/// </summary>
|
||||
ValidationText Text { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the current validation state
|
||||
/// </summary>
|
||||
bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the observable for validation state changes.
|
||||
/// </summary>
|
||||
IObservable<ValidationState> ValidationStatusChange { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using ReactiveUI.Validation.Collections;
|
||||
using ReactiveUI.Validation.Comparators;
|
||||
using ReactiveUI.Validation.Components.Abstractions;
|
||||
using ReactiveUI.Validation.States;
|
||||
|
||||
namespace ReactiveUI.Validation.Components
|
||||
{
|
||||
/// <inheritdoc cref="ReactiveObject" />
|
||||
/// <inheritdoc cref="IDisposable" />
|
||||
/// <inheritdoc cref="IValidationComponent" />
|
||||
/// <summary>
|
||||
/// Base class for items which are used to build a <see cref="T:ReactiveUI.Validation.Contexts.ValidationContext" />
|
||||
/// </summary>
|
||||
public abstract class BasePropertyValidation<TViewModel> : ReactiveObject, IDisposable, IValidationComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The items to be disposed.
|
||||
/// </summary>
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// The current valid state
|
||||
/// </summary>
|
||||
private readonly ReplaySubject<bool> _isValidSubject = new ReplaySubject<bool>(1);
|
||||
|
||||
/// <summary>
|
||||
/// The list of property names this validator
|
||||
/// </summary>
|
||||
private readonly HashSet<string> _propertyNames = new HashSet<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The connected observable to kick off seeing <see cref="ValidationStatusChange" />
|
||||
/// </summary>
|
||||
private IConnectableObservable<ValidationState> _connectedChange;
|
||||
|
||||
private bool _isConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Our current validity state
|
||||
/// </summary>
|
||||
private bool _isValid;
|
||||
|
||||
private ValidationText _text;
|
||||
|
||||
protected BasePropertyValidation()
|
||||
{
|
||||
// subscribe to the valid subject so we can assign the validity
|
||||
_disposables.Add(_isValidSubject.Subscribe(v => _isValid = v));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the total number of properties referenced.
|
||||
/// </summary>
|
||||
public int PropertyCount => _propertyNames.Count;
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_disposables?.Dispose();
|
||||
}
|
||||
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
Activate();
|
||||
return _isValid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The public mechanism indicating that the validation state has changed.
|
||||
/// </summary>
|
||||
public IObservable<ValidationState> ValidationStatusChange
|
||||
{
|
||||
get
|
||||
{
|
||||
Activate();
|
||||
|
||||
return _connectedChange;
|
||||
}
|
||||
}
|
||||
|
||||
public ValidationText Text
|
||||
{
|
||||
get
|
||||
{
|
||||
Activate();
|
||||
|
||||
return _text;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determine if a property name is actually contained within this
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="exclusively"></param>
|
||||
/// <returns></returns>
|
||||
public bool ContainsProperty<TProp>(Expression<Func<TViewModel, TProp>> property, bool exclusively = false)
|
||||
{
|
||||
var propertyName = property.Body.ToString();
|
||||
|
||||
return exclusively
|
||||
? _propertyNames.Contains(propertyName) && _propertyNames.Count == 1
|
||||
: _propertyNames.Contains(propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a property to the list of this which this validation is associated with.
|
||||
/// </summary>
|
||||
/// <typeparam name="TProp"></typeparam>
|
||||
/// <param name="property"></param>
|
||||
protected void AddProperty<TProp>(Expression<Func<TViewModel, TProp>> property)
|
||||
{
|
||||
var propertyName = property.Body.ToString();
|
||||
|
||||
_propertyNames.Add(propertyName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the validation change observable, implemented by concrete classes.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected abstract IObservable<ValidationState> GetValidationChangeObservable();
|
||||
|
||||
private void Activate()
|
||||
{
|
||||
if (_isConnected)
|
||||
return;
|
||||
|
||||
_connectedChange = GetValidationChangeObservable()
|
||||
.Do(state =>
|
||||
{
|
||||
_isValid = state.IsValid;
|
||||
_text = state.Text;
|
||||
})
|
||||
.Replay(1);
|
||||
|
||||
_disposables.Add(_connectedChange.Connect());
|
||||
|
||||
_isConnected = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Property validator for a single view model property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TProperty1"></typeparam>
|
||||
public sealed class BasePropertyValidation<TViewModel, TProperty1> : BasePropertyValidation<TViewModel>
|
||||
{
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// The message to be constructed.
|
||||
/// </summary>
|
||||
private readonly Func<TProperty1, bool, ValidationText> _message;
|
||||
|
||||
private readonly IConnectableObservable<TProperty1> _valueConnectedObservable;
|
||||
|
||||
/// <summary>
|
||||
/// The value calculated from the properties.
|
||||
/// </summary>
|
||||
private readonly ReplaySubject<TProperty1> _valueSubject = new ReplaySubject<TProperty1>(1);
|
||||
|
||||
private bool _isConnected;
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> viewModelProperty,
|
||||
Func<TProperty1, bool> isValidFunc,
|
||||
string message) : this(viewModel,
|
||||
viewModelProperty, isValidFunc, (p, v) => new ValidationText(v ? string.Empty : message))
|
||||
{
|
||||
}
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> viewModelProperty,
|
||||
Func<TProperty1, bool> isValidFunc,
|
||||
Func<TProperty1, string> message) :
|
||||
this(viewModel, viewModelProperty, isValidFunc,
|
||||
(p, v) => new ValidationText(v ? string.Empty : message(p)))
|
||||
{
|
||||
}
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> viewModelProperty,
|
||||
Func<TProperty1, bool> isValidFunc,
|
||||
Func<TProperty1, bool, string> messageFunc) : this(viewModel, viewModelProperty,
|
||||
isValidFunc, (prop1, isValid) => new ValidationText(messageFunc(prop1, isValid)))
|
||||
{
|
||||
}
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> viewModelProperty,
|
||||
Func<TProperty1, bool> isValidFunc,
|
||||
Func<TProperty1, bool, ValidationText> messageFunc)
|
||||
{
|
||||
// Now, we have a function, which, in this case uses the value of the view Model Property...
|
||||
IsValidFunc = isValidFunc;
|
||||
|
||||
// Record this property name
|
||||
AddProperty(viewModelProperty);
|
||||
|
||||
// The function invoked
|
||||
_message = messageFunc;
|
||||
|
||||
// Our connected observable
|
||||
_valueConnectedObservable = viewModel.WhenAny(viewModelProperty, v => v.Value).DistinctUntilChanged()
|
||||
.Multicast(_valueSubject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The mechanism to determine if the property(s) is valid or not
|
||||
/// </summary>
|
||||
private Func<TProperty1, bool> IsValidFunc { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Get the validation change observable.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override IObservable<ValidationState> GetValidationChangeObservable()
|
||||
{
|
||||
Activate();
|
||||
|
||||
return _valueSubject.Select(value => new ValidationState(IsValidFunc(value), GetMessage(value), this))
|
||||
.DistinctUntilChanged(new ValidationStateComparer());
|
||||
}
|
||||
|
||||
private void Activate()
|
||||
{
|
||||
if (_isConnected)
|
||||
return;
|
||||
|
||||
_disposables.Add(_valueConnectedObservable.Connect());
|
||||
|
||||
_isConnected = true;
|
||||
}
|
||||
|
||||
private ValidationText GetMessage(TProperty1 value)
|
||||
{
|
||||
// Need something subtle to deal with validity having not actual message
|
||||
return _message(value, IsValidFunc(value));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using ReactiveUI.Validation.Collections;
|
||||
using ReactiveUI.Validation.Components.Abstractions;
|
||||
using ReactiveUI.Validation.States;
|
||||
|
||||
namespace ReactiveUI.Validation.Components
|
||||
{
|
||||
/// <inheritdoc cref="ReactiveObject" />
|
||||
/// <inheritdoc cref="IValidationComponent" />
|
||||
/// <inheritdoc cref="IDisposable" />
|
||||
/// <summary>
|
||||
/// More generic observable for determination of validity
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// We probably need a more 'complex' one, where the params of the validation block are
|
||||
/// passed through ?
|
||||
/// Also, what about access to the view model to output the error message ?
|
||||
/// </remarks>
|
||||
public class ModelObservableValidation<TViewModel> : ReactiveObject, IValidationComponent, IDisposable
|
||||
{
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
private readonly ReplaySubject<ValidationState> _lastValidationStateSubject =
|
||||
new ReplaySubject<ValidationState>(1);
|
||||
|
||||
// the underlying connected observable for the validation change which is published
|
||||
private readonly IConnectableObservable<ValidationState> _validityConnectedObservable;
|
||||
|
||||
private bool _isActive;
|
||||
private bool _isValid;
|
||||
|
||||
private ValidationText _text;
|
||||
|
||||
|
||||
public ModelObservableValidation(TViewModel model,
|
||||
Func<TViewModel, IObservable<bool>> validityObservable,
|
||||
Func<TViewModel, bool, string> messageFunc) : this(model, validityObservable,
|
||||
(vm, state) => new ValidationText(messageFunc(vm, state)))
|
||||
{
|
||||
}
|
||||
|
||||
public ModelObservableValidation(TViewModel model,
|
||||
Func<TViewModel, IObservable<bool>> validityObservable,
|
||||
Func<TViewModel, bool, ValidationText> messageFunc)
|
||||
{
|
||||
_disposables.Add(_lastValidationStateSubject.Do(s =>
|
||||
{
|
||||
_isValid = s.IsValid;
|
||||
_text = s.Text;
|
||||
}).Subscribe());
|
||||
|
||||
_validityConnectedObservable = Observable.Defer(() => validityObservable(model))
|
||||
.Select(v => new ValidationState(v, messageFunc(model, v), this))
|
||||
.Multicast(_lastValidationStateSubject);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposables?.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Get the current validation text
|
||||
/// </summary>
|
||||
public ValidationText Text
|
||||
{
|
||||
get
|
||||
{
|
||||
Activate();
|
||||
return _text;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Get the current state
|
||||
/// </summary>
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
Activate();
|
||||
return _isValid;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Get the observable for <see cref="T:ReactiveUI.Validation.States.ValidationState" /> changes
|
||||
/// </summary>
|
||||
public IObservable<ValidationState> ValidationStatusChange
|
||||
{
|
||||
get
|
||||
{
|
||||
Activate();
|
||||
return _validityConnectedObservable;
|
||||
}
|
||||
}
|
||||
|
||||
private void Activate()
|
||||
{
|
||||
if (_isActive)
|
||||
return;
|
||||
|
||||
_isActive = true;
|
||||
_disposables.Add(_validityConnectedObservable.Connect());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using ReactiveUI.Legacy;
|
||||
using ReactiveUI.Validation.Collections;
|
||||
using ReactiveUI.Validation.Components.Abstractions;
|
||||
using ReactiveUI.Validation.States;
|
||||
|
||||
namespace ReactiveUI.Validation.Contexts
|
||||
{
|
||||
/// <inheritdoc cref="ReactiveObject" />
|
||||
/// <inheritdoc cref="IDisposable" />
|
||||
/// <inheritdoc cref="IValidationComponent" />
|
||||
/// <summary>
|
||||
/// The overall context for a view model under which validation takes place.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Contains all of the <see cref="T:ReactiveUI.Validation.Components.Contracts.IValidationComponent" /> instances
|
||||
/// applicable to the view model.
|
||||
/// </remarks>
|
||||
public class ValidationContext : ReactiveObject, IDisposable, IValidationComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// What needs to be disposed off
|
||||
/// </summary>
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// Backing field for the current validation state
|
||||
/// </summary>
|
||||
private readonly ObservableAsPropertyHelper<bool> _isValid;
|
||||
|
||||
private readonly IConnectableObservable<bool> _validationConnectable;
|
||||
|
||||
/// <summary>
|
||||
/// The list of current validations
|
||||
/// </summary>
|
||||
private readonly ReactiveList<IValidationComponent> _validations = new ReactiveList<IValidationComponent>();
|
||||
|
||||
private readonly ReplaySubject<ValidationState> _validationStatusChange = new ReplaySubject<ValidationState>(1);
|
||||
|
||||
/// <summary>
|
||||
/// Backing field for the validation summary
|
||||
/// </summary>
|
||||
private readonly ObservableAsPropertyHelper<ValidationText> _validationText;
|
||||
|
||||
/// <summary>
|
||||
/// Subject for validality of the context.
|
||||
/// </summary>
|
||||
private readonly ReplaySubject<bool> _validSubject = new ReplaySubject<bool>(1);
|
||||
|
||||
private bool _isActive;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Create the context
|
||||
/// </summary>
|
||||
public ValidationContext()
|
||||
{
|
||||
// publish the current validation state
|
||||
_disposables.Add(_validSubject.StartWith(true).ToProperty(this, m => m.IsValid, out _isValid));
|
||||
|
||||
// when a change occurs in the validation state, publish the updated validation text
|
||||
_disposables.Add(_validSubject.StartWith(true).Select(v => BuildText())
|
||||
.ToProperty(this, m => m.Text, out _validationText, new ValidationText()));
|
||||
|
||||
//publish the current validation state
|
||||
_disposables.Add(_validSubject.Select(v => new ValidationState(IsValid, BuildText(), this))
|
||||
.Do(vc => _validationStatusChange.OnNext(vc)).Subscribe());
|
||||
|
||||
|
||||
// observe the defined validations and whenever there is a change publish the current validation state.
|
||||
_validationConnectable = _validations.CountChanged.StartWith(0)
|
||||
.Select(_ =>
|
||||
_validations
|
||||
.Select(v => v.ValidationStatusChange).Merge()
|
||||
.Select(o => Unit.Default).StartWith(Unit.Default))
|
||||
.Switch()
|
||||
.Select(_ => GetIsValid()).Multicast(_validSubject);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An observable for the Valid state
|
||||
/// </summary>
|
||||
public IObservable<bool> Valid
|
||||
{
|
||||
get
|
||||
{
|
||||
Activate();
|
||||
|
||||
return _validSubject.AsObservable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of validations
|
||||
/// </summary>
|
||||
public IReadOnlyReactiveList<IValidationComponent> Validations => _validations;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposables?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get whether currently valid or not
|
||||
/// </summary>
|
||||
public bool IsValid
|
||||
{
|
||||
get
|
||||
{
|
||||
Activate();
|
||||
return _isValid.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public IObservable<ValidationState> ValidationStatusChange
|
||||
{
|
||||
get
|
||||
{
|
||||
Activate();
|
||||
return _validationStatusChange.AsObservable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Get the current validation summary.
|
||||
/// </summary>
|
||||
public ValidationText Text
|
||||
{
|
||||
get
|
||||
{
|
||||
Activate();
|
||||
return _validationText.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void Activate()
|
||||
{
|
||||
if (_isActive)
|
||||
return;
|
||||
|
||||
_isActive = true;
|
||||
_disposables.Add(_validationConnectable.Connect());
|
||||
}
|
||||
|
||||
public void Add(IValidationComponent validation)
|
||||
{
|
||||
_validations.Add(validation);
|
||||
}
|
||||
|
||||
public bool GetIsValid()
|
||||
{
|
||||
var isValid = _validations.Count == 0 || _validations.All(v => v.IsValid);
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build a list of the validation text for each invalid component
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private ValidationText BuildText()
|
||||
{
|
||||
return new ValidationText(_validations.Where(p => !p.IsValid).Select(p => p.Text));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using ReactiveUI.Validation.Abstractions;
|
||||
using ReactiveUI.Validation.Components;
|
||||
using ReactiveUI.Validation.Helpers;
|
||||
|
||||
namespace ReactiveUI.Validation.Extensions
|
||||
{
|
||||
public static class SupportsValidationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Validation rule for a property of a view model
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TViewModelProp"></typeparam>
|
||||
/// <param name="viewModel"></param>
|
||||
/// <param name="viewModelProperty"></param>
|
||||
/// <param name="viewPropertyValid"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
public static ValidationHelper ValidationRule<TViewModel, TViewModelProp>(this TViewModel viewModel,
|
||||
Expression<Func<TViewModel,
|
||||
TViewModelProp>>
|
||||
viewModelProperty,
|
||||
Func<TViewModelProp, bool>
|
||||
viewPropertyValid,
|
||||
string message)
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
{
|
||||
// we need to associate the view model property
|
||||
// with something that can be easily looked up and bound to
|
||||
|
||||
var propValidation = new BasePropertyValidation<TViewModel, TViewModelProp>(viewModel, viewModelProperty,
|
||||
viewPropertyValid, message);
|
||||
|
||||
viewModel.ValidationContext.Add(propValidation);
|
||||
|
||||
var validationHelper = new ValidationHelper(propValidation);
|
||||
|
||||
return validationHelper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup a validation rule for a specified view model property
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TViewModelProp"></typeparam>
|
||||
/// <param name="viewModel"></param>
|
||||
/// <param name="viewModelProperty"></param>
|
||||
/// <param name="viewPropertyValid"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <returns></returns>
|
||||
public static ValidationHelper ValidationRule<TViewModel, TViewModelProp>(this TViewModel viewModel,
|
||||
Expression<Func<TViewModel,
|
||||
TViewModelProp>>
|
||||
viewModelProperty,
|
||||
Func<TViewModelProp, bool>
|
||||
viewPropertyValid,
|
||||
Func<TViewModelProp, string> message)
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
{
|
||||
// we need to associate the view model property
|
||||
// with something that can be easily looked up and bound to
|
||||
|
||||
var propValidation = new BasePropertyValidation<TViewModel, TViewModelProp>(viewModel, viewModelProperty,
|
||||
viewPropertyValid, message);
|
||||
|
||||
viewModel.ValidationContext.Add(propValidation);
|
||||
|
||||
return new ValidationHelper(propValidation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Setup a rule with a general observable indicating validity.
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <param name="viewModel"></param>
|
||||
/// <param name="validationObservableFunc"></param>
|
||||
/// <param name="messageFunc"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// It should be noted that the observable should provide an initial value, otherwise that can result in an
|
||||
/// inconsistent performance.
|
||||
/// </remarks>
|
||||
public static ValidationHelper ValidationRule<TViewModel>(this TViewModel viewModel,
|
||||
Func<TViewModel, IObservable<bool>>
|
||||
validationObservableFunc,
|
||||
Func<TViewModel, bool, string> messageFunc)
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
{
|
||||
var validation =
|
||||
new ModelObservableValidation<TViewModel>(viewModel, validationObservableFunc, messageFunc);
|
||||
|
||||
viewModel.ValidationContext.Add(validation);
|
||||
|
||||
return new ValidationHelper(validation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an observable for the validity of the view model.
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <param name="viewModel"></param>
|
||||
/// <returns></returns>
|
||||
public static IObservable<bool> IsValid<TViewModel>(this TViewModel viewModel)
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
{
|
||||
return viewModel.ValidationContext.Valid;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using ReactiveUI.Validation.Components;
|
||||
using ReactiveUI.Validation.Contexts;
|
||||
using ReactiveUI.Validation.TemplateGenerators;
|
||||
|
||||
namespace ReactiveUI.Validation.Extensions
|
||||
{
|
||||
public static class ValidationContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Resolve the property valuation for a specified property
|
||||
/// </summary>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TProperty1"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static BasePropertyValidation<TViewModel, TProperty1> ResolveFor<TViewModel, TProperty1>(
|
||||
this ValidationContext context,
|
||||
Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
|
||||
bool strict = true)
|
||||
{
|
||||
var instance = context.Validations.Where(p => p is BasePropertyValidation<TViewModel, TProperty1>)
|
||||
.Cast<BasePropertyValidation<TViewModel, TProperty1>>()
|
||||
.FirstOrDefault(v => v.ContainsProperty(viewModelProperty1, strict));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static BasePropertyValidation<TViewModel, TProperty1, TProperty2> ResolveFor<TViewModel, TProperty1,
|
||||
TProperty2>(this ValidationContext context,
|
||||
Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
|
||||
Expression<Func<TViewModel, TProperty1>> viewModelProperty2,
|
||||
bool strict = true)
|
||||
{
|
||||
var instance = context
|
||||
.Validations.Where(p => p is BasePropertyValidation<TViewModel, TProperty1, TProperty2>)
|
||||
.Cast<BasePropertyValidation<TViewModel, TProperty1, TProperty2>>().FirstOrDefault(v =>
|
||||
v.ContainsProperty(viewModelProperty1) && v.ContainsProperty(viewModelProperty2) &&
|
||||
v.PropertyCount == 2);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3> ResolveFor<TViewModel,
|
||||
TProperty1, TProperty2, TProperty3>(this ValidationContext context,
|
||||
Expression<Func<TViewModel, TProperty1>> viewModelProperty1,
|
||||
Expression<Func<TViewModel, TProperty1>> viewModelProperty2,
|
||||
Expression<Func<TViewModel, TProperty1>> viewModelProperty3,
|
||||
bool strict = true)
|
||||
{
|
||||
var instance = context
|
||||
.Validations
|
||||
.Where(p => p is BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3>)
|
||||
.Cast<BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3>>()
|
||||
.FirstOrDefault(v =>
|
||||
v.ContainsProperty(viewModelProperty1) && v.ContainsProperty(viewModelProperty2) &&
|
||||
v.ContainsProperty(viewModelProperty3) && v.PropertyCount == 3);
|
||||
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reactive.Linq;
|
||||
using ReactiveUI.Validation.Abstractions;
|
||||
using ReactiveUI.Validation.Helpers;
|
||||
using ReactiveUI.Validation.ValidationBindings;
|
||||
|
||||
namespace ReactiveUI.Validation.Extensions
|
||||
{
|
||||
public static class ViewForExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Bind the specified view property validation to the view property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TViewModelProperty1"></typeparam>
|
||||
/// <typeparam name="TViewProperty"></typeparam>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="viewModel"></param>
|
||||
/// <param name="viewModelProperty1"></param>
|
||||
/// <param name="viewProperty"></param>
|
||||
/// <returns></returns>
|
||||
public static IDisposable BindValidation
|
||||
<TView, TViewModel, TViewModelProperty1, TViewProperty>(this TView view,
|
||||
TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TViewModelProperty1>>
|
||||
viewModelProperty1,
|
||||
Expression<Func<TView, TViewProperty>> viewProperty)
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
where TView : IViewFor<TViewModel>
|
||||
{
|
||||
return ValidationBinding.ForProperty(view, viewModelProperty1, viewProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind the overall validation of a view model to a specified view property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TViewProperty"></typeparam>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="viewModel"></param>
|
||||
/// <param name="viewProperty"></param>
|
||||
/// <returns></returns>
|
||||
public static IDisposable BindValidation
|
||||
<TView, TViewModel, TViewProperty>(this TView view,
|
||||
TViewModel viewModel,
|
||||
Expression<Func<TView, TViewProperty>> viewProperty)
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
where TView : IViewFor<TViewModel>
|
||||
{
|
||||
return ValidationBinding.ForViewModel<TView, TViewModel, TViewProperty>(view, viewProperty);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Bind a <see cref="ValidationHelper" /> from a view model to a specified view property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TViewProperty"></typeparam>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="viewModel"></param>
|
||||
/// <param name="viewModelHelperProperty"></param>
|
||||
/// <param name="viewProperty"></param>
|
||||
/// <returns></returns>
|
||||
public static IDisposable BindValidation
|
||||
<TView, TViewModel, TViewProperty>(this TView view,
|
||||
TViewModel viewModel,
|
||||
Expression<Func<TViewModel, ValidationHelper>> viewModelHelperProperty,
|
||||
Expression<Func<TView, TViewProperty>> viewProperty)
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
where TView : IViewFor<TViewModel>
|
||||
{
|
||||
return ValidationBinding.ForValidationHelperProperty(view, viewModelHelperProperty, viewProperty);
|
||||
}
|
||||
|
||||
|
||||
public static IDisposable BindToDirect
|
||||
<TTarget, TValue>(IObservable<TValue> This, TTarget target, Expression viewExpression)
|
||||
{
|
||||
var setter = Reflection.GetValueSetterOrThrow(viewExpression.GetMemberInfo());
|
||||
if (viewExpression.GetParent().NodeType == ExpressionType.Parameter)
|
||||
return This.Subscribe(
|
||||
x => setter(target, x, viewExpression.GetArgumentsArray()),
|
||||
ex =>
|
||||
{
|
||||
//this.Log().ErrorException(String.Format("{0} Binding received an Exception!", viewExpression), ex);
|
||||
});
|
||||
|
||||
var bindInfo = This.CombineLatest(target.WhenAnyDynamic(viewExpression.GetParent(), x => x.Value),
|
||||
(val, host) => new {val, host});
|
||||
|
||||
return bindInfo
|
||||
.Where(x => x.host != null)
|
||||
.Subscribe(
|
||||
x => setter(x.host, x.val, viewExpression.GetArgumentsArray()),
|
||||
ex =>
|
||||
{
|
||||
//this.Log().ErrorException(String.Format("{0} Binding received an Exception!", viewExpression), ex);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using ReactiveUI.Validation.Collections;
|
||||
|
||||
namespace ReactiveUI.Validation.Formatters.Abstractions
|
||||
{
|
||||
/// <summary>
|
||||
/// Specification for a <see cref="ValidationText" /> formatter.
|
||||
/// </summary>
|
||||
/// <typeparam name="TOut"></typeparam>
|
||||
public interface IValidationTextFormatter<out TOut>
|
||||
{
|
||||
TOut Format(ValidationText validationText);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
using ReactiveUI.Validation.Collections;
|
||||
using ReactiveUI.Validation.Formatters.Abstractions;
|
||||
|
||||
namespace ReactiveUI.Validation.Formatters
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Helper class to generate a single formatted line for a
|
||||
/// <see cref="T:ReactiveUI.Validation.Collections.ValidationText" />
|
||||
/// </summary>
|
||||
public class SingleLineFormatter : IValidationTextFormatter<string>
|
||||
{
|
||||
private readonly string _separator;
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance with an optional, custom separator.
|
||||
/// </summary>
|
||||
/// <param name="separator"></param>
|
||||
public SingleLineFormatter(string separator = null)
|
||||
{
|
||||
_separator = separator;
|
||||
}
|
||||
|
||||
public static SingleLineFormatter Default { get; } = new SingleLineFormatter();
|
||||
|
||||
public string Format(ValidationText validationText)
|
||||
{
|
||||
return validationText != null ? validationText.ToSingleLine(_separator) : string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using ReactiveUI.Validation.Collections;
|
||||
using ReactiveUI.Validation.Components.Abstractions;
|
||||
using ReactiveUI.Validation.States;
|
||||
|
||||
namespace ReactiveUI.Validation.Helpers
|
||||
{
|
||||
/// <inheritdoc cref="ReactiveObject" />
|
||||
/// <inheritdoc cref="IDisposable" />
|
||||
/// <summary>
|
||||
/// Encapsulation of a validation with bindable properties.
|
||||
/// </summary>
|
||||
public class ValidationHelper : ReactiveObject, IDisposable
|
||||
{
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
private readonly IValidationComponent _validation;
|
||||
|
||||
// how do we get this to be reactive though? we need to publish
|
||||
// validation object
|
||||
private ObservableAsPropertyHelper<bool> _isValid;
|
||||
|
||||
private ObservableAsPropertyHelper<ValidationText> _message;
|
||||
|
||||
public ValidationHelper(IValidationComponent validation)
|
||||
{
|
||||
_validation = validation;
|
||||
Setup();
|
||||
}
|
||||
|
||||
public bool IsValid => _isValid.Value;
|
||||
public ValidationText Message => _message.Value;
|
||||
|
||||
public IObservable<ValidationState> ValidationChanged => _validation.ValidationStatusChange;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposables?.Dispose();
|
||||
}
|
||||
|
||||
private void Setup()
|
||||
{
|
||||
_disposables.Add(_validation.ValidationStatusChange.Select(v => v.IsValid)
|
||||
.ToProperty(this, vm => vm.IsValid, out _isValid));
|
||||
_disposables.Add(_validation.ValidationStatusChange.Select(v => v.Text)
|
||||
.ToProperty(this, vm => vm.Message, out _message));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Class1.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Collections\" />
|
||||
<Folder Include="Components\" />
|
||||
<Folder Include="Contexts\" />
|
||||
<Folder Include="Abstractions\" />
|
||||
<Folder Include="Comparators\" />
|
||||
<Folder Include="Extensions\" />
|
||||
<Folder Include="Formatters\" />
|
||||
<Folder Include="Helpers\" />
|
||||
<Folder Include="States\" />
|
||||
<Folder Include="TemplateGenerators\" />
|
||||
<Folder Include="ValidationBindings\" />
|
||||
<Folder Include="Components\Abstractions\" />
|
||||
<Folder Include="Formatters\Abstractions\" />
|
||||
<Folder Include="ValidationBindings\Abstractions\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ReactiveUI" Version="9.11.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,17 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveUI.Validation", "ReactiveUI.Validation.csproj", "{B62AABD0-22A4-470D-B6EB-F6B3EAE668DE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B62AABD0-22A4-470D-B6EB-F6B3EAE668DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B62AABD0-22A4-470D-B6EB-F6B3EAE668DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B62AABD0-22A4-470D-B6EB-F6B3EAE668DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B62AABD0-22A4-470D-B6EB-F6B3EAE668DE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,51 @@
|
|||
using ReactiveUI.Validation.Collections;
|
||||
using ReactiveUI.Validation.Components.Abstractions;
|
||||
|
||||
namespace ReactiveUI.Validation.States
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the validation state of a validation component.
|
||||
/// </summary>
|
||||
public sealed class ValidationState
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Create an instance.
|
||||
/// </summary>
|
||||
/// <param name="isValid"></param>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="component"></param>
|
||||
public ValidationState(bool isValid, string text, IValidationComponent component) : this(isValid,
|
||||
new ValidationText(text), component)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance.
|
||||
/// </summary>
|
||||
/// <param name="isValid"></param>
|
||||
/// <param name="text"></param>
|
||||
/// <param name="component"></param>
|
||||
public ValidationState(bool isValid, ValidationText text, IValidationComponent component)
|
||||
{
|
||||
IsValid = isValid;
|
||||
Text = text;
|
||||
Component = component;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The associated component
|
||||
/// </summary>
|
||||
public IValidationComponent Component { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get whether the validation is currently valid or not.
|
||||
/// </summary>
|
||||
public bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the validation text.
|
||||
/// </summary>
|
||||
public ValidationText Text { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,646 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using ReactiveUI.Validation.Collections;
|
||||
using ReactiveUI.Validation.Comparators;
|
||||
using ReactiveUI.Validation.Components;
|
||||
using ReactiveUI.Validation.States;
|
||||
|
||||
namespace ReactiveUI.Validation.TemplateGenerators
|
||||
{
|
||||
public sealed class BasePropertyValidation<TViewModel, TProperty1, TProperty2> : BasePropertyValidation<TViewModel>
|
||||
{
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// Function to determine if valid or not.
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<TProperty1, TProperty2>, bool> _isValidFunc;
|
||||
|
||||
/// <summary>
|
||||
/// The validation message factory
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<TProperty1, TProperty2>, bool, ValidationText> _message;
|
||||
|
||||
/// <summary>
|
||||
/// The connected observable to see updates in properties being validated
|
||||
/// </summary>
|
||||
private readonly IConnectableObservable<Tuple<TProperty1, TProperty2>> _valueConnectedObservable;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the current value.
|
||||
/// </summary>
|
||||
private readonly Subject<Tuple<TProperty1, TProperty2>> _valueSubject =
|
||||
new Subject<Tuple<TProperty1, TProperty2>>();
|
||||
|
||||
/// <summary>
|
||||
/// Are we connected
|
||||
/// </summary>
|
||||
private bool _connected;
|
||||
|
||||
/// <summary>
|
||||
/// The last calculated value of the properties.
|
||||
/// </summary>
|
||||
private Tuple<TProperty1, TProperty2> _lastValue;
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Func<Tuple<TProperty1, TProperty2>, bool> isValidFunc,
|
||||
Func<Tuple<TProperty1, TProperty2>, string> message) :
|
||||
this(viewModel, property1, property2, isValidFunc,
|
||||
(p, v) => new ValidationText(v ? string.Empty : message(p)))
|
||||
{
|
||||
}
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Func<Tuple<TProperty1, TProperty2>, bool> isValidFunc,
|
||||
Func<Tuple<TProperty1, TProperty2>, bool, string> messageFunc) :
|
||||
this(viewModel, property1, property2, isValidFunc,
|
||||
(parameters, isValid) => new ValidationText(messageFunc(parameters, isValid)))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Func<Tuple<TProperty1, TProperty2>, bool> validSelector,
|
||||
Func<Tuple<TProperty1, TProperty2>, bool, ValidationText> message)
|
||||
{
|
||||
_message = message;
|
||||
_isValidFunc = validSelector;
|
||||
|
||||
// add the properties used to our list
|
||||
AddProperty(property1);
|
||||
AddProperty(property2);
|
||||
|
||||
// always record the last value seen
|
||||
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
|
||||
|
||||
// setup a connected observable to see when values change and cast that to our value subject
|
||||
_valueConnectedObservable = viewModel.WhenAnyValue(property1, property2).DistinctUntilChanged()
|
||||
.Multicast(_valueSubject);
|
||||
}
|
||||
|
||||
protected override IObservable<ValidationState> GetValidationChangeObservable()
|
||||
{
|
||||
Activate();
|
||||
|
||||
return _valueSubject.Select(value =>
|
||||
{
|
||||
var isValid = _isValidFunc(value);
|
||||
return new ValidationState(isValid, GetMessage(value, isValid), this);
|
||||
}).DistinctUntilChanged(new ValidationStateComparer());
|
||||
}
|
||||
|
||||
|
||||
protected ValidationText GetMessage(Tuple<TProperty1, TProperty2> @params, bool isValid)
|
||||
{
|
||||
return _message(@params, isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate the connection to ensure we start seeing validations.
|
||||
/// </summary>
|
||||
private void Activate()
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
_disposables.Add(_valueConnectedObservable.Connect());
|
||||
|
||||
_connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_disposables.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class
|
||||
BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3> : BasePropertyValidation<TViewModel>
|
||||
{
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// Function to determine if valid or not.
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3>, bool> _isValidFunc;
|
||||
|
||||
/// <summary>
|
||||
/// The validation message factory
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3>, bool, ValidationText> _message;
|
||||
|
||||
/// <summary>
|
||||
/// The connected observable to see updates in properties being validated
|
||||
/// </summary>
|
||||
private readonly IConnectableObservable<Tuple<TProperty1, TProperty2, TProperty3>> _valueConnectedObservable;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the current value.
|
||||
/// </summary>
|
||||
private readonly Subject<Tuple<TProperty1, TProperty2, TProperty3>> _valueSubject =
|
||||
new Subject<Tuple<TProperty1, TProperty2, TProperty3>>();
|
||||
|
||||
/// <summary>
|
||||
/// Are we connected
|
||||
/// </summary>
|
||||
private bool _connected;
|
||||
|
||||
/// <summary>
|
||||
/// The last calculated value of the properties.
|
||||
/// </summary>
|
||||
private Tuple<TProperty1, TProperty2, TProperty3> _lastValue;
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3>, bool> isValidFunc,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3>, string> message) :
|
||||
this(viewModel, property1, property2, property3, isValidFunc,
|
||||
(p, v) => new ValidationText(v ? string.Empty : message(p)))
|
||||
{
|
||||
}
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3>, bool> isValidFunc,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3>, bool, string> messageFunc) :
|
||||
this(viewModel, property1, property2, property3, isValidFunc,
|
||||
(parameters, isValid) => new ValidationText(messageFunc(parameters, isValid)))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3>, bool> validSelector,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3>, bool, ValidationText> message)
|
||||
{
|
||||
_message = message;
|
||||
_isValidFunc = validSelector;
|
||||
|
||||
// add the properties used to our list
|
||||
AddProperty(property1);
|
||||
AddProperty(property2);
|
||||
AddProperty(property3);
|
||||
|
||||
// always record the last value seen
|
||||
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
|
||||
|
||||
// setup a connected observable to see when values change and cast that to our value subject
|
||||
_valueConnectedObservable = viewModel.WhenAnyValue(property1, property2, property3).DistinctUntilChanged()
|
||||
.Multicast(_valueSubject);
|
||||
}
|
||||
|
||||
protected override IObservable<ValidationState> GetValidationChangeObservable()
|
||||
{
|
||||
Activate();
|
||||
|
||||
return _valueSubject.Select(value =>
|
||||
{
|
||||
var isValid = _isValidFunc(value);
|
||||
return new ValidationState(isValid, GetMessage(value, isValid), this);
|
||||
}).DistinctUntilChanged(new ValidationStateComparer());
|
||||
}
|
||||
|
||||
|
||||
protected ValidationText GetMessage(Tuple<TProperty1, TProperty2, TProperty3> @params, bool isValid)
|
||||
{
|
||||
return _message(@params, isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate the connection to ensure we start seeing validations.
|
||||
/// </summary>
|
||||
private void Activate()
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
_disposables.Add(_valueConnectedObservable.Connect());
|
||||
|
||||
_connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_disposables.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class
|
||||
BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3,
|
||||
TProperty4> : BasePropertyValidation<TViewModel>
|
||||
{
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// Function to determine if valid or not.
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool> _isValidFunc;
|
||||
|
||||
/// <summary>
|
||||
/// The validation message factory
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool, ValidationText> _message;
|
||||
|
||||
/// <summary>
|
||||
/// The connected observable to see updates in properties being validated
|
||||
/// </summary>
|
||||
private readonly IConnectableObservable<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>>
|
||||
_valueConnectedObservable;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the current value.
|
||||
/// </summary>
|
||||
private readonly Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>> _valueSubject =
|
||||
new Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>>();
|
||||
|
||||
/// <summary>
|
||||
/// Are we connected
|
||||
/// </summary>
|
||||
private bool _connected;
|
||||
|
||||
/// <summary>
|
||||
/// The last calculated value of the properties.
|
||||
/// </summary>
|
||||
private Tuple<TProperty1, TProperty2, TProperty3, TProperty4> _lastValue;
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Expression<Func<TViewModel, TProperty4>> property4,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool> isValidFunc,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, string> message) :
|
||||
this(viewModel, property1, property2, property3, property4, isValidFunc,
|
||||
(p, v) => new ValidationText(v ? string.Empty : message(p)))
|
||||
{
|
||||
}
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Expression<Func<TViewModel, TProperty4>> property4,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool> isValidFunc,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool, string> messageFunc) :
|
||||
this(viewModel, property1, property2, property3, property4, isValidFunc,
|
||||
(parameters, isValid) => new ValidationText(messageFunc(parameters, isValid)))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Expression<Func<TViewModel, TProperty4>> property4,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool> validSelector,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4>, bool, ValidationText> message)
|
||||
{
|
||||
_message = message;
|
||||
_isValidFunc = validSelector;
|
||||
|
||||
// add the properties used to our list
|
||||
AddProperty(property1);
|
||||
AddProperty(property2);
|
||||
AddProperty(property3);
|
||||
AddProperty(property4);
|
||||
|
||||
// always record the last value seen
|
||||
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
|
||||
|
||||
// setup a connected observable to see when values change and cast that to our value subject
|
||||
_valueConnectedObservable = viewModel.WhenAnyValue(property1, property2, property3, property4)
|
||||
.DistinctUntilChanged().Multicast(_valueSubject);
|
||||
}
|
||||
|
||||
protected override IObservable<ValidationState> GetValidationChangeObservable()
|
||||
{
|
||||
Activate();
|
||||
|
||||
return _valueSubject.Select(value =>
|
||||
{
|
||||
var isValid = _isValidFunc(value);
|
||||
return new ValidationState(isValid, GetMessage(value, isValid), this);
|
||||
}).DistinctUntilChanged(new ValidationStateComparer());
|
||||
}
|
||||
|
||||
|
||||
protected ValidationText GetMessage(Tuple<TProperty1, TProperty2, TProperty3, TProperty4> @params, bool isValid)
|
||||
{
|
||||
return _message(@params, isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate the connection to ensure we start seeing validations.
|
||||
/// </summary>
|
||||
private void Activate()
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
_disposables.Add(_valueConnectedObservable.Connect());
|
||||
|
||||
_connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_disposables.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class
|
||||
BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3, TProperty4,
|
||||
TProperty5> : BasePropertyValidation<TViewModel>
|
||||
{
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// Function to determine if valid or not.
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool> _isValidFunc;
|
||||
|
||||
/// <summary>
|
||||
/// The validation message factory
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool, ValidationText>
|
||||
_message;
|
||||
|
||||
/// <summary>
|
||||
/// The connected observable to see updates in properties being validated
|
||||
/// </summary>
|
||||
private readonly IConnectableObservable<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>>
|
||||
_valueConnectedObservable;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the current value.
|
||||
/// </summary>
|
||||
private readonly Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>> _valueSubject =
|
||||
new Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>>();
|
||||
|
||||
/// <summary>
|
||||
/// Are we connected
|
||||
/// </summary>
|
||||
private bool _connected;
|
||||
|
||||
/// <summary>
|
||||
/// The last calculated value of the properties.
|
||||
/// </summary>
|
||||
private Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5> _lastValue;
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Expression<Func<TViewModel, TProperty4>> property4,
|
||||
Expression<Func<TViewModel, TProperty5>> property5,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool> isValidFunc,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, string> message) :
|
||||
this(viewModel, property1, property2, property3, property4, property5, isValidFunc,
|
||||
(p, v) => new ValidationText(v ? string.Empty : message(p)))
|
||||
{
|
||||
}
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Expression<Func<TViewModel, TProperty4>> property4,
|
||||
Expression<Func<TViewModel, TProperty5>> property5,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool> isValidFunc,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool, string> messageFunc) :
|
||||
this(viewModel, property1, property2, property3, property4, property5, isValidFunc,
|
||||
(parameters, isValid) => new ValidationText(messageFunc(parameters, isValid)))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Expression<Func<TViewModel, TProperty4>> property4,
|
||||
Expression<Func<TViewModel, TProperty5>> property5,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool> validSelector,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5>, bool, ValidationText> message)
|
||||
{
|
||||
_message = message;
|
||||
_isValidFunc = validSelector;
|
||||
|
||||
// add the properties used to our list
|
||||
AddProperty(property1);
|
||||
AddProperty(property2);
|
||||
AddProperty(property3);
|
||||
AddProperty(property4);
|
||||
AddProperty(property5);
|
||||
|
||||
// always record the last value seen
|
||||
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
|
||||
|
||||
// setup a connected observable to see when values change and cast that to our value subject
|
||||
_valueConnectedObservable = viewModel.WhenAnyValue(property1, property2, property3, property4, property5)
|
||||
.DistinctUntilChanged().Multicast(_valueSubject);
|
||||
}
|
||||
|
||||
protected override IObservable<ValidationState> GetValidationChangeObservable()
|
||||
{
|
||||
Activate();
|
||||
|
||||
return _valueSubject.Select(value =>
|
||||
{
|
||||
var isValid = _isValidFunc(value);
|
||||
return new ValidationState(isValid, GetMessage(value, isValid), this);
|
||||
}).DistinctUntilChanged(new ValidationStateComparer());
|
||||
}
|
||||
|
||||
|
||||
protected ValidationText GetMessage(Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5> @params,
|
||||
bool isValid)
|
||||
{
|
||||
return _message(@params, isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate the connection to ensure we start seeing validations.
|
||||
/// </summary>
|
||||
private void Activate()
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
_disposables.Add(_valueConnectedObservable.Connect());
|
||||
|
||||
_connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_disposables.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BasePropertyValidation<TViewModel, TProperty1, TProperty2, TProperty3, TProperty4, TProperty5,
|
||||
TProperty6> : BasePropertyValidation<TViewModel>
|
||||
{
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// Function to determine if valid or not.
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool>
|
||||
_isValidFunc;
|
||||
|
||||
/// <summary>
|
||||
/// The validation message factory
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool,
|
||||
ValidationText> _message;
|
||||
|
||||
/// <summary>
|
||||
/// The connected observable to see updates in properties being validated
|
||||
/// </summary>
|
||||
private readonly
|
||||
IConnectableObservable<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>>
|
||||
_valueConnectedObservable;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the current value.
|
||||
/// </summary>
|
||||
private readonly Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>>
|
||||
_valueSubject =
|
||||
new Subject<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>>();
|
||||
|
||||
/// <summary>
|
||||
/// Are we connected
|
||||
/// </summary>
|
||||
private bool _connected;
|
||||
|
||||
/// <summary>
|
||||
/// The last calculated value of the properties.
|
||||
/// </summary>
|
||||
private Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6> _lastValue;
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Expression<Func<TViewModel, TProperty4>> property4,
|
||||
Expression<Func<TViewModel, TProperty5>> property5,
|
||||
Expression<Func<TViewModel, TProperty6>> property6,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool> isValidFunc,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, string> message) :
|
||||
this(viewModel, property1, property2, property3, property4, property5, property6, isValidFunc,
|
||||
(p, v) => new ValidationText(v ? string.Empty : message(p)))
|
||||
{
|
||||
}
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Expression<Func<TViewModel, TProperty4>> property4,
|
||||
Expression<Func<TViewModel, TProperty5>> property5,
|
||||
Expression<Func<TViewModel, TProperty6>> property6,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool> isValidFunc,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool, string>
|
||||
messageFunc) :
|
||||
this(viewModel, property1, property2, property3, property4, property5, property6, isValidFunc,
|
||||
(parameters, isValid) => new ValidationText(messageFunc(parameters, isValid)))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
Expression<Func<TViewModel, TProperty1>> property1,
|
||||
Expression<Func<TViewModel, TProperty2>> property2,
|
||||
Expression<Func<TViewModel, TProperty3>> property3,
|
||||
Expression<Func<TViewModel, TProperty4>> property4,
|
||||
Expression<Func<TViewModel, TProperty5>> property5,
|
||||
Expression<Func<TViewModel, TProperty6>> property6,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool> validSelector,
|
||||
Func<Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6>, bool, ValidationText>
|
||||
message)
|
||||
{
|
||||
_message = message;
|
||||
_isValidFunc = validSelector;
|
||||
|
||||
// add the properties used to our list
|
||||
AddProperty(property1);
|
||||
AddProperty(property2);
|
||||
AddProperty(property3);
|
||||
AddProperty(property4);
|
||||
AddProperty(property5);
|
||||
AddProperty(property6);
|
||||
|
||||
// always record the last value seen
|
||||
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
|
||||
|
||||
// setup a connected observable to see when values change and cast that to our value subject
|
||||
_valueConnectedObservable = viewModel
|
||||
.WhenAnyValue(property1, property2, property3, property4, property5, property6).DistinctUntilChanged()
|
||||
.Multicast(_valueSubject);
|
||||
}
|
||||
|
||||
protected override IObservable<ValidationState> GetValidationChangeObservable()
|
||||
{
|
||||
Activate();
|
||||
|
||||
return _valueSubject.Select(value =>
|
||||
{
|
||||
var isValid = _isValidFunc(value);
|
||||
return new ValidationState(isValid, GetMessage(value, isValid), this);
|
||||
}).DistinctUntilChanged(new ValidationStateComparer());
|
||||
}
|
||||
|
||||
|
||||
protected ValidationText GetMessage(
|
||||
Tuple<TProperty1, TProperty2, TProperty3, TProperty4, TProperty5, TProperty6> @params, bool isValid)
|
||||
{
|
||||
return _message(@params, isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate the connection to ensure we start seeing validations.
|
||||
/// </summary>
|
||||
private void Activate()
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
_disposables.Add(_valueConnectedObservable.Connect());
|
||||
|
||||
_connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_disposables.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
<#@ template debug="false" hostspecific="false" language="C#" #>
|
||||
<#@ assembly name="System.Core" #>
|
||||
<#@ import namespace="System.Linq" #>
|
||||
<#@ import namespace="System.Text" #>
|
||||
<#@ import namespace="System.Collections.Generic" #>
|
||||
<#@ output extension=".cs" #>
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Validation.Collections;
|
||||
using ReactiveUI.Validation.Components;
|
||||
using ReactiveUI.Validation.States;
|
||||
using ReactiveUI.Validation.Comparators;
|
||||
|
||||
namespace ReactiveUI.Validation.TemplateGenerators
|
||||
{
|
||||
<# int maxFuncLength = 6; #>
|
||||
<# for(int length=2; length <= maxFuncLength; length++) { #>
|
||||
<# var templParams = Enumerable.Range(1, length).Select(x => "TProperty" + x.ToString()); #>
|
||||
<# string valuePropertyParams = String.Join(", ", Enumerable.Range(1, length).Select(x => String.Format("property{0}", x))); #>
|
||||
<# string actionParams = String.Join(", ", Enumerable.Range(1, length).Select(x => String.Format("prop{0}", x))); #>
|
||||
public sealed class BasePropertyValidation<TViewModel,<#= String.Join(",",templParams) #>> : BasePropertyValidation<TViewModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// The last calculated value of the properties.
|
||||
/// </summary>
|
||||
private Tuple<<#= String.Join(",",templParams) #>> _lastValue;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the current value.
|
||||
/// </summary>
|
||||
private readonly Subject<Tuple<<#= String.Join(",",templParams) #>>> _valueSubject = new Subject<Tuple<<#= String.Join(",",templParams) #>>>();
|
||||
|
||||
/// <summary>
|
||||
/// The validation message factory
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<<#= String.Join(",",templParams) #>>,bool,ValidationText> _message;
|
||||
|
||||
/// <summary>
|
||||
/// The connected observable to see updates in properties being validated
|
||||
/// </summary>
|
||||
private readonly IConnectableObservable<Tuple<<#= String.Join(",",templParams) #>>> _valueConnectedObservable;
|
||||
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// Are we connected
|
||||
/// </summary>
|
||||
private bool _connected;
|
||||
|
||||
/// <summary>
|
||||
/// Function to determine if valid or not.
|
||||
/// </summary>
|
||||
private readonly Func<Tuple<<#= String.Join(",",templParams) #>>, bool> _isValidFunc;
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
|
||||
<# for(int property=1;property <= length;++property) { #>
|
||||
Expression<Func<TViewModel,TProperty<#=property#>>> property<#=property #>,
|
||||
<# } #>
|
||||
|
||||
Func<Tuple<<#=String.Join(",",templParams)#>>, bool> isValidFunc,
|
||||
Func<Tuple<<#=String.Join(",",templParams)#>>,string> message):
|
||||
|
||||
this(viewModel,<#=String.Join(",",valuePropertyParams)#>,isValidFunc,
|
||||
(Tuple<<#=String.Join(",",templParams)#>> p, bool v) => new ValidationText(v ? string.Empty : message(p)))
|
||||
{
|
||||
}
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
|
||||
<# for(int property=1;property <= length;++property) { #>
|
||||
Expression<Func<TViewModel,TProperty<#=property#>>> property<#=property #>,
|
||||
<# } #>
|
||||
|
||||
Func<Tuple<<#=String.Join(",",templParams) #>>, bool> isValidFunc,
|
||||
|
||||
Func<Tuple<<#=String.Join(",",templParams)#>>, bool, string> messageFunc):
|
||||
this(viewModel,<#=String.Join(",",valuePropertyParams)#>,isValidFunc,(parameters,isValid) => new ValidationText(messageFunc(parameters,isValid)))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public BasePropertyValidation(TViewModel viewModel,
|
||||
<# for(int property=1;property <= length;++property) { #>
|
||||
Expression<Func<TViewModel,TProperty<#=property#>>> property<#=property #>,
|
||||
<# } #>
|
||||
Func<Tuple<<#= String.Join(",",templParams) #>>,bool> validSelector,
|
||||
Func<Tuple<<#= String.Join(",",templParams) #>>,bool,ValidationText> message)
|
||||
{
|
||||
_message = message;
|
||||
_isValidFunc = validSelector;
|
||||
|
||||
// add the properties used to our list
|
||||
<# for(int property=1;property<=length;property++) { #>
|
||||
AddProperty(property<#=property#>);
|
||||
<# } #>
|
||||
|
||||
// always record the last value seen
|
||||
_disposables.Add(_valueSubject.Subscribe(v => _lastValue = v));
|
||||
|
||||
// setup a connected observable to see when values change and cast that to our value subject
|
||||
_valueConnectedObservable = viewModel.WhenAnyValue(<#=valuePropertyParams#>).DistinctUntilChanged().Multicast(_valueSubject);
|
||||
}
|
||||
|
||||
protected override IObservable<ValidationState> GetValidationChangeObservable()
|
||||
{
|
||||
Activate();
|
||||
|
||||
return _valueSubject.
|
||||
Select(value =>
|
||||
{
|
||||
var isValid = _isValidFunc(value);
|
||||
return new ValidationState(isValid, this.GetMessage(value, isValid), this);
|
||||
}).DistinctUntilChanged(new ValidationStateComparer());
|
||||
}
|
||||
|
||||
|
||||
protected ValidationText GetMessage(Tuple<<#=String.Join(",",templParams)#>> @params,bool isValid)
|
||||
{
|
||||
return _message(@params,isValid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate the connection to ensure we start seeing validations.
|
||||
/// </summary>
|
||||
private void Activate()
|
||||
{
|
||||
if (!_connected)
|
||||
{
|
||||
_disposables.Add(_valueConnectedObservable.Connect());
|
||||
|
||||
_connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
_disposables.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
}
|
||||
<# } #>
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
using System;
|
||||
|
||||
namespace ReactiveUI.Validation.ValidationBindings.Abstractions
|
||||
{
|
||||
public interface IValidationBinding : IDisposable
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,273 @@
|
|||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using ReactiveUI.Validation.Abstractions;
|
||||
using ReactiveUI.Validation.Extensions;
|
||||
using ReactiveUI.Validation.Formatters;
|
||||
using ReactiveUI.Validation.Formatters.Abstractions;
|
||||
using ReactiveUI.Validation.Helpers;
|
||||
using ReactiveUI.Validation.States;
|
||||
using ReactiveUI.Validation.ValidationBindings.Abstractions;
|
||||
|
||||
namespace ReactiveUI.Validation.ValidationBindings
|
||||
{
|
||||
/// <summary>
|
||||
/// A validation binding.
|
||||
/// </summary>
|
||||
public class ValidationBinding : IValidationBinding
|
||||
{
|
||||
private readonly CompositeDisposable _disposables = new CompositeDisposable();
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance with a specified observable for validation changes.
|
||||
/// </summary>
|
||||
/// <param name="validationObservable"></param>
|
||||
public ValidationBinding(IObservable<Unit> validationObservable)
|
||||
{
|
||||
_disposables.Add(validationObservable.Subscribe());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_disposables?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a binding between a view model property and a view property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TViewModelProperty1"></typeparam>
|
||||
/// <typeparam name="TViewProperty"></typeparam>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="viewModelProperty"></param>
|
||||
/// <param name="viewProperty"></param>
|
||||
/// <param name="formatter"></param>
|
||||
/// <param name="strict"></param>
|
||||
/// <returns></returns>
|
||||
public static IValidationBinding ForProperty
|
||||
<TView, TViewModel, TViewModelProperty1, TViewProperty>(TView view,
|
||||
Expression<Func<TViewModel, TViewModelProperty1>>
|
||||
viewModelProperty,
|
||||
Expression<Func<TView, TViewProperty>>
|
||||
viewProperty,
|
||||
IValidationTextFormatter<string> formatter = null,
|
||||
bool strict = true)
|
||||
where TView : IViewFor<TViewModel>
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
{
|
||||
if (formatter == null) formatter = SingleLineFormatter.Default;
|
||||
|
||||
var vcObs = view.WhenAnyValue(v => v.ViewModel)
|
||||
.Where(vm => vm != null)
|
||||
.Select(
|
||||
viewModel =>
|
||||
viewModel.ValidationContext
|
||||
.ResolveFor(viewModelProperty, strict)
|
||||
.ValidationStatusChange).Switch().Select(vc => formatter.Format(vc.Text));
|
||||
|
||||
var updateObs = BindToView(vcObs, view, viewProperty).Select(_ => Unit.Default);
|
||||
|
||||
return new ValidationBinding(updateObs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binding a specified view model property to a provided action.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TViewModelProperty1"></typeparam>
|
||||
/// <typeparam name="TOut"></typeparam>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="viewModelProperty"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="formatter"></param>
|
||||
/// <param name="strict"></param>
|
||||
/// <returns></returns>
|
||||
public static IValidationBinding ForProperty
|
||||
<TView, TViewModel, TViewModelProperty1, TOut>(TView view,
|
||||
Expression<Func<TViewModel, TViewModelProperty1>>
|
||||
viewModelProperty,
|
||||
Action<ValidationState, TOut> action,
|
||||
IValidationTextFormatter<TOut> formatter = null,
|
||||
bool strict = true)
|
||||
where TView : IViewFor<TViewModel>
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
{
|
||||
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
||||
|
||||
var vcObs = view.WhenAnyValue(v => v.ViewModel).Where(vm => vm != null).Select(
|
||||
viewModel =>
|
||||
viewModel.ValidationContext.ResolveFor(viewModelProperty, strict)
|
||||
.ValidationStatusChange).Switch()
|
||||
.Select(vc => new {ValidationChange = vc, Formatted = formatter.Format(vc.Text)})
|
||||
.Do(r => action(r.ValidationChange, r.Formatted)).Select(_ => Unit.Default);
|
||||
|
||||
return new ValidationBinding(vcObs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a binding between a <see cref="ValidationHelper" /> and a specified view property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TViewProperty"></typeparam>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="viewModelHelperProperty"></param>
|
||||
/// <param name="viewProperty"></param>
|
||||
/// <param name="formatter"></param>
|
||||
/// <returns></returns>
|
||||
public static IValidationBinding ForValidationHelperProperty
|
||||
<TView, TViewModel, TViewProperty>(TView view,
|
||||
Expression<Func<TViewModel, ValidationHelper>> viewModelHelperProperty,
|
||||
Expression<Func<TView, TViewProperty>> viewProperty,
|
||||
IValidationTextFormatter<string> formatter = null)
|
||||
where TView : IViewFor<TViewModel>
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
|
||||
{
|
||||
if (formatter == null) formatter = SingleLineFormatter.Default;
|
||||
|
||||
var vcObs = view.WhenAnyValue(v => v.ViewModel).Where(vm => vm != null).Select(
|
||||
viewModel =>
|
||||
viewModel.WhenAnyValue(viewModelHelperProperty)
|
||||
.SelectMany(vy => vy.ValidationChanged)).Switch()
|
||||
.Select(vc => formatter.Format(vc.Text));
|
||||
|
||||
var updateObs = BindToView(vcObs, view, viewProperty).Select(_ => Unit.Default);
|
||||
|
||||
return new ValidationBinding(updateObs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind a <see cref="ValidationHelper" /> to a specified action.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TOut"></typeparam>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="viewModelHelperProperty"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="formatter"></param>
|
||||
/// <returns></returns>
|
||||
public static IValidationBinding ForValidationHelperProperty
|
||||
<TView, TViewModel, TOut>(TView view,
|
||||
Expression<Func<TViewModel, ValidationHelper>> viewModelHelperProperty,
|
||||
Action<ValidationState, TOut> action,
|
||||
IValidationTextFormatter<TOut> formatter = null)
|
||||
where TView : IViewFor<TViewModel>
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
{
|
||||
if (formatter == null) throw new ArgumentNullException(nameof(formatter));
|
||||
|
||||
var vcObs = view.WhenAnyValue(v => v.ViewModel)
|
||||
.Where(vm => vm != null)
|
||||
.Select(
|
||||
viewModel =>
|
||||
viewModel.WhenAnyValue(viewModelHelperProperty)
|
||||
.SelectMany(vy => vy.ValidationChanged))
|
||||
.Switch()
|
||||
.Select(vc =>
|
||||
new {ValidationChange = vc, Formatted = formatter.Format(vc.Text)});
|
||||
|
||||
var updateObs = vcObs.Do(r => { action(r.ValidationChange, r.Formatted); })
|
||||
.Select(_ => Unit.Default);
|
||||
|
||||
return new ValidationBinding(updateObs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a binding between a view model and a specified action.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TOut"></typeparam>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="formatter"></param>
|
||||
/// <returns></returns>
|
||||
public static IValidationBinding ForViewModel<TView, TViewModel, TOut>(TView view,
|
||||
Action<TOut> action,
|
||||
IValidationTextFormatter<TOut> formatter)
|
||||
where TView : IViewFor<TViewModel>
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
{
|
||||
if (formatter == null)
|
||||
throw new ArgumentNullException(nameof(formatter));
|
||||
|
||||
var vcObs = view.WhenAnyValue(v => v.ViewModel)
|
||||
.Where(vm => vm != null)
|
||||
.Select(vm => vm.ValidationContext.Text)
|
||||
.Select(formatter.Format);
|
||||
|
||||
var updateObs = vcObs.Do(action).Select(_ => Unit.Default);
|
||||
|
||||
return new ValidationBinding(updateObs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a binding between a view model and a view property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TView"></typeparam>
|
||||
/// <typeparam name="TViewModel"></typeparam>
|
||||
/// <typeparam name="TViewProperty"></typeparam>
|
||||
/// <param name="view"></param>
|
||||
/// <param name="viewProperty"></param>
|
||||
/// <param name="formatter"></param>
|
||||
/// <returns></returns>
|
||||
public static IValidationBinding ForViewModel
|
||||
<TView, TViewModel, TViewProperty>(TView view,
|
||||
Expression<Func<TView, TViewProperty>> viewProperty,
|
||||
IValidationTextFormatter<string> formatter = null
|
||||
)
|
||||
where TView : IViewFor<TViewModel>
|
||||
where TViewModel : ReactiveObject, ISupportsValidation
|
||||
{
|
||||
if (formatter == null)
|
||||
formatter = SingleLineFormatter.Default;
|
||||
|
||||
var vcObs = view.WhenAnyValue(v => v.ViewModel)
|
||||
.Where(vm => vm != null)
|
||||
.SelectMany(vm => vm.ValidationContext.ValidationStatusChange)
|
||||
.Select(vc => formatter.Format(vc.Text));
|
||||
|
||||
var updateObs = BindToView(vcObs, view, viewProperty).Select(_ => Unit.Default);
|
||||
|
||||
return new ValidationBinding(updateObs);
|
||||
}
|
||||
|
||||
|
||||
public static IObservable<TValue> BindToView<TView, TViewProp, TTarget, TValue>(
|
||||
IObservable<TValue> valueChange,
|
||||
TTarget target,
|
||||
Expression<Func<TView, TViewProp>> viewProperty)
|
||||
{
|
||||
var viewExpression = Reflection.Rewrite(viewProperty.Body);
|
||||
|
||||
var setter = Reflection.GetValueSetterOrThrow(viewExpression.GetMemberInfo());
|
||||
|
||||
if (viewExpression.GetParent().NodeType == ExpressionType.Parameter)
|
||||
return valueChange.Do(
|
||||
x => setter(target, x, viewExpression.GetArgumentsArray()),
|
||||
ex =>
|
||||
{
|
||||
//this.Log().ErrorException(String.Format("{0} Binding received an Exception!", viewExpression), ex);
|
||||
});
|
||||
|
||||
var bindInfo = valueChange.CombineLatest(target.WhenAnyDynamic(viewExpression.GetParent(), x => x.Value),
|
||||
(val, host) => new {val, host});
|
||||
|
||||
return bindInfo
|
||||
.Where(x => x.host != null)
|
||||
.Do(
|
||||
x => setter(x.host, x.val, viewExpression.GetArgumentsArray()),
|
||||
ex =>
|
||||
{
|
||||
//this.Log().ErrorException(String.Format("{0} Binding received an Exception!", viewExpression), ex);
|
||||
})
|
||||
.Select(v => v.val);
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче