diff --git a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
index c38124e6da..f4c3aa7596 100644
--- a/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
+++ b/src/Android/Avalonia.Android/Platform/AndroidInsetsManager.cs
@@ -8,6 +8,7 @@ using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Animation.Easings;
using Avalonia.Controls.Platform;
using Avalonia.Media;
+using Avalonia.Threading;
using AndroidWindow = Android.Views.Window;
namespace Avalonia.Android.Platform
@@ -40,7 +41,7 @@ namespace Avalonia.Android.Platform
if (oldState != value && Build.VERSION.SdkInt <= BuildVersionCodes.Q)
{
var currentRect = OccludedRect;
- StateChanged?.Invoke(this, new InputPaneStateEventArgs(value, _previousRect, currentRect, TimeSpan.Zero, null));
+ NotifyStateChanged(value, _previousRect, currentRect, TimeSpan.Zero, null);
_previousRect = currentRect;
}
}
@@ -151,7 +152,12 @@ namespace Avalonia.Android.Platform
private void NotifySafeAreaChanged(Thickness safeAreaPadding)
{
- SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding));
+ Dispatcher.UIThread.Send(_ => SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(safeAreaPadding)));
+ }
+
+ private void NotifyStateChanged(InputPaneState newState, Rect? startRect, Rect endRect, TimeSpan animationDuration, IEasing easing)
+ {
+ Dispatcher.UIThread.Send(_ => StateChanged?.Invoke(this, new InputPaneStateEventArgs(newState, startRect, endRect, animationDuration, easing)));
}
public void OnGlobalLayout()
@@ -294,7 +300,7 @@ namespace Avalonia.Android.Platform
var duration = TimeSpan.FromMilliseconds(animation.DurationMillis);
bool isOpening = State == InputPaneState.Open;
- StateChanged?.Invoke(this, new InputPaneStateEventArgs(State, isOpening ? upperRect : lowerRect, isOpening ? lowerRect : upperRect, duration, new AnimationEasing(animation.Interpolator)));
+ NotifyStateChanged(State, isOpening ? upperRect : lowerRect, isOpening ? lowerRect : upperRect, duration, new AnimationEasing(animation.Interpolator));
}
}
diff --git a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
index a63682a0b9..e6cc6b872d 100644
--- a/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
+++ b/src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
@@ -3,6 +3,7 @@ using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Media;
using Avalonia.Metadata;
+using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Platform
@@ -50,7 +51,8 @@ namespace Avalonia.Platform
protected void OnColorValuesChanged(PlatformColorValues colorValues)
{
- ColorValuesChanged?.Invoke(this, colorValues);
+ Dispatcher.UIThread.Send(
+ _ => ColorValuesChanged?.Invoke(this, colorValues));
}
}
}
diff --git a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
index dbf95fb609..5a2dca4cf2 100644
--- a/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
+++ b/src/Avalonia.Base/Threading/Dispatcher.Invoke.cs
@@ -635,7 +635,7 @@ public partial class Dispatcher
///
/// When on the same thread with Send priority, callback is executed immediately, without changing synchronization context.
///
- internal void Send(SendOrPostCallback action, object? arg, DispatcherPriority? priority = null)
+ internal void Send(SendOrPostCallback action, object? arg = null, DispatcherPriority? priority = null)
{
_ = action ?? throw new ArgumentNullException(nameof(action));
priority ??= DispatcherPriority.Send;
diff --git a/src/Avalonia.Controls/Platform/IInputPane.cs b/src/Avalonia.Controls/Platform/IInputPane.cs
index c36788570c..a272879dd2 100644
--- a/src/Avalonia.Controls/Platform/IInputPane.cs
+++ b/src/Avalonia.Controls/Platform/IInputPane.cs
@@ -1,6 +1,7 @@
using System;
using Avalonia.Animation.Easings;
using Avalonia.Metadata;
+using Avalonia.Threading;
namespace Avalonia.Controls.Platform
{
@@ -26,6 +27,20 @@ namespace Avalonia.Controls.Platform
event EventHandler? StateChanged;
}
+ [PrivateApi]
+ public abstract class InputPaneBase : IInputPane
+ {
+ public virtual InputPaneState State { get; protected set; }
+ public virtual Rect OccludedRect { get; protected set; }
+ public event EventHandler? StateChanged;
+
+ protected void OnStateChanged(InputPaneStateEventArgs eventArgs)
+ {
+ Dispatcher.UIThread.Send(
+ _ => StateChanged?.Invoke(this, eventArgs));
+ }
+ }
+
///
/// The input pane opened state.
///
diff --git a/src/Avalonia.Controls/Platform/IInsetsManager.cs b/src/Avalonia.Controls/Platform/IInsetsManager.cs
index c604b89e5c..a8b692c82a 100644
--- a/src/Avalonia.Controls/Platform/IInsetsManager.cs
+++ b/src/Avalonia.Controls/Platform/IInsetsManager.cs
@@ -1,11 +1,11 @@
using System;
using Avalonia.Media;
using Avalonia.Metadata;
+using Avalonia.Threading;
#nullable enable
namespace Avalonia.Controls.Platform
{
- [Unstable]
[NotClientImplementable]
public interface IInsetsManager
{
@@ -34,6 +34,23 @@ namespace Avalonia.Controls.Platform
///
event EventHandler? SafeAreaChanged;
}
+
+ [PrivateApi]
+ public abstract class InsetsManagerBase : IInsetsManager
+ {
+ public virtual bool? IsSystemBarVisible { get; set; }
+ public virtual bool DisplayEdgeToEdge { get; set; }
+ public virtual Thickness SafeAreaPadding { get; protected set; }
+ public virtual Color? SystemBarColor { get; set; }
+ public event EventHandler? SafeAreaChanged;
+
+ protected void OnSafeAreaChanged(SafeAreaChangedArgs eventArgs)
+ {
+ Dispatcher.UIThread.Send(
+ _ => SafeAreaChanged?.Invoke(this, eventArgs),
+ null, DispatcherPriority.Send);
+ }
+ }
public class SafeAreaChangedArgs : EventArgs
{
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 23841a8130..70e531a789 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -272,7 +272,7 @@ namespace Avalonia.Controls
systemNavigationManager.BackRequested += (_, e) =>
{
e.RoutedEvent = BackRequestedEvent;
- RaiseEvent(e);
+ Dispatcher.UIThread.Send(_ => RaiseEvent(e));
};
}
@@ -306,7 +306,7 @@ namespace Avalonia.Controls
if (backRequested)
{
var backRequestedEventArgs = new RoutedEventArgs(BackRequestedEvent);
- RaiseEvent(backRequestedEventArgs);
+ Dispatcher.UIThread.Send(_ => RaiseEvent(backRequestedEventArgs));
e.Handled = backRequestedEventArgs.Handled;
}
@@ -739,7 +739,7 @@ namespace Avalonia.Controls
private void HandleScalingChanged(double scaling)
{
LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
- ScalingChanged?.Invoke(this, EventArgs.Empty);
+ Dispatcher.UIThread.Send(_ => ScalingChanged?.Invoke(this, EventArgs.Empty));
}
private void HandleTransparencyLevelChanged(WindowTransparencyLevel transparencyLevel)
@@ -786,14 +786,17 @@ namespace Avalonia.Controls
protected virtual void OnOpened(EventArgs e)
{
FrameSize = PlatformImpl?.FrameSize;
- Opened?.Invoke(this, e);
- }
+ Dispatcher.UIThread.Send(_ => Opened?.Invoke(this, e));
+ }
///
/// Raises the event.
///
/// The event args.
- protected virtual void OnClosed(EventArgs e) => Closed?.Invoke(this, e);
+ protected virtual void OnClosed(EventArgs e)
+ {
+ Dispatcher.UIThread.Send(_ => Closed?.Invoke(this, e));
+ }
///
/// Tries to get a service from an , logging a
diff --git a/src/Browser/Avalonia.Browser/BrowserInputPane.cs b/src/Browser/Avalonia.Browser/BrowserInputPane.cs
index 0b96599fcc..be132ee956 100644
--- a/src/Browser/Avalonia.Browser/BrowserInputPane.cs
+++ b/src/Browser/Avalonia.Browser/BrowserInputPane.cs
@@ -5,17 +5,13 @@ using Avalonia.Controls.Platform;
namespace Avalonia.Browser;
-internal class BrowserInputPane : IInputPane
+internal class BrowserInputPane : InputPaneBase
{
public BrowserInputPane(JSObject container)
{
InputHelper.SubscribeKeyboardGeometryChange(container, OnGeometryChange);
}
- public InputPaneState State { get; private set; }
- public Rect OccludedRect { get; private set; }
- public event EventHandler? StateChanged;
-
private bool OnGeometryChange(JSObject args)
{
var oldState = (OccludedRect, State);
@@ -29,7 +25,7 @@ internal class BrowserInputPane : IInputPane
if (oldState != (OccludedRect, State))
{
- StateChanged?.Invoke(this, new InputPaneStateEventArgs(State, null, OccludedRect));
+ OnStateChanged(new InputPaneStateEventArgs(State, null, OccludedRect));
}
return true;
diff --git a/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs b/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs
index 0f64003699..dac5a3f14a 100644
--- a/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs
+++ b/src/Browser/Avalonia.Browser/BrowserInsetsManager.cs
@@ -10,9 +10,9 @@ using static Avalonia.Controls.Platform.IInsetsManager;
namespace Avalonia.Browser
{
- internal class BrowserInsetsManager : IInsetsManager
+ internal class BrowserInsetsManager : InsetsManagerBase
{
- public bool? IsSystemBarVisible
+ public override bool? IsSystemBarVisible
{
get
{
@@ -24,11 +24,9 @@ namespace Avalonia.Browser
}
}
- public bool DisplayEdgeToEdge { get; set; }
+ public override bool DisplayEdgeToEdge { get; set; }
- public event EventHandler? SafeAreaChanged;
-
- public Thickness SafeAreaPadding
+ public override Thickness SafeAreaPadding
{
get
{
@@ -38,11 +36,11 @@ namespace Avalonia.Browser
}
}
- public Color? SystemBarColor { get; set; }
+ public override Color? SystemBarColor { get; set; }
public void NotifySafeAreaPaddingChanged()
{
- SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));
+ OnSafeAreaChanged(new SafeAreaChangedArgs(SafeAreaPadding));
}
}
}
diff --git a/src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs b/src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs
index cc20d06dc7..73195ad500 100644
--- a/src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs
+++ b/src/Windows/Avalonia.Win32/Input/WindowsInputPane.cs
@@ -8,7 +8,7 @@ using MicroCom.Runtime;
namespace Avalonia.Win32.Input;
-internal unsafe class WindowsInputPane : IInputPane, IDisposable
+internal unsafe class WindowsInputPane : InputPaneBase, IDisposable
{
private static readonly Lazy s_inputPaneSupported = new(() =>
WinRTApiInformation.IsTypePresent("Windows.UI.ViewManagement.InputPane"));
@@ -48,12 +48,6 @@ internal unsafe class WindowsInputPane : IInputPane, IDisposable
return null;
}
-
- public InputPaneState State { get; private set; }
-
- public Rect OccludedRect { get; private set; }
-
- public event EventHandler? StateChanged;
private void OnStateChanged(bool showing, UnmanagedMethods.RECT? prcInputPaneScreenLocation)
{
@@ -65,7 +59,7 @@ internal unsafe class WindowsInputPane : IInputPane, IDisposable
if (oldState != (OccludedRect, State))
{
- StateChanged?.Invoke(this, new InputPaneStateEventArgs(State, null, OccludedRect));
+ OnStateChanged(new InputPaneStateEventArgs(State, null, OccludedRect));
}
}
diff --git a/src/iOS/Avalonia.iOS/InsetsManager.cs b/src/iOS/Avalonia.iOS/InsetsManager.cs
index d1439fcd3f..105e208ee9 100644
--- a/src/iOS/Avalonia.iOS/InsetsManager.cs
+++ b/src/iOS/Avalonia.iOS/InsetsManager.cs
@@ -4,7 +4,7 @@ using Avalonia.Media;
namespace Avalonia.iOS;
-internal class InsetsManager : IInsetsManager
+internal class InsetsManager : InsetsManagerBase
{
private IAvaloniaViewController? _controller;
private bool _displayEdgeToEdge = true;
@@ -16,13 +16,13 @@ internal class InsetsManager : IInsetsManager
{
_controller.SafeAreaPaddingChanged += (_, _) =>
{
- SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));
+ OnSafeAreaChanged(new SafeAreaChangedArgs(SafeAreaPadding));
DisplayEdgeToEdgeChanged?.Invoke(this, _displayEdgeToEdge);
};
}
}
- public bool? IsSystemBarVisible
+ public override bool? IsSystemBarVisible
{
get => _controller?.PrefersStatusBarHidden == false;
set
@@ -33,10 +33,9 @@ internal class InsetsManager : IInsetsManager
}
}
}
- public event EventHandler? SafeAreaChanged;
public event EventHandler? DisplayEdgeToEdgeChanged;
- public bool DisplayEdgeToEdge
+ public override bool DisplayEdgeToEdge
{
get => _displayEdgeToEdge;
set
@@ -45,12 +44,12 @@ internal class InsetsManager : IInsetsManager
{
_displayEdgeToEdge = value;
DisplayEdgeToEdgeChanged?.Invoke(this, value);
- SafeAreaChanged?.Invoke(this, new SafeAreaChangedArgs(SafeAreaPadding));
+ OnSafeAreaChanged(new SafeAreaChangedArgs(SafeAreaPadding));
}
}
}
- public Thickness SafeAreaPadding => _displayEdgeToEdge ? _controller?.SafeAreaPadding ?? default : default;
+ public override Thickness SafeAreaPadding => _displayEdgeToEdge ? _controller?.SafeAreaPadding ?? default : default;
- public Color? SystemBarColor { get; set; }
+ public override Color? SystemBarColor { get; set; }
}
diff --git a/src/iOS/Avalonia.iOS/UIKitInputPane.cs b/src/iOS/Avalonia.iOS/UIKitInputPane.cs
index 8706398104..e2ca9ed023 100644
--- a/src/iOS/Avalonia.iOS/UIKitInputPane.cs
+++ b/src/iOS/Avalonia.iOS/UIKitInputPane.cs
@@ -11,7 +11,7 @@ namespace Avalonia.iOS;
[UnsupportedOSPlatform("tvos")]
[SupportedOSPlatform("maccatalyst")]
[SupportedOSPlatform("ios")]
-internal sealed class UIKitInputPane : IInputPane
+internal sealed class UIKitInputPane : InputPaneBase
{
public static UIKitInputPane Instance { get; } = new();
@@ -24,11 +24,7 @@ internal sealed class UIKitInputPane : IInputPane
.DefaultCenter
.AddObserver(UIKeyboard.WillHideNotification, KeyboardDownNotification);
}
-
- public InputPaneState State { get; private set; }
- public Rect OccludedRect { get; private set; }
- public event EventHandler? StateChanged;
-
+
private void KeyboardDownNotification(NSNotification obj) => RaiseEventFromNotification(false, obj);
private void KeyboardUpNotification(NSNotification obj) => RaiseEventFromNotification(true, obj);
@@ -38,7 +34,7 @@ internal sealed class UIKitInputPane : IInputPane
State = isUp ? InputPaneState.Open : InputPaneState.Closed;
#if MACCATALYST
OccludedRect = default;
- StateChanged?.Invoke(this, new InputPaneStateEventArgs(
+ OnStateChanged(new InputPaneStateEventArgs(
State, null, OccludedRect));
#else
var startFrame = UIKeyboard.FrameBeginFromNotification(notification);
@@ -55,7 +51,7 @@ internal sealed class UIKitInputPane : IInputPane
var startRect = new Rect(startFrame.X, startFrame.Y, startFrame.Width, startFrame.Height);
OccludedRect = new Rect(endFrame.X, endFrame.Y, endFrame.Width, endFrame.Height);
- StateChanged?.Invoke(this, new InputPaneStateEventArgs(
+ OnStateChanged(new InputPaneStateEventArgs(
State, startRect, OccludedRect, TimeSpan.FromSeconds(duration), easing));
#endif
}