DataTriggerBehavior.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. using System;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Globalization;
  4. using Avalonia.Reactive;
  5. using Avalonia.Xaml.Interactivity;
  6. namespace Avalonia.Xaml.Interactions.Core;
  7. /// <summary>
  8. /// A behavior that performs actions when the bound data meets a specified condition.
  9. /// </summary>
  10. [RequiresUnreferencedCode("This functionality is not compatible with trimming.")]
  11. public class DataTriggerBehavior : StyledElementTrigger
  12. {
  13. /// <summary>
  14. /// Identifies the <seealso cref="Binding"/> avalonia property.
  15. /// </summary>
  16. public static readonly StyledProperty<object?> BindingProperty =
  17. AvaloniaProperty.Register<DataTriggerBehavior, object?>(nameof(Binding));
  18. /// <summary>
  19. /// Identifies the <seealso cref="ComparisonCondition"/> avalonia property.
  20. /// </summary>
  21. public static readonly StyledProperty<ComparisonConditionType> ComparisonConditionProperty =
  22. AvaloniaProperty.Register<DataTriggerBehavior, ComparisonConditionType>(nameof(ComparisonCondition));
  23. /// <summary>
  24. /// Identifies the <seealso cref="Value"/> avalonia property.
  25. /// </summary>
  26. public static readonly StyledProperty<object?> ValueProperty =
  27. AvaloniaProperty.Register<DataTriggerBehavior, object?>(nameof(Value));
  28. /// <summary>
  29. /// Gets or sets the bound object that the <see cref="DataTriggerBehavior"/> will listen to. This is a avalonia property.
  30. /// </summary>
  31. public object? Binding
  32. {
  33. get => GetValue(BindingProperty);
  34. set => SetValue(BindingProperty, value);
  35. }
  36. /// <summary>
  37. /// Gets or sets the type of comparison to be performed between <see cref="DataTriggerBehavior.Binding"/> and <see cref="DataTriggerBehavior.Value"/>. This is a avalonia property.
  38. /// </summary>
  39. public ComparisonConditionType ComparisonCondition
  40. {
  41. get => GetValue(ComparisonConditionProperty);
  42. set => SetValue(ComparisonConditionProperty, value);
  43. }
  44. /// <summary>
  45. /// Gets or sets the value to be compared with the value of <see cref="DataTriggerBehavior.Binding"/>. This is a avalonia property.
  46. /// </summary>
  47. public object? Value
  48. {
  49. get => GetValue(ValueProperty);
  50. set => SetValue(ValueProperty, value);
  51. }
  52. static DataTriggerBehavior()
  53. {
  54. BindingProperty.Changed.Subscribe(
  55. new AnonymousObserver<AvaloniaPropertyChangedEventArgs<object?>>(OnValueChanged));
  56. ComparisonConditionProperty.Changed.Subscribe(
  57. new AnonymousObserver<AvaloniaPropertyChangedEventArgs<ComparisonConditionType>>(OnValueChanged));
  58. ValueProperty.Changed.Subscribe(
  59. new AnonymousObserver<AvaloniaPropertyChangedEventArgs<object?>>(OnValueChanged));
  60. }
  61. private static bool Compare(object? leftOperand, ComparisonConditionType operatorType, object? rightOperand)
  62. {
  63. if (leftOperand is not null && rightOperand is not null)
  64. {
  65. var value = rightOperand.ToString();
  66. var destinationType = leftOperand.GetType();
  67. if (value is not null)
  68. {
  69. rightOperand = TypeConverterHelper.Convert(value, destinationType);
  70. }
  71. }
  72. var leftComparableOperand = leftOperand as IComparable;
  73. var rightComparableOperand = rightOperand as IComparable;
  74. if (leftComparableOperand is not null && rightComparableOperand is not null)
  75. {
  76. return EvaluateComparable(leftComparableOperand, operatorType, rightComparableOperand);
  77. }
  78. switch (operatorType)
  79. {
  80. case ComparisonConditionType.Equal:
  81. return Equals(leftOperand, rightOperand);
  82. case ComparisonConditionType.NotEqual:
  83. return !Equals(leftOperand, rightOperand);
  84. case ComparisonConditionType.LessThan:
  85. case ComparisonConditionType.LessThanOrEqual:
  86. case ComparisonConditionType.GreaterThan:
  87. case ComparisonConditionType.GreaterThanOrEqual:
  88. {
  89. throw leftComparableOperand switch
  90. {
  91. null when rightComparableOperand is null => new ArgumentException(string.Format(
  92. CultureInfo.CurrentCulture,
  93. "Binding property of type {0} and Value property of type {1} cannot be used with operator {2}.",
  94. leftOperand?.GetType().Name ?? "null", rightOperand?.GetType().Name ?? "null",
  95. operatorType.ToString())),
  96. null => new ArgumentException(string.Format(CultureInfo.CurrentCulture,
  97. "Binding property of type {0} cannot be used with operator {1}.",
  98. leftOperand?.GetType().Name ?? "null", operatorType.ToString())),
  99. _ => new ArgumentException(string.Format(CultureInfo.CurrentCulture,
  100. "Value property of type {0} cannot be used with operator {1}.",
  101. rightOperand?.GetType().Name ?? "null", operatorType.ToString()))
  102. };
  103. }
  104. }
  105. return false;
  106. }
  107. /// <summary>
  108. /// Evaluates both operands that implement the IComparable interface.
  109. /// </summary>
  110. private static bool EvaluateComparable(IComparable leftOperand, ComparisonConditionType operatorType, IComparable rightOperand)
  111. {
  112. object? convertedOperand = null;
  113. try
  114. {
  115. convertedOperand = Convert.ChangeType(rightOperand, leftOperand.GetType(), CultureInfo.CurrentCulture);
  116. }
  117. catch (FormatException)
  118. {
  119. // FormatException: Convert.ChangeType("hello", typeof(double), ...);
  120. }
  121. catch (InvalidCastException)
  122. {
  123. // InvalidCastException: Convert.ChangeType(4.0d, typeof(Rectangle), ...);
  124. }
  125. if (convertedOperand is null)
  126. {
  127. return operatorType == ComparisonConditionType.NotEqual;
  128. }
  129. var comparison = leftOperand.CompareTo((IComparable)convertedOperand);
  130. return operatorType switch
  131. {
  132. ComparisonConditionType.Equal => comparison == 0,
  133. ComparisonConditionType.NotEqual => comparison != 0,
  134. ComparisonConditionType.LessThan => comparison < 0,
  135. ComparisonConditionType.LessThanOrEqual => comparison <= 0,
  136. ComparisonConditionType.GreaterThan => comparison > 0,
  137. ComparisonConditionType.GreaterThanOrEqual => comparison >= 0,
  138. _ => false
  139. };
  140. }
  141. private static void OnValueChanged(AvaloniaPropertyChangedEventArgs args)
  142. {
  143. if (args.Sender is not DataTriggerBehavior behavior || behavior.AssociatedObject is null)
  144. {
  145. return;
  146. }
  147. if (!behavior.IsEnabled)
  148. {
  149. return;
  150. }
  151. // NOTE: In UWP version binding null check is not present but Avalonia throws exception as Bindings are null when first initialized.
  152. var binding = behavior.Binding;
  153. if (binding is not null)
  154. {
  155. // Some value has changed--either the binding value, reference value, or the comparison condition. Re-evaluate the equation.
  156. if (Compare(behavior.Binding, behavior.ComparisonCondition, behavior.Value))
  157. {
  158. Interaction.ExecuteActions(behavior.AssociatedObject, behavior.Actions, args);
  159. }
  160. }
  161. }
  162. }