diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index a6f463fdcb..353e01dca7 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -153,6 +153,9 @@ + + + diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 2c9efc7767..d89d6f3690 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -664,14 +664,12 @@ namespace Avalonia /// The property that has changed. /// The old property value. /// The new property value. - /// The priority of the binding that produced the value. protected void RaisePropertyChanged( DirectPropertyBase property, - Optional oldValue, - BindingValue newValue, - BindingPriority priority = BindingPriority.LocalValue) + T oldValue, + T newValue) { - RaisePropertyChanged(property, oldValue, newValue, priority, true); + RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue, true); } /// @@ -720,7 +718,7 @@ namespace Avalonia /// /// True if the value changed, otherwise false. /// - protected bool SetAndRaise(AvaloniaProperty property, ref T field, T value) + protected bool SetAndRaise(DirectPropertyBase property, ref T field, T value) { VerifyAccess(); diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 2cdb973174..5b8dac2f53 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -524,13 +524,7 @@ namespace Avalonia NotifyResourcesChanged(); } -#nullable disable - RaisePropertyChanged( - ParentProperty, - new Optional(old), - new BindingValue(Parent), - BindingPriority.LocalValue); -#nullable enable + RaisePropertyChanged(ParentProperty, old, Parent); } } diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index 87bb1d3790..8b0cc06136 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -573,7 +573,7 @@ namespace Avalonia /// The new visual parent. protected virtual void OnVisualParentChanged(Visual? oldParent, Visual? newParent) { - RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue); + RaisePropertyChanged(VisualParentProperty, oldParent, newParent); } internal override ParametrizedLogger? GetBindingWarningLogger( diff --git a/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs new file mode 100644 index 0000000000..42b15eec96 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/SliderAutomationPeer.cs @@ -0,0 +1,22 @@ +using Avalonia.Automation.Peers; + +namespace Avalonia.Controls.Automation.Peers +{ + public class SliderAutomationPeer : RangeBaseAutomationPeer + { + public SliderAutomationPeer(Slider owner) : base(owner) + { + } + + override protected string GetClassNameCore() + { + return "Slider"; + } + + override protected AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.Slider; + } + + } +} diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 065c4ff2e5..2ee32b0dda 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -345,10 +345,7 @@ namespace Avalonia.Controls.Primitives if (_oldSelectedItems != SelectedItems) { - RaisePropertyChanged( - SelectedItemsProperty, - new Optional(_oldSelectedItems), - new BindingValue(SelectedItems)); + RaisePropertyChanged(SelectedItemsProperty, _oldSelectedItems, SelectedItems); _oldSelectedItems = SelectedItems; } } @@ -909,10 +906,7 @@ namespace Avalonia.Controls.Primitives else if (e.PropertyName == nameof(InternalSelectionModel.WritableSelectedItems) && _oldSelectedItems != (Selection as InternalSelectionModel)?.SelectedItems) { - RaisePropertyChanged( - SelectedItemsProperty, - new Optional(_oldSelectedItems), - new BindingValue(SelectedItems)); + RaisePropertyChanged(SelectedItemsProperty, _oldSelectedItems, SelectedItems); _oldSelectedItems = SelectedItems; } else if (e.PropertyName == nameof(ISelectionModel.Source)) diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 828bf2a1fb..7de726a932 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -10,6 +10,7 @@ using Avalonia.Interactivity; using Avalonia.Layout; using Avalonia.Utilities; using Avalonia.Automation; +using Avalonia.Controls.Automation.Peers; namespace Avalonia.Controls { @@ -380,6 +381,11 @@ namespace Avalonia.Controls } } + protected override AutomationPeer OnCreateAutomationPeer() + { + return new SliderAutomationPeer(this); + } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 5ca4ef63bf..48ebd4068e 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -20,7 +20,6 @@ namespace Avalonia.Win32.Automation IRawElementProviderSimple, IRawElementProviderSimple2, IRawElementProviderFragment, - IRawElementProviderAdviseEvents, IInvokeProvider { private static Dictionary s_propertyMap = new() @@ -47,14 +46,31 @@ namespace Avalonia.Win32.Automation private static ConditionalWeakTable s_nodes = new(); private readonly int[] _runtimeId; - private int _raiseFocusChanged; - private int _raisePropertyChanged; public AutomationNode(AutomationPeer peer) { _runtimeId = new int[] { 3, GetHashCode() }; Peer = peer; s_nodes.Add(peer, this); + peer.ChildrenChanged += Peer_ChildrenChanged; + peer.PropertyChanged += Peer_PropertyChanged; + } + + private void Peer_ChildrenChanged(object? sender, EventArgs e) + { + ChildrenChanged(); + } + + private void Peer_PropertyChanged(object? sender, AutomationPropertyChangedEventArgs e) + { + if (s_propertyMap.TryGetValue(e.Property, out var id)) + { + UiaCoreProviderApi.UiaRaiseAutomationPropertyChangedEvent( + this, + (int)id, + e.OldValue as IConvertible, + e.NewValue as IConvertible); + } } public AutomationPeer Peer { get; protected set; } @@ -86,14 +102,6 @@ namespace Avalonia.Win32.Automation 0); } - public void PropertyChanged(AutomationProperty property, object? oldValue, object? newValue) - { - if (_raisePropertyChanged > 0 && s_propertyMap.TryGetValue(property, out var id)) - { - UiaCoreProviderApi.UiaRaiseAutomationPropertyChangedEvent(this, (int)id, oldValue, newValue); - } - } - [return: MarshalAs(UnmanagedType.IUnknown)] public virtual object? GetPatternProvider(int patternId) { @@ -188,32 +196,6 @@ namespace Avalonia.Win32.Automation void IRawElementProviderSimple2.ShowContextMenu() => InvokeSync(() => Peer.ShowContextMenu()); void IInvokeProvider.Invoke() => InvokeSync((AAP.IInvokeProvider x) => x.Invoke()); - void IRawElementProviderAdviseEvents.AdviseEventAdded(int eventId, int[] properties) - { - switch ((UiaEventId)eventId) - { - case UiaEventId.AutomationPropertyChanged: - ++_raisePropertyChanged; - break; - case UiaEventId.AutomationFocusChanged: - ++_raiseFocusChanged; - break; - } - } - - void IRawElementProviderAdviseEvents.AdviseEventRemoved(int eventId, int[] properties) - { - switch ((UiaEventId)eventId) - { - case UiaEventId.AutomationPropertyChanged: - --_raisePropertyChanged; - break; - case UiaEventId.AutomationFocusChanged: - --_raiseFocusChanged; - break; - } - } - protected void InvokeSync(Action action) { if (Dispatcher.UIThread.CheckAccess()) @@ -266,15 +248,6 @@ namespace Avalonia.Win32.Automation throw new NotSupportedException(); } - protected void RaiseFocusChanged(AutomationNode? focused) - { - if (_raiseFocusChanged > 0) - { - UiaCoreProviderApi.UiaRaiseAutomationEvent( - focused, - (int)UiaEventId.AutomationFocusChanged); - } - } private AutomationNode? GetRoot() { diff --git a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs index 3d8b4995ad..ff8ff69d5e 100644 --- a/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/RootAutomationNode.cs @@ -10,8 +10,11 @@ namespace Avalonia.Win32.Automation { [RequiresUnreferencedCode("Requires .NET COM interop")] internal class RootAutomationNode : AutomationNode, - IRawElementProviderFragmentRoot + IRawElementProviderFragmentRoot, + IRawElementProviderAdviseEvents { + private int _raiseFocusChanged; + public RootAutomationNode(AutomationPeer peer) : base(peer) { @@ -42,6 +45,36 @@ namespace Avalonia.Win32.Automation return GetOrCreate(focus); } + void IRawElementProviderAdviseEvents.AdviseEventAdded(int eventId, int[] properties) + { + switch ((UiaEventId)eventId) + { + case UiaEventId.AutomationFocusChanged: + ++_raiseFocusChanged; + break; + } + } + + void IRawElementProviderAdviseEvents.AdviseEventRemoved(int eventId, int[] properties) + { + switch ((UiaEventId)eventId) + { + case UiaEventId.AutomationFocusChanged: + --_raiseFocusChanged; + break; + } + } + + protected void RaiseFocusChanged(AutomationNode? focused) + { + if (_raiseFocusChanged > 0) + { + UiaCoreProviderApi.UiaRaiseAutomationEvent( + focused, + (int)UiaEventId.AutomationFocusChanged); + } + } + public void FocusChanged(object? sender, EventArgs e) { RaiseFocusChanged(GetOrCreate(Peer.GetFocus())); diff --git a/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs new file mode 100644 index 0000000000..7fa5eb83ee --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Appium/SliderTests.cs @@ -0,0 +1,35 @@ +using System; +using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Interactions; +using Xunit; + +namespace Avalonia.IntegrationTests.Appium +{ + [Collection("Default")] + public class SliderTests + { + private readonly AppiumDriver _session; + + public SliderTests(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("SliderTab"); + tab.Click(); + } + + [Fact] + public void Changes_Value_When_Clicking_Increase_Button() + { + var slider = _session.FindElementByAccessibilityId("Slider"); + + // slider.Text gets the Slider value + Assert.True(double.Parse(slider.Text) == 30); + + new Actions(_session).Click(slider).Perform(); + + Assert.Equal(50, Math.Round(double.Parse(slider.Text))); + } + } +}