Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Control.SizeChanged Event #9307

Merged
merged 7 commits into from
Nov 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/Avalonia.Base/Interactivity/RoutedEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,59 @@

namespace Avalonia.Interactivity
{
/// <summary>
/// Provides state information and data specific to a routed event.
/// </summary>
public class RoutedEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="RoutedEventArgs"/> class.
/// </summary>
public RoutedEventArgs()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="RoutedEventArgs"/> class.
/// </summary>
/// <param name="routedEvent">The routed event associated with these event args.</param>
public RoutedEventArgs(RoutedEvent? routedEvent)
{
RoutedEvent = routedEvent;
}

/// <summary>
/// Initializes a new instance of the <see cref="RoutedEventArgs"/> 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 RoutedEventArgs(RoutedEvent? routedEvent, IInteractive? source)
{
RoutedEvent = routedEvent;
Source = source;
}

/// <summary>
/// Gets or sets a value indicating whether the routed event has already been handled.
/// </summary>
/// <remarks>
/// Once handled, a routed event should be ignored.
/// </remarks>
public bool Handled { get; set; }

/// <summary>
/// Gets or sets the routed event associated with these event args.
/// </summary>
public RoutedEvent? RoutedEvent { get; set; }

/// <summary>
/// Gets or sets the routing strategy (direct, bubbling, or tunneling) of the routed event.
/// </summary>
public RoutingStrategies Route { get; set; }

/// <summary>
/// Gets or sets the source object that raised the routed event.
/// </summary>
public IInteractive? Source { get; set; }
}
}
41 changes: 39 additions & 2 deletions src/Avalonia.Controls/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ public class Control : InputElement, IControl, INamed, IVisualBrushInitialize, I
nameof(Unloaded),
RoutingStrategies.Direct);

/// <summary>
/// Defines the <see cref="SizeChanged"/> event.
/// </summary>
public static readonly RoutedEvent<SizeChangedEventArgs> SizeChangedEvent =
RoutedEvent.Register<Control, SizeChangedEventArgs>(
nameof(SizeChanged), RoutingStrategies.Direct);
maxkatz6 marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Defines the <see cref="FlowDirection"/> property.
/// </summary>
Expand Down Expand Up @@ -211,6 +218,15 @@ public event EventHandler<RoutedEventArgs>? Unloaded
remove => RemoveHandler(UnloadedEvent, value);
}

/// <summary>
/// Occurs when the bounds (actual size) of the control have changed.
/// </summary>
public event EventHandler<SizeChangedEventArgs>? SizeChanged
{
add => AddHandler(SizeChangedEvent, value);
remove => RemoveHandler(SizeChangedEvent, value);
}

public new IControl? Parent => (IControl?)base.Parent;

/// <summary>
Expand Down Expand Up @@ -530,14 +546,35 @@ protected override void OnKeyUp(KeyEventArgs e)
}
}

/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

if (change.Property == FlowDirectionProperty)
if (change.Property == BoundsProperty)
{
var oldValue = change.GetOldValue<Rect>();
var newValue = change.GetNewValue<Rect>();

// 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)
Expand Down
83 changes: 83 additions & 0 deletions src/Avalonia.Controls/SizeChangedEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Utilities;

namespace Avalonia.Controls
{
/// <summary>
/// Provides data specific to a SizeChanged event.
/// </summary>
public class SizeChangedEventArgs : RoutedEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="SizeChangedEventArgs"/> class.
/// </summary>
/// <param name="routedEvent">The routed event associated with these event args.</param>
public SizeChangedEventArgs(RoutedEvent? routedEvent)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if these events should have internal ctor. Like it was discussed here #8860
But either way it's not important right now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That conversation doesn't make sense to me. There is little harm in having these public -- this API isn't going to change. More though, how are apps supposed to implement this event themselves if constructors are private? I never understood why you are hiding constructors.

: base (routedEvent)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SizeChangedEventArgs"/> 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 SizeChangedEventArgs(RoutedEvent? routedEvent, IInteractive? source)
: base(routedEvent, source)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="SizeChangedEventArgs"/> 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>
/// <param name="previousSize">The previous size (or bounds) of the object.</param>
/// <param name="newSize">The new size (or bounds) of the object.</param>
public SizeChangedEventArgs(
RoutedEvent? routedEvent,
IInteractive? source,
Size previousSize,
Size newSize)
robloo marked this conversation as resolved.
Show resolved Hide resolved
: base(routedEvent, source)
{
PreviousSize = previousSize;
NewSize = newSize;
}

/// <summary>
/// Gets a value indicating whether the height of the new size is considered
/// different than the previous size height.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public bool HeightChanged => !MathUtilities.AreClose(NewSize.Height, PreviousSize.Height, LayoutHelper.LayoutEpsilon);

/// <summary>
/// Gets the new size (or bounds) of the object.
/// </summary>
public Size NewSize { get; init; }
robloo marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Gets the previous size (or bounds) of the object.
/// </summary>
public Size PreviousSize { get; init; }

/// <summary>
/// Gets a value indicating whether the width of the new size is considered
/// different than the previous size width.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public bool WidthChanged => !MathUtilities.AreClose(NewSize.Width, PreviousSize.Width, LayoutHelper.LayoutEpsilon);
}
}