Browse Source

Merge pull request #9979 from robloo/expander-isexpanded-styled-prop

Switch Expander.IsExpanded to a StyledProperty
pull/10104/head
Max Katz 3 years ago
committed by GitHub
parent
commit
2072b19817
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      samples/ControlCatalog/Pages/ExpanderPage.xaml.cs
  2. 39
      src/Avalonia.Base/Interactivity/CancelRoutedEventArgs.cs
  3. 130
      src/Avalonia.Controls/Expander.cs

4
samples/ControlCatalog/Pages/ExpanderPage.xaml.cs

@ -14,8 +14,8 @@ namespace ControlCatalog.Pages
var CollapsingDisabledExpander = this.Get<Expander>("CollapsingDisabledExpander");
var ExpandingDisabledExpander = this.Get<Expander>("ExpandingDisabledExpander");
CollapsingDisabledExpander.Collapsing += (s, e) => { e.Handled = true; };
ExpandingDisabledExpander.Expanding += (s, e) => { e.Handled = true; };
CollapsingDisabledExpander.Collapsing += (s, e) => { e.Cancel = true; };
ExpandingDisabledExpander.Expanding += (s, e) => { e.Cancel = true; };
}
private void InitializeComponent()

39
src/Avalonia.Base/Interactivity/CancelRoutedEventArgs.cs

@ -0,0 +1,39 @@
namespace Avalonia.Interactivity
{
/// <summary>
/// Provides state information and data specific to a cancelable routed event.
/// </summary>
public class CancelRoutedEventArgs : RoutedEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="CancelRoutedEventArgs"/> class.
/// </summary>
public CancelRoutedEventArgs()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CancelRoutedEventArgs"/> class.
/// </summary>
/// <param name="routedEvent">The routed event associated with these event args.</param>
public CancelRoutedEventArgs(RoutedEvent? routedEvent)
: base(routedEvent)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CancelRoutedEventArgs"/> class.
/// </summary>
/// <param name="routedEvent">The routed event associated with these event args.</param>
/// <param name="source">The source object that raised the routed event.</param>
public CancelRoutedEventArgs(RoutedEvent? routedEvent, object? source)
: base(routedEvent, source)
{
}
/// <summary>
/// Gets or sets a value indicating whether the routed event should be canceled.
/// </summary>
public bool Cancel { get; set; } = false;
}
}

130
src/Avalonia.Controls/Expander.cs

@ -59,12 +59,11 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="IsExpanded"/> property.
/// </summary>
public static readonly DirectProperty<Expander, bool> IsExpandedProperty =
AvaloniaProperty.RegisterDirect<Expander, bool>(
public static readonly StyledProperty<bool> IsExpandedProperty =
AvaloniaProperty.Register<Expander, bool>(
nameof(IsExpanded),
o => o.IsExpanded,
(o, v) => o.IsExpanded = v,
defaultBindingMode: Data.BindingMode.TwoWay);
defaultBindingMode: BindingMode.TwoWay,
coerce: CoerceIsExpanded);
/// <summary>
/// Defines the <see cref="Collapsed"/> event.
@ -77,8 +76,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Collapsing"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> CollapsingEvent =
RoutedEvent.Register<Expander, RoutedEventArgs>(
public static readonly RoutedEvent<CancelRoutedEventArgs> CollapsingEvent =
RoutedEvent.Register<Expander, CancelRoutedEventArgs>(
nameof(Collapsing),
RoutingStrategies.Bubble);
@ -93,13 +92,12 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Expanding"/> event.
/// </summary>
public static readonly RoutedEvent<RoutedEventArgs> ExpandingEvent =
RoutedEvent.Register<Expander, RoutedEventArgs>(
public static readonly RoutedEvent<CancelRoutedEventArgs> ExpandingEvent =
RoutedEvent.Register<Expander, CancelRoutedEventArgs>(
nameof(Expanding),
RoutingStrategies.Bubble);
private bool _ignorePropertyChanged = false;
private bool _isExpanded;
private CancellationTokenSource? _lastTransitionCts;
/// <summary>
@ -134,50 +132,8 @@ namespace Avalonia.Controls
/// </summary>
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);
}
/// <summary>
@ -193,10 +149,10 @@ namespace Avalonia.Controls
/// Occurs as the content area is closing.
/// </summary>
/// <remarks>
/// The event args <see cref="RoutedEventArgs.Handled"/> property may be set to true to cancel the event
/// The event args <see cref="CancelRoutedEventArgs.Cancel"/> property may be set to true to cancel the event
/// and keep the control open (expanded).
/// </remarks>
public event EventHandler<RoutedEventArgs>? Collapsing
public event EventHandler<CancelRoutedEventArgs>? Collapsing
{
add => AddHandler(CollapsingEvent, value);
remove => RemoveHandler(CollapsingEvent, value);
@ -215,10 +171,10 @@ namespace Avalonia.Controls
/// Occurs as the content area is opening.
/// </summary>
/// <remarks>
/// The event args <see cref="RoutedEventArgs.Handled"/> property may be set to true to cancel the event
/// The event args <see cref="CancelRoutedEventArgs.Cancel"/> property may be set to true to cancel the event
/// and keep the control closed (collapsed).
/// </remarks>
public event EventHandler<RoutedEventArgs>? Expanding
public event EventHandler<CancelRoutedEventArgs>? Expanding
{
add => AddHandler(ExpandingEvent, value);
remove => RemoveHandler(ExpandingEvent, value);
@ -332,5 +288,63 @@ namespace Avalonia.Controls
PseudoClasses.Set(":expanded", IsExpanded);
}
/// <summary>
/// Called when the <see cref="IsExpanded"/> property has to be coerced.
/// </summary>
/// <param name="value">The value to coerce.</param>
protected virtual bool OnCoerceIsExpanded(bool value)
{
CancelRoutedEventArgs eventArgs;
if (value)
{
eventArgs = new CancelRoutedEventArgs(ExpandingEvent, this);
OnExpanding(eventArgs);
}
else
{
eventArgs = new CancelRoutedEventArgs(CollapsingEvent, this);
OnCollapsing(eventArgs);
}
if (eventArgs.Cancel)
{
// If the event was externally 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;
}
/// <summary>
/// Coerces/validates the <see cref="IsExpanded"/> property value.
/// </summary>
/// <param name="instance">The <see cref="Expander"/> instance.</param>
/// <param name="value">The value to coerce.</param>
/// <returns>The coerced/validated value.</returns>
private static bool CoerceIsExpanded(AvaloniaObject instance, bool value)
{
if (instance is Expander expander)
{
return expander.OnCoerceIsExpanded(value);
}
return value;
}
}
}

Loading…
Cancel
Save