using System; using System.Diagnostics.CodeAnalysis; using System.Globalization; using Avalonia.Reactive; using Avalonia.Xaml.Interactivity; namespace Avalonia.Xaml.Interactions.Core; /// /// A behavior that performs actions when the bound data meets a specified condition. /// [RequiresUnreferencedCode("This functionality is not compatible with trimming.")] public class DataTriggerBehavior : StyledElementTrigger { /// /// Identifies the avalonia property. /// public static readonly StyledProperty BindingProperty = AvaloniaProperty.Register(nameof(Binding)); /// /// Identifies the avalonia property. /// public static readonly StyledProperty ComparisonConditionProperty = AvaloniaProperty.Register(nameof(ComparisonCondition)); /// /// Identifies the avalonia property. /// public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register(nameof(Value)); /// /// Gets or sets the bound object that the will listen to. This is a avalonia property. /// public object? Binding { get => GetValue(BindingProperty); set => SetValue(BindingProperty, value); } /// /// Gets or sets the type of comparison to be performed between and . This is a avalonia property. /// public ComparisonConditionType ComparisonCondition { get => GetValue(ComparisonConditionProperty); set => SetValue(ComparisonConditionProperty, value); } /// /// Gets or sets the value to be compared with the value of . This is a avalonia property. /// public object? Value { get => GetValue(ValueProperty); set => SetValue(ValueProperty, value); } static DataTriggerBehavior() { BindingProperty.Changed.Subscribe( new AnonymousObserver>(OnValueChanged)); ComparisonConditionProperty.Changed.Subscribe( new AnonymousObserver>(OnValueChanged)); ValueProperty.Changed.Subscribe( new AnonymousObserver>(OnValueChanged)); } private static bool Compare(object? leftOperand, ComparisonConditionType operatorType, object? rightOperand) { if (leftOperand is not null && rightOperand is not null) { var value = rightOperand.ToString(); var destinationType = leftOperand.GetType(); if (value is not null) { rightOperand = TypeConverterHelper.Convert(value, destinationType); } } var leftComparableOperand = leftOperand as IComparable; var rightComparableOperand = rightOperand as IComparable; if (leftComparableOperand is not null && rightComparableOperand is not null) { return EvaluateComparable(leftComparableOperand, operatorType, rightComparableOperand); } switch (operatorType) { case ComparisonConditionType.Equal: return Equals(leftOperand, rightOperand); case ComparisonConditionType.NotEqual: return !Equals(leftOperand, rightOperand); case ComparisonConditionType.LessThan: case ComparisonConditionType.LessThanOrEqual: case ComparisonConditionType.GreaterThan: case ComparisonConditionType.GreaterThanOrEqual: { throw leftComparableOperand switch { null when rightComparableOperand is null => new ArgumentException(string.Format( CultureInfo.CurrentCulture, "Binding property of type {0} and Value property of type {1} cannot be used with operator {2}.", leftOperand?.GetType().Name ?? "null", rightOperand?.GetType().Name ?? "null", operatorType.ToString())), null => new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Binding property of type {0} cannot be used with operator {1}.", leftOperand?.GetType().Name ?? "null", operatorType.ToString())), _ => new ArgumentException(string.Format(CultureInfo.CurrentCulture, "Value property of type {0} cannot be used with operator {1}.", rightOperand?.GetType().Name ?? "null", operatorType.ToString())) }; } } return false; } /// /// Evaluates both operands that implement the IComparable interface. /// private static bool EvaluateComparable(IComparable leftOperand, ComparisonConditionType operatorType, IComparable rightOperand) { object? convertedOperand = null; try { convertedOperand = Convert.ChangeType(rightOperand, leftOperand.GetType(), CultureInfo.CurrentCulture); } catch (FormatException) { // FormatException: Convert.ChangeType("hello", typeof(double), ...); } catch (InvalidCastException) { // InvalidCastException: Convert.ChangeType(4.0d, typeof(Rectangle), ...); } if (convertedOperand is null) { return operatorType == ComparisonConditionType.NotEqual; } var comparison = leftOperand.CompareTo((IComparable)convertedOperand); return operatorType switch { ComparisonConditionType.Equal => comparison == 0, ComparisonConditionType.NotEqual => comparison != 0, ComparisonConditionType.LessThan => comparison < 0, ComparisonConditionType.LessThanOrEqual => comparison <= 0, ComparisonConditionType.GreaterThan => comparison > 0, ComparisonConditionType.GreaterThanOrEqual => comparison >= 0, _ => false }; } private static void OnValueChanged(AvaloniaPropertyChangedEventArgs args) { if (args.Sender is not DataTriggerBehavior behavior || behavior.AssociatedObject is null) { return; } if (!behavior.IsEnabled) { return; } // NOTE: In UWP version binding null check is not present but Avalonia throws exception as Bindings are null when first initialized. var binding = behavior.Binding; if (binding is not null) { // Some value has changed--either the binding value, reference value, or the comparison condition. Re-evaluate the equation. if (Compare(behavior.Binding, behavior.ComparisonCondition, behavior.Value)) { Interaction.ExecuteActions(behavior.AssociatedObject, behavior.Actions, args); } } } }