diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs
index 65227a826a..93f5e0fc14 100644
--- a/src/Avalonia.Controls/Expander.cs
+++ b/src/Avalonia.Controls/Expander.cs
@@ -59,12 +59,11 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly DirectProperty IsExpandedProperty =
- AvaloniaProperty.RegisterDirect(
+ public static readonly StyledProperty IsExpandedProperty =
+ AvaloniaProperty.Register(
nameof(IsExpanded),
- o => o.IsExpanded,
- (o, v) => o.IsExpanded = v,
- defaultBindingMode: Data.BindingMode.TwoWay);
+ defaultBindingMode: BindingMode.TwoWay,
+ coerce: CoerceIsExpanded);
///
/// Defines the event.
@@ -99,7 +98,6 @@ namespace Avalonia.Controls
RoutingStrategies.Bubble);
private bool _ignorePropertyChanged = false;
- private bool _isExpanded;
private CancellationTokenSource? _lastTransitionCts;
///
@@ -134,50 +132,8 @@ namespace Avalonia.Controls
///
public bool IsExpanded
{
- get => _isExpanded;
- set
- {
- // It is important here that IsExpanded is a direct property so events can be invoked
- // BEFORE the property system gets notified of updated values. This is because events
- // may be canceled by external code.
- if (_isExpanded != value)
- {
- RoutedEventArgs eventArgs;
-
- if (value)
- {
- eventArgs = new RoutedEventArgs(ExpandingEvent, this);
- OnExpanding(eventArgs);
- }
- else
- {
- eventArgs = new RoutedEventArgs(CollapsingEvent, this);
- OnCollapsing(eventArgs);
- }
-
- if (eventArgs.Handled)
- {
- // If the event was externally handled (canceled) we must still notify the value has changed.
- // This property changed notification will update any external code observing this property that itself may have set the new value.
- // We are essentially reverted any external state change along with ignoring the IsExpanded property set.
- // Remember IsExpanded is usually controlled by a ToggleButton in the control theme.
- _ignorePropertyChanged = true;
-
- RaisePropertyChanged(
- IsExpandedProperty,
- oldValue: value,
- newValue: _isExpanded,
- BindingPriority.LocalValue,
- isEffectiveValue: true);
-
- _ignorePropertyChanged = false;
- }
- else
- {
- SetAndRaise(IsExpandedProperty, ref _isExpanded, value);
- }
- }
- }
+ get => GetValue(IsExpandedProperty);
+ set => SetValue(IsExpandedProperty, value);
}
///
@@ -332,5 +288,63 @@ namespace Avalonia.Controls
PseudoClasses.Set(":expanded", IsExpanded);
}
+
+ ///
+ /// Called when the property has to be coerced.
+ ///
+ /// The value to coerce.
+ protected virtual bool OnCoerceIsExpanded(bool value)
+ {
+ RoutedEventArgs eventArgs;
+
+ if (value)
+ {
+ eventArgs = new RoutedEventArgs(ExpandingEvent, this);
+ OnExpanding(eventArgs);
+ }
+ else
+ {
+ eventArgs = new RoutedEventArgs(CollapsingEvent, this);
+ OnCollapsing(eventArgs);
+ }
+
+ if (eventArgs.Handled)
+ {
+ // If the event was externally handled (canceled) we must still notify the value has changed.
+ // This property changed notification will update any external code observing this property that itself may have set the new value.
+ // We are essentially reverted any external state change along with ignoring the IsExpanded property set.
+ // Remember IsExpanded is usually controlled by a ToggleButton in the control theme and is also used for animations.
+ _ignorePropertyChanged = true;
+
+ RaisePropertyChanged(
+ IsExpandedProperty,
+ oldValue: value,
+ newValue: !value,
+ BindingPriority.LocalValue,
+ isEffectiveValue: true);
+
+ _ignorePropertyChanged = false;
+
+ return !value;
+ }
+
+ return value;
+ }
+
+ ///
+ /// Coerces/validates the property value.
+ ///
+ /// The instance.
+ /// The value to coerce.
+ /// The coerced/validated value.
+ private static bool CoerceIsExpanded(AvaloniaObject instance, bool value)
+ {
+ if (instance is Expander expander)
+ {
+ return expander.OnCoerceIsExpanded(value);
+ }
+
+ return value;
+ }
}
}