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);
}
}
}
}