Skip to content

Commit

Permalink
Handle prop changed events to validation errors (#553)
Browse files Browse the repository at this point in the history
Co-authored-by: Morten Nielsen <mort5161@esri.com>
  • Loading branch information
dotMorten and Morten Nielsen committed Feb 27, 2024
1 parent 51821dc commit 7058598
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ public partial class FeatureFormView : Control
private const string ItemsViewName = "ItemsView";
private const string FeatureFormContentScrollViewerName = "FeatureFormContentScrollViewer";

private static readonly DependencyPropertyKey IsValidPropertyKey
= DependencyProperty.RegisterReadOnly(nameof(IsValid), typeof(bool), typeof(FeatureFormView), new FrameworkPropertyMetadata(false));

/// <summary>
/// Identifies the <see cref="IsValid"/> Dependency property.
/// </summary>
public static readonly DependencyProperty IsValidProperty = IsValidPropertyKey.DependencyProperty;

/// <summary>
/// Gets a value indicating whether this form has any validation errors.
/// </summary>
/// <seealso cref="FeatureForm.ValidationErrors"/>
public bool IsValid
{
get { return (bool)GetValue(IsValidProperty); }
private set { SetValue(IsValidPropertyKey, value); }
}
}
}
#endif
48 changes: 41 additions & 7 deletions src/Toolkit/Toolkit/UI/Controls/FeatureForm/FeatureFormView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ namespace Esri.ArcGISRuntime.Toolkit.UI.Controls
/// <seealso cref="Esri.ArcGISRuntime.Mapping.FeatureLayer.FeatureFormDefinition"/>
public partial class FeatureFormView
{
private WeakEventListener<FeatureFormView, INotifyPropertyChanged, object?, PropertyChangedEventArgs>? _elementPropertyChangedListener;

/// <summary>
/// Initializes a new instance of the <see cref="FeatureFormView"/> class.
/// </summary>
Expand Down Expand Up @@ -129,27 +131,59 @@ private void InvalidateForm()
/// </summary>
#if MAUI
public static readonly BindableProperty FeatureFormProperty =
BindableProperty.Create(nameof(FeatureForm), typeof(FeatureForm), typeof(FeatureFormView), null, propertyChanged: OnFeatureFormPropertyChanged);
BindableProperty.Create(nameof(FeatureForm), typeof(FeatureForm), typeof(FeatureFormView), null, propertyChanged: (s, oldValue, newValue) => ((FeatureFormView)s).OnFeatureFormPropertyChanged(oldValue, newValue));
#else
public static readonly DependencyProperty FeatureFormProperty =
DependencyProperty.Register(nameof(FeatureForm), typeof(FeatureForm), typeof(FeatureFormView),
new PropertyMetadata(null, (s, e) => FeatureFormView.OnFeatureFormPropertyChanged(s, e.OldValue, e.NewValue)));
new PropertyMetadata(null, (s, e) => ((FeatureFormView)s).OnFeatureFormPropertyChanged(e.OldValue, e.NewValue)));
#endif

private static void OnFeatureFormPropertyChanged(DependencyObject d, object oldValue, object newValue)
private void OnFeatureFormPropertyChanged(object oldValue, object newValue)
{
var formView = (FeatureFormView)d;
var oldForm = oldValue as FeatureForm;
var newForm = newValue as FeatureForm;
if (newForm is not null)
{
formView.InvalidateForm();
InvalidateForm();
}

#if MAUI
(formView.GetTemplateChild(FeatureFormContentScrollViewerName) as ScrollViewer)?.ScrollToAsync(0,0,false);
(GetTemplateChild(FeatureFormContentScrollViewerName) as ScrollViewer)?.ScrollToAsync(0,0,false);
#else
(formView.GetTemplateChild(FeatureFormContentScrollViewerName) as ScrollViewer)?.ScrollToHome();
(GetTemplateChild(FeatureFormContentScrollViewerName) as ScrollViewer)?.ScrollToHome();
#endif

if (oldValue is INotifyPropertyChanged inpcOld)
{
_elementPropertyChangedListener?.Detach();
_elementPropertyChangedListener = null;
}
if (newValue is INotifyPropertyChanged inpcNew)
{
_elementPropertyChangedListener = new WeakEventListener<FeatureFormView, INotifyPropertyChanged, object?, PropertyChangedEventArgs>(this, inpcNew)
{
OnEventAction = static (instance, source, eventArgs) => instance.FeatureForm_PropertyChanged(source, eventArgs),
OnDetachAction = static (instance, source, weakEventListener) => source.PropertyChanged -= weakEventListener.OnEvent,
};
inpcNew.PropertyChanged += _elementPropertyChangedListener.OnEvent;
}
UpdateIsValidProperty();
}

private void FeatureForm_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(FeatureForm.ValidationErrors))
{
UpdateIsValidProperty();
}
}

private void UpdateIsValidProperty()
{
#if WPF
IsValid = FeatureForm?.ValidationErrors?.Any() != true;
#elif MAUI
#warning TODO IsValid read-only property
#endif
}

Expand Down
43 changes: 26 additions & 17 deletions src/Toolkit/Toolkit/UI/Controls/FeatureForm/FieldFormElementView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,23 +118,6 @@ private async void OnValuePropertyChanged()
try
{
await FeatureForm.EvaluateExpressionsAsync();
var errors = Element.ValidationErrors;

string? errMessage = null;
if (errors != null && errors.Any())
{
errMessage = string.Join("\n", errors.Select(e => FeatureFormView.ValidationErrorToLocalizedString(Element, e)!));
}
else if (Element?.IsRequired == true && (Element.Value is null || Element?.Value is string str && string.IsNullOrEmpty(str)))
{
errMessage = "Required";
}

if (GetTemplateChild("ErrorLabel") is TextBlock tb)
{
tb.Text = errMessage;
}

}
catch (System.Exception)
{
Expand All @@ -157,6 +140,32 @@ private void FieldFormElement_PropertyChanged(object? sender, System.ComponentMo
OnValuePropertyChanged();
#endif
}
else if (e.PropertyName == nameof(FieldFormElement.ValidationErrors))
{
UpdateErrorMessages();
}
}

private void UpdateErrorMessages()
{
string? errMessage = null;
if (Element is not null)
{
var errors = Element.ValidationErrors;

if (errors != null && errors.Any())
{
errMessage = string.Join("\n", errors.Select(e => FeatureFormView.ValidationErrorToLocalizedString(Element, e)!));
}
else if (Element?.IsRequired == true && (Element.Value is null || Element?.Value is string str && string.IsNullOrEmpty(str)))
{
errMessage = Properties.Resources.GetString("FeatureFormFieldIsRequired");
}
}
if (GetTemplateChild("ErrorLabel") is TextBlock tb)
{
tb.Text = errMessage;
}
}
}
}
Expand Down
37 changes: 24 additions & 13 deletions src/Toolkit/Toolkit/UI/Controls/FeatureForm/TextFormInputView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public override void OnApplyTemplate()
barcodeButton.Click += BarcodeButton_Click;
#endif
}
UpdateValidationState();
}

private void BarcodeButton_Click(object sender, RoutedEventArgs e)
Expand Down Expand Up @@ -133,21 +134,11 @@ private void Apply()
value = datevalue;
try
{
Element?.UpdateValue(value); // Throws FeatureFormIncorrectValueTypeException if type is incorrect instead of populating ValidationErrors IssueRef:apollo#456
Element?.UpdateValue(value);
}
catch (System.Exception)
catch (System.Exception) // Unexpected error setting value
{
_textInput.Text = Element?.Value?.ToString(); //Reset input to previous valid value
return;
}
var err = Element?.ValidationErrors;
if (err != null && err.Any())
{
VisualStateManager.GoToState(this, "InputError", true);
}
else
{
VisualStateManager.GoToState(this, "InputValid", true);
}
}

Expand All @@ -164,7 +155,7 @@ public int MinLines
/// Identifies the <see cref="MinLines"/> dependency property.
/// </summary>
public static readonly DependencyProperty MinLinesProperty =
DependencyProperty.Register("MinLines", typeof(int), typeof(TextFormInputView), new PropertyMetadata(1));
DependencyProperty.Register(nameof(MinLines), typeof(int), typeof(TextFormInputView), new PropertyMetadata(1));

/// <summary>
/// Gets or sets the maximum number of visible lines.
Expand Down Expand Up @@ -254,6 +245,26 @@ private void Element_PropertyChanged(object? sender, System.ComponentModel.Prope
else
Dispatcher.Invoke(ConfigureTextBox);
}
else if (e.PropertyName == nameof(FieldFormElement.ValidationErrors))
{
if (Dispatcher.CheckAccess())
UpdateValidationState();
else
Dispatcher.Invoke(UpdateValidationState);
}
}

private void UpdateValidationState()
{
var err = Element?.ValidationErrors;
if (err != null && err.Any())
{
VisualStateManager.GoToState(this, "InputError", true);
}
else
{
VisualStateManager.GoToState(this, "InputValid", true);
}
}
}
}
Expand Down

0 comments on commit 7058598

Please sign in to comment.