diff --git a/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs b/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs index ccbb41b7dc5..2b660e70809 100644 --- a/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs +++ b/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs @@ -2,29 +2,59 @@ namespace Avalonia.Interactivity { + /// + /// Provides state information and data specific to a routed event. + /// public class RoutedEventArgs : EventArgs { + /// + /// Initializes a new instance of the class. + /// public RoutedEventArgs() { } + /// + /// Initializes a new instance of the class. + /// + /// The routed event associated with these event args. public RoutedEventArgs(RoutedEvent? routedEvent) { RoutedEvent = routedEvent; } + /// + /// Initializes a new instance of the class. + /// + /// The routed event associated with these event args. + /// The source object that raised the routed event. public RoutedEventArgs(RoutedEvent? routedEvent, IInteractive? source) { RoutedEvent = routedEvent; Source = source; } + /// + /// Gets or sets a value indicating whether the routed event has already been handled. + /// + /// + /// Once handled, a routed event should be ignored. + /// public bool Handled { get; set; } + /// + /// Gets or sets the routed event associated with these event args. + /// public RoutedEvent? RoutedEvent { get; set; } + /// + /// Gets or sets the routing strategy (direct, bubbling, or tunneling) of the routed event. + /// public RoutingStrategies Route { get; set; } + /// + /// Gets or sets the source object that raised the routed event. + /// public IInteractive? Source { get; set; } } } diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index 524362fcf9d..beaee34b076 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -84,6 +84,13 @@ public class Control : InputElement, IControl, INamed, IVisualBrushInitialize, I nameof(Unloaded), RoutingStrategies.Direct); + /// + /// Defines the event. + /// + public static readonly RoutedEvent SizeChangedEvent = + RoutedEvent.Register( + nameof(SizeChanged), RoutingStrategies.Direct); + /// /// Defines the property. /// @@ -211,6 +218,15 @@ public event EventHandler? Unloaded remove => RemoveHandler(UnloadedEvent, value); } + /// + /// Occurs when the bounds (actual size) of the control have changed. + /// + public event EventHandler? SizeChanged + { + add => AddHandler(SizeChangedEvent, value); + remove => RemoveHandler(SizeChangedEvent, value); + } + public new IControl? Parent => (IControl?)base.Parent; /// @@ -530,14 +546,35 @@ protected override void OnKeyUp(KeyEventArgs e) } } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); - if (change.Property == FlowDirectionProperty) + if (change.Property == BoundsProperty) + { + var oldValue = change.GetOldValue(); + var newValue = change.GetNewValue(); + + // Bounds is a Rect with an X/Y Position as well as Height/Width. + // This means it is possible for the Rect to change position but not size. + // Therefore, we want to explicity check only the size and raise an event + // only when that size has changed. + if (newValue.Size != oldValue.Size) + { + var sizeChangedEventArgs = new SizeChangedEventArgs( + SizeChangedEvent, + source: this, + previousSize: new Size(oldValue.Width, oldValue.Height), + newSize: new Size(newValue.Width, newValue.Height)); + + RaiseEvent(sizeChangedEventArgs); + } + } + else if (change.Property == FlowDirectionProperty) { InvalidateMirrorTransform(); - + foreach (var visual in VisualChildren) { if (visual is Control child) diff --git a/src/Avalonia.Controls/SizeChangedEventArgs.cs b/src/Avalonia.Controls/SizeChangedEventArgs.cs new file mode 100644 index 00000000000..2dc642b1638 --- /dev/null +++ b/src/Avalonia.Controls/SizeChangedEventArgs.cs @@ -0,0 +1,83 @@ +using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Utilities; + +namespace Avalonia.Controls +{ + /// + /// Provides data specific to a SizeChanged event. + /// + public class SizeChangedEventArgs : RoutedEventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The routed event associated with these event args. + public SizeChangedEventArgs(RoutedEvent? routedEvent) + : base (routedEvent) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The routed event associated with these event args. + /// The source object that raised the routed event. + public SizeChangedEventArgs(RoutedEvent? routedEvent, IInteractive? source) + : base(routedEvent, source) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The routed event associated with these event args. + /// The source object that raised the routed event. + /// The previous size (or bounds) of the object. + /// The new size (or bounds) of the object. + public SizeChangedEventArgs( + RoutedEvent? routedEvent, + IInteractive? source, + Size previousSize, + Size newSize) + : base(routedEvent, source) + { + PreviousSize = previousSize; + NewSize = newSize; + } + + /// + /// Gets a value indicating whether the height of the new size is considered + /// different than the previous size height. + /// + /// + /// This will take into account layout epsilon and will not be true if both + /// heights are considered equivalent for layout purposes. Remember there can + /// be small variations in the calculations between layout cycles due to + /// rounding and precision even when the size has not otherwise changed. + /// + public bool HeightChanged => !MathUtilities.AreClose(NewSize.Height, PreviousSize.Height, LayoutHelper.LayoutEpsilon); + + /// + /// Gets the new size (or bounds) of the object. + /// + public Size NewSize { get; init; } + + /// + /// Gets the previous size (or bounds) of the object. + /// + public Size PreviousSize { get; init; } + + /// + /// Gets a value indicating whether the width of the new size is considered + /// different than the previous size width. + /// + /// + /// This will take into account layout epsilon and will not be true if both + /// widths are considered equivalent for layout purposes. Remember there can + /// be small variations in the calculations between layout cycles due to + /// rounding and precision even when the size has not otherwise changed. + /// + public bool WidthChanged => !MathUtilities.AreClose(NewSize.Width, PreviousSize.Width, LayoutHelper.LayoutEpsilon); + } +}