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