committed by
GitHub
158 changed files with 6688 additions and 75 deletions
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,5 @@ |
|||||
|
<ProjectConfiguration> |
||||
|
<Settings> |
||||
|
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely> |
||||
|
</Settings> |
||||
|
</ProjectConfiguration> |
||||
@ -0,0 +1,12 @@ |
|||||
|
#import <Cocoa/Cocoa.h> |
||||
|
#include "window.h" |
||||
|
|
||||
|
NS_ASSUME_NONNULL_BEGIN |
||||
|
|
||||
|
class IAvnAutomationPeer; |
||||
|
|
||||
|
@interface AvnAccessibilityElement : NSAccessibilityElement |
||||
|
+ (AvnAccessibilityElement *) acquire:(IAvnAutomationPeer *) peer; |
||||
|
@end |
||||
|
|
||||
|
NS_ASSUME_NONNULL_END |
||||
@ -0,0 +1,496 @@ |
|||||
|
#include "common.h" |
||||
|
#include "automation.h" |
||||
|
#include "AvnString.h" |
||||
|
#include "window.h" |
||||
|
|
||||
|
@interface AvnAccessibilityElement (Events) |
||||
|
- (void) raiseChildrenChanged; |
||||
|
@end |
||||
|
|
||||
|
@interface AvnRootAccessibilityElement : AvnAccessibilityElement |
||||
|
- (AvnView *) ownerView; |
||||
|
- (AvnRootAccessibilityElement *) initWithPeer:(IAvnAutomationPeer *) peer owner:(AvnView*) owner; |
||||
|
- (void) raiseFocusChanged; |
||||
|
@end |
||||
|
|
||||
|
class AutomationNode : public ComSingleObject<IAvnAutomationNode, &IID_IAvnAutomationNode> |
||||
|
{ |
||||
|
public: |
||||
|
FORWARD_IUNKNOWN() |
||||
|
|
||||
|
AutomationNode(AvnAccessibilityElement* owner) |
||||
|
{ |
||||
|
_owner = owner; |
||||
|
} |
||||
|
|
||||
|
AvnAccessibilityElement* GetOwner() |
||||
|
{ |
||||
|
return _owner; |
||||
|
} |
||||
|
|
||||
|
virtual void Dispose() override |
||||
|
{ |
||||
|
_owner = nil; |
||||
|
} |
||||
|
|
||||
|
virtual void ChildrenChanged () override |
||||
|
{ |
||||
|
[_owner raiseChildrenChanged]; |
||||
|
} |
||||
|
|
||||
|
virtual void PropertyChanged (AvnAutomationProperty property) override |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
virtual void FocusChanged () override |
||||
|
{ |
||||
|
[(AvnRootAccessibilityElement*)_owner raiseFocusChanged]; |
||||
|
} |
||||
|
|
||||
|
private: |
||||
|
__strong AvnAccessibilityElement* _owner; |
||||
|
}; |
||||
|
|
||||
|
@implementation AvnAccessibilityElement |
||||
|
{ |
||||
|
IAvnAutomationPeer* _peer; |
||||
|
AutomationNode* _node; |
||||
|
NSMutableArray* _children; |
||||
|
} |
||||
|
|
||||
|
+ (AvnAccessibilityElement *)acquire:(IAvnAutomationPeer *)peer |
||||
|
{ |
||||
|
if (peer == nullptr) |
||||
|
return nil; |
||||
|
|
||||
|
auto instance = peer->GetNode(); |
||||
|
|
||||
|
if (instance != nullptr) |
||||
|
return dynamic_cast<AutomationNode*>(instance)->GetOwner(); |
||||
|
|
||||
|
if (peer->IsRootProvider()) |
||||
|
{ |
||||
|
auto window = peer->RootProvider_GetWindow(); |
||||
|
auto holder = dynamic_cast<INSWindowHolder*>(window); |
||||
|
auto view = holder->GetNSView(); |
||||
|
return [[AvnRootAccessibilityElement alloc] initWithPeer:peer owner:view]; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
return [[AvnAccessibilityElement alloc] initWithPeer:peer]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (AvnAccessibilityElement *)initWithPeer:(IAvnAutomationPeer *)peer |
||||
|
{ |
||||
|
self = [super init]; |
||||
|
_peer = peer; |
||||
|
_node = new AutomationNode(self); |
||||
|
_peer->SetNode(_node); |
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
- (void)dealloc |
||||
|
{ |
||||
|
if (_node) |
||||
|
delete _node; |
||||
|
_node = nullptr; |
||||
|
} |
||||
|
|
||||
|
- (NSString *)description |
||||
|
{ |
||||
|
return [NSString stringWithFormat:@"%@ '%@' (%p)", |
||||
|
GetNSStringAndRelease(_peer->GetClassName()), |
||||
|
GetNSStringAndRelease(_peer->GetName()), |
||||
|
_peer]; |
||||
|
} |
||||
|
|
||||
|
- (IAvnAutomationPeer *)peer |
||||
|
{ |
||||
|
return _peer; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)isAccessibilityElement |
||||
|
{ |
||||
|
return _peer->IsControlElement(); |
||||
|
} |
||||
|
|
||||
|
- (NSAccessibilityRole)accessibilityRole |
||||
|
{ |
||||
|
auto controlType = _peer->GetAutomationControlType(); |
||||
|
|
||||
|
switch (controlType) { |
||||
|
case AutomationButton: return NSAccessibilityButtonRole; |
||||
|
case AutomationCalendar: return NSAccessibilityGridRole; |
||||
|
case AutomationCheckBox: return NSAccessibilityCheckBoxRole; |
||||
|
case AutomationComboBox: return NSAccessibilityPopUpButtonRole; |
||||
|
case AutomationComboBoxItem: return NSAccessibilityMenuItemRole; |
||||
|
case AutomationEdit: return NSAccessibilityTextFieldRole; |
||||
|
case AutomationHyperlink: return NSAccessibilityLinkRole; |
||||
|
case AutomationImage: return NSAccessibilityImageRole; |
||||
|
case AutomationListItem: return NSAccessibilityRowRole; |
||||
|
case AutomationList: return NSAccessibilityTableRole; |
||||
|
case AutomationMenu: return NSAccessibilityMenuBarRole; |
||||
|
case AutomationMenuBar: return NSAccessibilityMenuBarRole; |
||||
|
case AutomationMenuItem: return NSAccessibilityMenuItemRole; |
||||
|
case AutomationProgressBar: return NSAccessibilityProgressIndicatorRole; |
||||
|
case AutomationRadioButton: return NSAccessibilityRadioButtonRole; |
||||
|
case AutomationScrollBar: return NSAccessibilityScrollBarRole; |
||||
|
case AutomationSlider: return NSAccessibilitySliderRole; |
||||
|
case AutomationSpinner: return NSAccessibilityIncrementorRole; |
||||
|
case AutomationStatusBar: return NSAccessibilityTableRole; |
||||
|
case AutomationTab: return NSAccessibilityTabGroupRole; |
||||
|
case AutomationTabItem: return NSAccessibilityRadioButtonRole; |
||||
|
case AutomationText: return NSAccessibilityStaticTextRole; |
||||
|
case AutomationToolBar: return NSAccessibilityToolbarRole; |
||||
|
case AutomationToolTip: return NSAccessibilityPopoverRole; |
||||
|
case AutomationTree: return NSAccessibilityOutlineRole; |
||||
|
case AutomationTreeItem: return NSAccessibilityCellRole; |
||||
|
case AutomationCustom: return NSAccessibilityUnknownRole; |
||||
|
case AutomationGroup: return NSAccessibilityGroupRole; |
||||
|
case AutomationThumb: return NSAccessibilityHandleRole; |
||||
|
case AutomationDataGrid: return NSAccessibilityGridRole; |
||||
|
case AutomationDataItem: return NSAccessibilityCellRole; |
||||
|
case AutomationDocument: return NSAccessibilityStaticTextRole; |
||||
|
case AutomationSplitButton: return NSAccessibilityPopUpButtonRole; |
||||
|
case AutomationWindow: return NSAccessibilityWindowRole; |
||||
|
case AutomationPane: return NSAccessibilityGroupRole; |
||||
|
case AutomationHeader: return NSAccessibilityGroupRole; |
||||
|
case AutomationHeaderItem: return NSAccessibilityButtonRole; |
||||
|
case AutomationTable: return NSAccessibilityTableRole; |
||||
|
case AutomationTitleBar: return NSAccessibilityGroupRole; |
||||
|
// Treat unknown roles as generic group container items. Returning |
||||
|
// NSAccessibilityUnknownRole is also possible but makes the screen |
||||
|
// reader focus on the item instead of passing focus to child items. |
||||
|
default: return NSAccessibilityGroupRole; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
- (NSString *)accessibilityIdentifier |
||||
|
{ |
||||
|
return GetNSStringAndRelease(_peer->GetAutomationId()); |
||||
|
} |
||||
|
|
||||
|
- (NSString *)accessibilityTitle |
||||
|
{ |
||||
|
// StaticText exposes its text via the value property. |
||||
|
if (_peer->GetAutomationControlType() != AutomationText) |
||||
|
{ |
||||
|
return GetNSStringAndRelease(_peer->GetName()); |
||||
|
} |
||||
|
|
||||
|
return [super accessibilityTitle]; |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityValue |
||||
|
{ |
||||
|
if (_peer->IsRangeValueProvider()) |
||||
|
{ |
||||
|
return [NSNumber numberWithDouble:_peer->RangeValueProvider_GetValue()]; |
||||
|
} |
||||
|
else if (_peer->IsToggleProvider()) |
||||
|
{ |
||||
|
switch (_peer->ToggleProvider_GetToggleState()) { |
||||
|
case 0: return [NSNumber numberWithBool:NO]; |
||||
|
case 1: return [NSNumber numberWithBool:YES]; |
||||
|
default: return [NSNumber numberWithInt:2]; |
||||
|
} |
||||
|
} |
||||
|
else if (_peer->IsValueProvider()) |
||||
|
{ |
||||
|
return GetNSStringAndRelease(_peer->ValueProvider_GetValue()); |
||||
|
} |
||||
|
else if (_peer->GetAutomationControlType() == AutomationText) |
||||
|
{ |
||||
|
return GetNSStringAndRelease(_peer->GetName()); |
||||
|
} |
||||
|
|
||||
|
return [super accessibilityValue]; |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityMinValue |
||||
|
{ |
||||
|
if (_peer->IsRangeValueProvider()) |
||||
|
{ |
||||
|
return [NSNumber numberWithDouble:_peer->RangeValueProvider_GetMinimum()]; |
||||
|
} |
||||
|
|
||||
|
return [super accessibilityMinValue]; |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityMaxValue |
||||
|
{ |
||||
|
if (_peer->IsRangeValueProvider()) |
||||
|
{ |
||||
|
return [NSNumber numberWithDouble:_peer->RangeValueProvider_GetMaximum()]; |
||||
|
} |
||||
|
|
||||
|
return [super accessibilityMaxValue]; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)isAccessibilityEnabled |
||||
|
{ |
||||
|
return _peer->IsEnabled(); |
||||
|
} |
||||
|
|
||||
|
- (BOOL)isAccessibilityFocused |
||||
|
{ |
||||
|
return _peer->HasKeyboardFocus(); |
||||
|
} |
||||
|
|
||||
|
- (NSArray *)accessibilityChildren |
||||
|
{ |
||||
|
if (_children == nullptr && _peer != nullptr) |
||||
|
[self recalculateChildren]; |
||||
|
return _children; |
||||
|
} |
||||
|
|
||||
|
- (NSRect)accessibilityFrame |
||||
|
{ |
||||
|
id topLevel = [self accessibilityTopLevelUIElement]; |
||||
|
auto result = NSZeroRect; |
||||
|
|
||||
|
if ([topLevel isKindOfClass:[AvnRootAccessibilityElement class]]) |
||||
|
{ |
||||
|
auto root = (AvnRootAccessibilityElement*)topLevel; |
||||
|
auto view = [root ownerView]; |
||||
|
|
||||
|
if (view) |
||||
|
{ |
||||
|
auto window = [view window]; |
||||
|
auto bounds = ToNSRect(_peer->GetBoundingRectangle()); |
||||
|
auto windowBounds = [view convertRect:bounds toView:nil]; |
||||
|
auto screenBounds = [window convertRectToScreen:windowBounds]; |
||||
|
result = screenBounds; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityParent |
||||
|
{ |
||||
|
auto parentPeer = _peer->GetParent(); |
||||
|
return parentPeer ? [AvnAccessibilityElement acquire:parentPeer] : [NSApplication sharedApplication]; |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityTopLevelUIElement |
||||
|
{ |
||||
|
auto rootPeer = _peer->GetRootPeer(); |
||||
|
return [AvnAccessibilityElement acquire:rootPeer]; |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityWindow |
||||
|
{ |
||||
|
id topLevel = [self accessibilityTopLevelUIElement]; |
||||
|
return [topLevel isKindOfClass:[NSWindow class]] ? topLevel : nil; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)isAccessibilityExpanded |
||||
|
{ |
||||
|
if (!_peer->IsExpandCollapseProvider()) |
||||
|
return NO; |
||||
|
return _peer->ExpandCollapseProvider_GetIsExpanded(); |
||||
|
} |
||||
|
|
||||
|
- (void)setAccessibilityExpanded:(BOOL)accessibilityExpanded |
||||
|
{ |
||||
|
if (!_peer->IsExpandCollapseProvider()) |
||||
|
return; |
||||
|
if (accessibilityExpanded) |
||||
|
_peer->ExpandCollapseProvider_Expand(); |
||||
|
else |
||||
|
_peer->ExpandCollapseProvider_Collapse(); |
||||
|
} |
||||
|
|
||||
|
- (BOOL)accessibilityPerformPress |
||||
|
{ |
||||
|
if (_peer->IsInvokeProvider()) |
||||
|
{ |
||||
|
_peer->InvokeProvider_Invoke(); |
||||
|
} |
||||
|
else if (_peer->IsExpandCollapseProvider()) |
||||
|
{ |
||||
|
_peer->ExpandCollapseProvider_Expand(); |
||||
|
} |
||||
|
else if (_peer->IsToggleProvider()) |
||||
|
{ |
||||
|
_peer->ToggleProvider_Toggle(); |
||||
|
} |
||||
|
return YES; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)accessibilityPerformIncrement |
||||
|
{ |
||||
|
if (!_peer->IsRangeValueProvider()) |
||||
|
return NO; |
||||
|
auto value = _peer->RangeValueProvider_GetValue(); |
||||
|
value += _peer->RangeValueProvider_GetSmallChange(); |
||||
|
_peer->RangeValueProvider_SetValue(value); |
||||
|
return YES; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)accessibilityPerformDecrement |
||||
|
{ |
||||
|
if (!_peer->IsRangeValueProvider()) |
||||
|
return NO; |
||||
|
auto value = _peer->RangeValueProvider_GetValue(); |
||||
|
value -= _peer->RangeValueProvider_GetSmallChange(); |
||||
|
_peer->RangeValueProvider_SetValue(value); |
||||
|
return YES; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)accessibilityPerformShowMenu |
||||
|
{ |
||||
|
if (!_peer->IsExpandCollapseProvider()) |
||||
|
return NO; |
||||
|
_peer->ExpandCollapseProvider_Expand(); |
||||
|
return YES; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)isAccessibilitySelected |
||||
|
{ |
||||
|
if (_peer->IsSelectionItemProvider()) |
||||
|
return _peer->SelectionItemProvider_IsSelected(); |
||||
|
return NO; |
||||
|
} |
||||
|
|
||||
|
- (BOOL)isAccessibilitySelectorAllowed:(SEL)selector |
||||
|
{ |
||||
|
if (selector == @selector(accessibilityPerformShowMenu)) |
||||
|
{ |
||||
|
return _peer->IsExpandCollapseProvider() && _peer->ExpandCollapseProvider_GetShowsMenu(); |
||||
|
} |
||||
|
else if (selector == @selector(isAccessibilityExpanded)) |
||||
|
{ |
||||
|
return _peer->IsExpandCollapseProvider(); |
||||
|
} |
||||
|
else if (selector == @selector(accessibilityPerformPress)) |
||||
|
{ |
||||
|
return _peer->IsInvokeProvider() || _peer->IsExpandCollapseProvider() || _peer->IsToggleProvider(); |
||||
|
} |
||||
|
else if (selector == @selector(accessibilityPerformIncrement) || |
||||
|
selector == @selector(accessibilityPerformDecrement) || |
||||
|
selector == @selector(accessibilityMinValue) || |
||||
|
selector == @selector(accessibilityMaxValue)) |
||||
|
{ |
||||
|
return _peer->IsRangeValueProvider(); |
||||
|
} |
||||
|
|
||||
|
return [super isAccessibilitySelectorAllowed:selector]; |
||||
|
} |
||||
|
|
||||
|
- (void)raiseChildrenChanged |
||||
|
{ |
||||
|
auto changed = _children ? [NSMutableSet setWithArray:_children] : [NSMutableSet set]; |
||||
|
|
||||
|
[self recalculateChildren]; |
||||
|
|
||||
|
if (_children) |
||||
|
[changed addObjectsFromArray:_children]; |
||||
|
|
||||
|
NSAccessibilityPostNotificationWithUserInfo( |
||||
|
self, |
||||
|
NSAccessibilityLayoutChangedNotification, |
||||
|
@{ NSAccessibilityUIElementsKey: [changed allObjects]}); |
||||
|
} |
||||
|
|
||||
|
- (void)raisePropertyChanged |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
- (void)setAccessibilityFocused:(BOOL)accessibilityFocused |
||||
|
{ |
||||
|
if (accessibilityFocused) |
||||
|
_peer->SetFocus(); |
||||
|
} |
||||
|
|
||||
|
- (void)recalculateChildren |
||||
|
{ |
||||
|
auto childPeers = _peer->GetChildren(); |
||||
|
auto childCount = childPeers != nullptr ? childPeers->GetCount() : 0; |
||||
|
|
||||
|
if (childCount > 0) |
||||
|
{ |
||||
|
_children = [[NSMutableArray alloc] initWithCapacity:childCount]; |
||||
|
|
||||
|
for (int i = 0; i < childCount; ++i) |
||||
|
{ |
||||
|
IAvnAutomationPeer* child; |
||||
|
|
||||
|
if (childPeers->Get(i, &child) == S_OK) |
||||
|
{ |
||||
|
auto element = [AvnAccessibilityElement acquire:child]; |
||||
|
[_children addObject:element]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_children = nil; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@end |
||||
|
|
||||
|
@implementation AvnRootAccessibilityElement |
||||
|
{ |
||||
|
AvnView* _owner; |
||||
|
} |
||||
|
|
||||
|
- (AvnRootAccessibilityElement *)initWithPeer:(IAvnAutomationPeer *)peer owner:(AvnView *)owner |
||||
|
{ |
||||
|
self = [super initWithPeer:peer]; |
||||
|
_owner = owner; |
||||
|
|
||||
|
// Seems we need to raise a focus changed notification here if we have focus |
||||
|
auto focusedPeer = [self peer]->RootProvider_GetFocus(); |
||||
|
id focused = [AvnAccessibilityElement acquire:focusedPeer]; |
||||
|
|
||||
|
if (focused) |
||||
|
NSAccessibilityPostNotification(focused, NSAccessibilityFocusedUIElementChangedNotification); |
||||
|
|
||||
|
return self; |
||||
|
} |
||||
|
|
||||
|
- (AvnView *)ownerView |
||||
|
{ |
||||
|
return _owner; |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityFocusedUIElement |
||||
|
{ |
||||
|
auto focusedPeer = [self peer]->RootProvider_GetFocus(); |
||||
|
return [AvnAccessibilityElement acquire:focusedPeer]; |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityHitTest:(NSPoint)point |
||||
|
{ |
||||
|
auto clientPoint = [[_owner window] convertPointFromScreen:point]; |
||||
|
auto localPoint = [_owner translateLocalPoint:ToAvnPoint(clientPoint)]; |
||||
|
auto hit = [self peer]->RootProvider_GetPeerFromPoint(localPoint); |
||||
|
return [AvnAccessibilityElement acquire:hit]; |
||||
|
} |
||||
|
|
||||
|
- (id)accessibilityParent |
||||
|
{ |
||||
|
return _owner; |
||||
|
} |
||||
|
|
||||
|
- (void)raiseFocusChanged |
||||
|
{ |
||||
|
id focused = [self accessibilityFocusedUIElement]; |
||||
|
NSAccessibilityPostNotification(focused, NSAccessibilityFocusedUIElementChangedNotification); |
||||
|
} |
||||
|
|
||||
|
// Although this method is marked as deprecated we get runtime warnings if we don't handle it. |
||||
|
#pragma clang diagnostic push |
||||
|
#pragma clang diagnostic ignored "-Wdeprecated-implementations" |
||||
|
- (void)accessibilityPerformAction:(NSAccessibilityActionName)action |
||||
|
{ |
||||
|
[_owner accessibilityPerformAction:action]; |
||||
|
} |
||||
|
#pragma clang diagnostic pop |
||||
|
|
||||
|
@end |
||||
@ -0,0 +1,7 @@ |
|||||
|
<Application xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:Class="IntegrationTestApp.App"> |
||||
|
<Application.Styles> |
||||
|
<FluentTheme Mode="Light"/> |
||||
|
</Application.Styles> |
||||
|
</Application> |
||||
@ -0,0 +1,24 @@ |
|||||
|
using Avalonia; |
||||
|
using Avalonia.Controls.ApplicationLifetimes; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace IntegrationTestApp |
||||
|
{ |
||||
|
public class App : Application |
||||
|
{ |
||||
|
public override void Initialize() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
|
||||
|
public override void OnFrameworkInitializationCompleted() |
||||
|
{ |
||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) |
||||
|
{ |
||||
|
desktop.MainWindow = new MainWindow(); |
||||
|
} |
||||
|
|
||||
|
base.OnFrameworkInitializationCompleted(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<OutputType>WinExe</OutputType> |
||||
|
<TargetFramework>net6.0</TargetFramework> |
||||
|
<Nullable>enable</Nullable> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<CFBundleName>IntegrationTestApp</CFBundleName> |
||||
|
<CFBundleIdentifier>net.avaloniaui.avalonia.integrationtestapp</CFBundleIdentifier> |
||||
|
<NSHighResolutionCapable>true</NSHighResolutionCapable> |
||||
|
<CFBundleShortVersionString>1.0.0</CFBundleShortVersionString> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Dotnet.Bundle" Version="0.9.13" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<Import Project="..\..\build\BuildTargets.targets" /> |
||||
|
<Import Project="..\..\build\SampleApp.props" /> |
||||
|
<Import Project="..\..\build\ReferenceCoreLibraries.props" /> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,95 @@ |
|||||
|
<Window xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" |
||||
|
x:Class="IntegrationTestApp.MainWindow" |
||||
|
Title="IntegrationTestApp"> |
||||
|
<NativeMenu.Menu> |
||||
|
<NativeMenu> |
||||
|
<NativeMenuItem Header="File"> |
||||
|
<NativeMenu> |
||||
|
<NativeMenuItem Header="Open..."/> |
||||
|
</NativeMenu> |
||||
|
</NativeMenuItem> |
||||
|
<NativeMenuItem Header="View"> |
||||
|
<NativeMenu/> |
||||
|
</NativeMenuItem> |
||||
|
</NativeMenu> |
||||
|
</NativeMenu.Menu> |
||||
|
<DockPanel> |
||||
|
<NativeMenuBar DockPanel.Dock="Top"/> |
||||
|
|
||||
|
<TabControl TabStripPlacement="Left" Name="MainTabs"> |
||||
|
<TabItem Header="Automation"> |
||||
|
<StackPanel> |
||||
|
<TextBlock Name="TextBlockWithName">TextBlockWithName</TextBlock> |
||||
|
<TextBlock Name="NotTheAutomationId" AutomationProperties.AutomationId="TextBlockWithNameAndAutomationId"> |
||||
|
TextBlockWithNameAndAutomationId |
||||
|
</TextBlock> |
||||
|
<TextBlock Name="TextBlockAsLabel">Label for TextBox</TextBlock> |
||||
|
<TextBox Name="LabeledByTextBox" AutomationProperties.LabeledBy="{Binding #TextBlockAsLabel}"> |
||||
|
Foo |
||||
|
</TextBox> |
||||
|
</StackPanel> |
||||
|
</TabItem> |
||||
|
|
||||
|
<TabItem Header="Button"> |
||||
|
<StackPanel> |
||||
|
<Button Name="DisabledButton" IsEnabled="False"> |
||||
|
Disabled Button |
||||
|
</Button> |
||||
|
<Button Name="BasicButton"> |
||||
|
Basic Button |
||||
|
</Button> |
||||
|
<Button Name="ButtonWithTextBlock"> |
||||
|
<TextBlock>Button with TextBlock</TextBlock> |
||||
|
</Button> |
||||
|
<Button Name="ButtonWithAcceleratorKey" HotKey="Ctrl+B">Button with Accelerator Key</Button> |
||||
|
</StackPanel> |
||||
|
</TabItem> |
||||
|
|
||||
|
<TabItem Header="CheckBox"> |
||||
|
<StackPanel> |
||||
|
<CheckBox Name="UncheckedCheckBox">Unchecked</CheckBox> |
||||
|
<CheckBox Name="CheckedCheckBox" IsChecked="True">Checked</CheckBox> |
||||
|
<CheckBox Name="ThreeStateCheckBox" IsThreeState="True" IsChecked="{x:Null}">ThreeState</CheckBox> |
||||
|
</StackPanel> |
||||
|
</TabItem> |
||||
|
|
||||
|
<TabItem Header="ComboBox"> |
||||
|
<StackPanel> |
||||
|
<ComboBox Name="BasicComboBox"> |
||||
|
<ComboBoxItem>Item 0</ComboBoxItem> |
||||
|
<ComboBoxItem>Item 1</ComboBoxItem> |
||||
|
</ComboBox> |
||||
|
<Button Name="ComboBoxSelectionClear">Clear Selection</Button> |
||||
|
<Button Name="ComboBoxSelectFirst">Select First</Button> |
||||
|
</StackPanel> |
||||
|
</TabItem> |
||||
|
|
||||
|
<TabItem Header="ListBox"> |
||||
|
<DockPanel> |
||||
|
<StackPanel DockPanel.Dock="Bottom"> |
||||
|
<Button Name="ListBoxSelectionClear">Clear Selection</Button> |
||||
|
</StackPanel> |
||||
|
<ListBox Name="BasicListBox" Items="{Binding ListBoxItems}" SelectionMode="Multiple"/> |
||||
|
</DockPanel> |
||||
|
</TabItem> |
||||
|
|
||||
|
<TabItem Header="Menu"> |
||||
|
<DockPanel> |
||||
|
<Menu DockPanel.Dock="Top"> |
||||
|
<MenuItem Name="RootMenuItem" Header="_Root"> |
||||
|
<MenuItem Name="Child1MenuItem" Header="_Child 1" InputGesture="Ctrl+O" Click="MenuClicked"/> |
||||
|
<MenuItem Name="Child2MenuItem" Header="_Child 1"> |
||||
|
<MenuItem Name="GrandchildMenuItem" Header="_Grandchild" Click="MenuClicked"/> |
||||
|
</MenuItem> |
||||
|
</MenuItem> |
||||
|
</Menu> |
||||
|
<TextBlock Name="ClickedMenuItem">None</TextBlock> |
||||
|
</DockPanel> |
||||
|
</TabItem> |
||||
|
</TabControl> |
||||
|
</DockPanel> |
||||
|
</Window> |
||||
@ -0,0 +1,67 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Interactivity; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
|
||||
|
namespace IntegrationTestApp |
||||
|
{ |
||||
|
public class MainWindow : Window |
||||
|
{ |
||||
|
public MainWindow() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
InitializeViewMenu(); |
||||
|
this.AttachDevTools(); |
||||
|
AddHandler(Button.ClickEvent, OnButtonClick); |
||||
|
ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList(); |
||||
|
DataContext = this; |
||||
|
} |
||||
|
|
||||
|
public List<string> ListBoxItems { get; } |
||||
|
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
AvaloniaXamlLoader.Load(this); |
||||
|
} |
||||
|
|
||||
|
private void InitializeViewMenu() |
||||
|
{ |
||||
|
var mainTabs = this.FindControl<TabControl>("MainTabs"); |
||||
|
var viewMenu = (NativeMenuItem)NativeMenu.GetMenu(this).Items[1]; |
||||
|
|
||||
|
foreach (TabItem tabItem in mainTabs.Items) |
||||
|
{ |
||||
|
var menuItem = new NativeMenuItem |
||||
|
{ |
||||
|
Header = (string)tabItem.Header!, |
||||
|
IsChecked = tabItem.IsSelected, |
||||
|
ToggleType = NativeMenuItemToggleType.Radio, |
||||
|
}; |
||||
|
|
||||
|
menuItem.Click += (s, e) => tabItem.IsSelected = true; |
||||
|
viewMenu.Menu.Items.Add(menuItem); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void MenuClicked(object? sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
var clickedMenuItemTextBlock = this.FindControl<TextBlock>("ClickedMenuItem"); |
||||
|
clickedMenuItemTextBlock.Text = ((MenuItem)sender!).Header.ToString(); |
||||
|
} |
||||
|
|
||||
|
private void OnButtonClick(object? sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
var source = e.Source as Button; |
||||
|
|
||||
|
if (source?.Name == "ComboBoxSelectionClear") |
||||
|
this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = -1; |
||||
|
if (source?.Name == "ComboBoxSelectFirst") |
||||
|
this.FindControl<ComboBox>("BasicComboBox").SelectedIndex = 0; |
||||
|
if (source?.Name == "ListBoxSelectionClear") |
||||
|
this.FindControl<ListBox>("BasicListBox").SelectedIndex = -1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System; |
||||
|
using Avalonia; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.ApplicationLifetimes; |
||||
|
|
||||
|
namespace IntegrationTestApp |
||||
|
{ |
||||
|
class Program |
||||
|
{ |
||||
|
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
|
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
|
// yet and stuff might break.
|
||||
|
public static void Main(string[] args) => BuildAvaloniaApp() |
||||
|
.StartWithClassicDesktopLifetime(args); |
||||
|
|
||||
|
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
|
public static AppBuilder BuildAvaloniaApp() |
||||
|
=> AppBuilder.Configure<App>() |
||||
|
.UsePlatformDetect() |
||||
|
.LogToTrace(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,5 @@ |
|||||
|
#!/usr/bin/env bash |
||||
|
|
||||
|
cd $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) |
||||
|
dotnet restore -r osx-arm64 |
||||
|
dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-arm64 -p:_AvaloniaUseExternalMSBuild=false |
||||
@ -0,0 +1,11 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
|
||||
|
<!-- |
||||
|
To use the Avalonia CI feed to get unstable packages, move this file to the root of your solution. |
||||
|
--> |
||||
|
|
||||
|
<configuration> |
||||
|
<packageSources> |
||||
|
<add key="AvaloniaCI" value="https://www.myget.org/F/avalonia-ci/api/v2" /> |
||||
|
</packageSources> |
||||
|
</configuration> |
||||
@ -0,0 +1,28 @@ |
|||||
|
using Avalonia.Automation.Peers; |
||||
|
|
||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains values used as automation property identifiers by UI Automation providers.
|
||||
|
/// </summary>
|
||||
|
public static class AutomationElementIdentifiers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Identifies the bounding rectangle automation property. The bounding rectangle property
|
||||
|
/// value is returned by the <see cref="AutomationPeer.GetBoundingRectangle"/> method.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty BoundingRectangleProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies the class name automation property. The class name property value is returned
|
||||
|
/// by the <see cref="AutomationPeer.GetClassName"/> method.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty ClassNameProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies the name automation property. The class name property value is returned
|
||||
|
/// by the <see cref="AutomationPeer.GetName"/> method.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty NameProperty { get; } = new AutomationProperty(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Describes the notification characteristics of a particular live region
|
||||
|
/// </summary>
|
||||
|
public enum AutomationLiveSetting |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The element does not send notifications if the content of the live region has changed.
|
||||
|
/// </summary>
|
||||
|
Off = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The element sends non-interruptive notifications if the content of the live region has
|
||||
|
/// changed. With this setting, UI Automation clients and assistive technologies are expected
|
||||
|
/// to not interrupt the user to inform of changes to the live region.
|
||||
|
/// </summary>
|
||||
|
Polite = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The element sends interruptive notifications if the content of the live region has changed.
|
||||
|
/// With this setting, UI Automation clients and assistive technologies are expected to interrupt
|
||||
|
/// the user to inform of changes to the live region.
|
||||
|
/// </summary>
|
||||
|
Assertive = 2, |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,630 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Automation.Peers; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Declares how a control should included in different views of the automation tree.
|
||||
|
/// </summary>
|
||||
|
public enum AccessibilityView |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The control is included in the Raw view of the automation tree.
|
||||
|
/// </summary>
|
||||
|
Raw, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The control is included in the Control view of the automation tree.
|
||||
|
/// </summary>
|
||||
|
Control, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The control is included in the Content view of the automation tree.
|
||||
|
/// </summary>
|
||||
|
Content, |
||||
|
} |
||||
|
|
||||
|
public static class AutomationProperties |
||||
|
{ |
||||
|
internal const int AutomationPositionInSetDefault = -1; |
||||
|
internal const int AutomationSizeOfSetDefault = -1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.AcceleratorKey attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<string> AcceleratorKeyProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, string>( |
||||
|
"AcceleratorKey", |
||||
|
typeof(AutomationProperties)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.AccessibilityView attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<AccessibilityView> AccessibilityViewProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, AccessibilityView>( |
||||
|
"AccessibilityView", |
||||
|
typeof(AutomationProperties), |
||||
|
defaultValue: AccessibilityView.Content); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.AccessKey attached property
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<string> AccessKeyProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, string>( |
||||
|
"AccessKey", |
||||
|
typeof(AutomationProperties)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.AutomationId attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<string> AutomationIdProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, string>( |
||||
|
"AutomationId", |
||||
|
typeof(AutomationProperties)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.ControlTypeOverride attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<AutomationControlType?> ControlTypeOverrideProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, AutomationControlType?>( |
||||
|
"ControlTypeOverride", |
||||
|
typeof(AutomationProperties)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.HelpText attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<string> HelpTextProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, string>( |
||||
|
"HelpText", |
||||
|
typeof(AutomationProperties)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.IsColumnHeader attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<bool> IsColumnHeaderProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, bool>( |
||||
|
"IsColumnHeader", |
||||
|
typeof(AutomationProperties), |
||||
|
false); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.IsRequiredForForm attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<bool> IsRequiredForFormProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, bool>( |
||||
|
"IsRequiredForForm", |
||||
|
typeof(AutomationProperties), |
||||
|
false); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.IsRowHeader attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<bool> IsRowHeaderProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, bool>( |
||||
|
"IsRowHeader", |
||||
|
typeof(AutomationProperties), |
||||
|
false); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.IsOffscreenBehavior attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<IsOffscreenBehavior> IsOffscreenBehaviorProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, IsOffscreenBehavior>( |
||||
|
"IsOffscreenBehavior", |
||||
|
typeof(AutomationProperties), |
||||
|
IsOffscreenBehavior.Default); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.ItemStatus attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<string> ItemStatusProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, string>( |
||||
|
"ItemStatus", |
||||
|
typeof(AutomationProperties)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.ItemType attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<string> ItemTypeProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, string>( |
||||
|
"ItemType", |
||||
|
typeof(AutomationProperties)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.LabeledBy attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<IControl> LabeledByProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, IControl>( |
||||
|
"LabeledBy", |
||||
|
typeof(AutomationProperties)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.LiveSetting attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<AutomationLiveSetting> LiveSettingProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, AutomationLiveSetting>( |
||||
|
"LiveSetting", |
||||
|
typeof(AutomationProperties), |
||||
|
AutomationLiveSetting.Off); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.Name attached attached property.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<string> NameProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, string>( |
||||
|
"Name", |
||||
|
typeof(AutomationProperties)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.PositionInSet attached property.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The PositionInSet property describes the ordinal location of the element within a set
|
||||
|
/// of elements which are considered to be siblings. PositionInSet works in coordination
|
||||
|
/// with the SizeOfSet property to describe the ordinal location in the set.
|
||||
|
/// </remarks>
|
||||
|
public static readonly AttachedProperty<int> PositionInSetProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, int>( |
||||
|
"PositionInSet", |
||||
|
typeof(AutomationProperties), |
||||
|
AutomationPositionInSetDefault); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the AutomationProperties.SizeOfSet attached property.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// The SizeOfSet property describes the count of automation elements in a group or set
|
||||
|
/// that are considered to be siblings. SizeOfSet works in coordination with the PositionInSet
|
||||
|
/// property to describe the count of items in the set.
|
||||
|
/// </remarks>
|
||||
|
public static readonly AttachedProperty<int> SizeOfSetProperty = |
||||
|
AvaloniaProperty.RegisterAttached<StyledElement, int>( |
||||
|
"SizeOfSet", |
||||
|
typeof(AutomationProperties), |
||||
|
AutomationSizeOfSetDefault); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting AcceleratorKey property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetAcceleratorKey(StyledElement element, string value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(AcceleratorKeyProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading AcceleratorKey property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static string GetAcceleratorKey(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((string)element.GetValue(AcceleratorKeyProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting AccessibilityView property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetAccessibilityView(StyledElement element, AccessibilityView value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(AccessibilityViewProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading AccessibilityView property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static AccessibilityView GetAccessibilityView(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return element.GetValue(AccessibilityViewProperty); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting AccessKey property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetAccessKey(StyledElement element, string value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(AccessKeyProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading AccessKey property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static string GetAccessKey(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((string)element.GetValue(AccessKeyProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting AutomationId property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetAutomationId(StyledElement element, string value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(AutomationIdProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading AutomationId property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static string GetAutomationId(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return element.GetValue(AutomationIdProperty); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting ControlTypeOverride property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetControlTypeOverride(StyledElement element, AutomationControlType? value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(ControlTypeOverrideProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading ControlTypeOverride property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static AutomationControlType? GetControlTypeOverride(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return element.GetValue(ControlTypeOverrideProperty); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting HelpText property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetHelpText(StyledElement element, string value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(HelpTextProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading HelpText property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static string GetHelpText(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((string)element.GetValue(HelpTextProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting IsColumnHeader property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetIsColumnHeader(StyledElement element, bool value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(IsColumnHeaderProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading IsColumnHeader property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static bool GetIsColumnHeader(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((bool)element.GetValue(IsColumnHeaderProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting IsRequiredForForm property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetIsRequiredForForm(StyledElement element, bool value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(IsRequiredForFormProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading IsRequiredForForm property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static bool GetIsRequiredForForm(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((bool)element.GetValue(IsRequiredForFormProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading IsRowHeader property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static bool GetIsRowHeader(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((bool)element.GetValue(IsRowHeaderProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting IsRowHeader property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetIsRowHeader(StyledElement element, bool value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(IsRowHeaderProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting IsOffscreenBehavior property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetIsOffscreenBehavior(StyledElement element, IsOffscreenBehavior value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(IsOffscreenBehaviorProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading IsOffscreenBehavior property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static IsOffscreenBehavior GetIsOffscreenBehavior(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((IsOffscreenBehavior)element.GetValue(IsOffscreenBehaviorProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting ItemStatus property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetItemStatus(StyledElement element, string value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(ItemStatusProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading ItemStatus property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static string GetItemStatus(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((string)element.GetValue(ItemStatusProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting ItemType property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetItemType(StyledElement element, string value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(ItemTypeProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading ItemType property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static string GetItemType(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((string)element.GetValue(ItemTypeProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting LabeledBy property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetLabeledBy(StyledElement element, IControl value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(LabeledByProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading LabeledBy property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static IControl GetLabeledBy(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return element.GetValue(LabeledByProperty); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting LiveSetting property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetLiveSetting(StyledElement element, AutomationLiveSetting value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(LiveSettingProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading LiveSetting property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static AutomationLiveSetting GetLiveSetting(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((AutomationLiveSetting)element.GetValue(LiveSettingProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting Name property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetName(StyledElement element, string value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(NameProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading Name property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static string GetName(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((string)element.GetValue(NameProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting PositionInSet property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetPositionInSet(StyledElement element, int value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(PositionInSetProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading PositionInSet property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static int GetPositionInSet(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((int)element.GetValue(PositionInSetProperty)); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for setting SizeOfSet property on a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static void SetSizeOfSet(StyledElement element, int value) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
element.SetValue(SizeOfSetProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Helper for reading SizeOfSet property from a StyledElement.
|
||||
|
/// </summary>
|
||||
|
public static int GetSizeOfSet(StyledElement element) |
||||
|
{ |
||||
|
if (element == null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(element)); |
||||
|
} |
||||
|
|
||||
|
return ((int)element.GetValue(SizeOfSetProperty)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Identifies a property of <see cref="AutomationElementIdentifiers"/> or of a specific
|
||||
|
/// control pattern.
|
||||
|
/// </summary>
|
||||
|
public sealed class AutomationProperty |
||||
|
{ |
||||
|
internal AutomationProperty() { } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
public class AutomationPropertyChangedEventArgs : EventArgs |
||||
|
{ |
||||
|
public AutomationPropertyChangedEventArgs( |
||||
|
AutomationProperty property, |
||||
|
object? oldValue, |
||||
|
object? newValue) |
||||
|
{ |
||||
|
Property = property; |
||||
|
OldValue = oldValue; |
||||
|
NewValue = newValue; |
||||
|
} |
||||
|
|
||||
|
public AutomationProperty Property { get; } |
||||
|
public object? OldValue { get; } |
||||
|
public object? NewValue { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
public class ElementNotEnabledException : Exception |
||||
|
{ |
||||
|
public ElementNotEnabledException() : base("Element not enabled.") { } |
||||
|
public ElementNotEnabledException(string message) : base(message) { } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using Avalonia.Automation.Provider; |
||||
|
|
||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains values used as identifiers by <see cref="IExpandCollapseProvider"/>.
|
||||
|
/// </summary>
|
||||
|
public static class ExpandCollapsePatternIdentifiers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IExpandCollapseProvider.ExpandCollapseState"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty ExpandCollapseStateProperty { get; } = new AutomationProperty(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains values that specify the <see cref="ExpandCollapseState"/> of a UI Automation element.
|
||||
|
/// </summary>
|
||||
|
public enum ExpandCollapseState |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// No child nodes, controls, or content of the UI Automation element are displayed.
|
||||
|
/// </summary>
|
||||
|
Collapsed, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// All child nodes, controls or content of the UI Automation element are displayed.
|
||||
|
/// </summary>
|
||||
|
Expanded, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The UI Automation element has no child nodes, controls, or content to display.
|
||||
|
/// </summary>
|
||||
|
LeafNode, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Some, but not all, child nodes, controls, or content of the UI Automation element are
|
||||
|
/// displayed.
|
||||
|
/// </summary>
|
||||
|
PartiallyExpanded |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// This enum offers different ways of evaluating the IsOffscreen AutomationProperty
|
||||
|
/// </summary>
|
||||
|
public enum IsOffscreenBehavior |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The AutomationProperty IsOffscreen is calculated based on IsVisible.
|
||||
|
/// </summary>
|
||||
|
Default, |
||||
|
/// <summary>
|
||||
|
/// The AutomationProperty IsOffscreen is false.
|
||||
|
/// </summary>
|
||||
|
Onscreen, |
||||
|
/// <summary>
|
||||
|
/// The AutomationProperty IsOffscreen if true.
|
||||
|
/// </summary>
|
||||
|
Offscreen, |
||||
|
/// <summary>
|
||||
|
/// The AutomationProperty IsOffscreen is calculated based on clip regions.
|
||||
|
/// </summary>
|
||||
|
FromClip, |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,263 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public enum AutomationControlType |
||||
|
{ |
||||
|
None, |
||||
|
Button, |
||||
|
Calendar, |
||||
|
CheckBox, |
||||
|
ComboBox, |
||||
|
ComboBoxItem, |
||||
|
Edit, |
||||
|
Hyperlink, |
||||
|
Image, |
||||
|
ListItem, |
||||
|
List, |
||||
|
Menu, |
||||
|
MenuBar, |
||||
|
MenuItem, |
||||
|
ProgressBar, |
||||
|
RadioButton, |
||||
|
ScrollBar, |
||||
|
Slider, |
||||
|
Spinner, |
||||
|
StatusBar, |
||||
|
Tab, |
||||
|
TabItem, |
||||
|
Text, |
||||
|
ToolBar, |
||||
|
ToolTip, |
||||
|
Tree, |
||||
|
TreeItem, |
||||
|
Custom, |
||||
|
Group, |
||||
|
Thumb, |
||||
|
DataGrid, |
||||
|
DataItem, |
||||
|
Document, |
||||
|
SplitButton, |
||||
|
Window, |
||||
|
Pane, |
||||
|
Header, |
||||
|
HeaderItem, |
||||
|
Table, |
||||
|
TitleBar, |
||||
|
Separator, |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides a base class that exposes an element to UI Automation.
|
||||
|
/// </summary>
|
||||
|
public abstract class AutomationPeer |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Attempts to bring the element associated with the automation peer into view.
|
||||
|
/// </summary>
|
||||
|
public void BringIntoView() => BringIntoViewCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the accelerator key combinations for the element that is associated with the UI
|
||||
|
/// Automation peer.
|
||||
|
/// </summary>
|
||||
|
public string? GetAcceleratorKey() => GetAcceleratorKeyCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the access key for the element that is associated with the automation peer.
|
||||
|
/// </summary>
|
||||
|
public string? GetAccessKey() => GetAccessKeyCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the control type for the element that is associated with the UI Automation peer.
|
||||
|
/// </summary>
|
||||
|
public AutomationControlType GetAutomationControlType() => GetControlTypeOverrideCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the automation ID of the element that is associated with the UI Automation peer.
|
||||
|
/// </summary>
|
||||
|
public string? GetAutomationId() => GetAutomationIdCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the bounding rectangle of the element that is associated with the automation peer
|
||||
|
/// in top-level coordinates.
|
||||
|
/// </summary>
|
||||
|
public Rect GetBoundingRectangle() => GetBoundingRectangleCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the child automation peers.
|
||||
|
/// </summary>
|
||||
|
public IReadOnlyList<AutomationPeer> GetChildren() => GetOrCreateChildrenCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a string that describes the class of the element.
|
||||
|
/// </summary>
|
||||
|
public string GetClassName() => GetClassNameCore() ?? string.Empty; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the automation peer for the label that is targeted to the element.
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
public AutomationPeer? GetLabeledBy() => GetLabeledByCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a human-readable localized string that represents the type of the control that is
|
||||
|
/// associated with this automation peer.
|
||||
|
/// </summary>
|
||||
|
public string GetLocalizedControlType() => GetLocalizedControlTypeCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets text that describes the element that is associated with this automation peer.
|
||||
|
/// </summary>
|
||||
|
public string GetName() => GetNameCore() ?? string.Empty; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the <see cref="AutomationPeer"/> that is the parent of this <see cref="AutomationPeer"/>.
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
public AutomationPeer? GetParent() => GetParentCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the element that is associated with this automation
|
||||
|
/// peer currently has keyboard focus.
|
||||
|
/// </summary>
|
||||
|
public bool HasKeyboardFocus() => HasKeyboardFocusCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the element that is associated with this automation
|
||||
|
/// peer contains data that is presented to the user.
|
||||
|
/// </summary>
|
||||
|
public bool IsContentElement() => IsControlElement() && IsContentElementCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the element is understood by the user as
|
||||
|
/// interactive or as contributing to the logical structure of the control in the GUI.
|
||||
|
/// </summary>
|
||||
|
public bool IsControlElement() => IsControlElementCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the control is enabled for user interaction.
|
||||
|
/// </summary>
|
||||
|
public bool IsEnabled() => IsEnabledCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the element can accept keyboard focus.
|
||||
|
/// </summary>
|
||||
|
/// <returns></returns>
|
||||
|
public bool IsKeyboardFocusable() => IsKeyboardFocusableCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the keyboard focus on the element that is associated with this automation peer.
|
||||
|
/// </summary>
|
||||
|
public void SetFocus() => SetFocusCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Shows the context menu for the element that is associated with this automation peer.
|
||||
|
/// </summary>
|
||||
|
/// <returns>true if a context menu is present for the element; otherwise false.</returns>
|
||||
|
public bool ShowContextMenu() => ShowContextMenuCore(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to get a provider of the specified type from the peer.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The provider type.</typeparam>
|
||||
|
/// <returns>The provider, or null if not implemented on this peer.</returns>
|
||||
|
public T? GetProvider<T>() => (T?)GetProviderCore(typeof(T)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Occurs when the children of the automation peer have changed.
|
||||
|
/// </summary>
|
||||
|
public event EventHandler? ChildrenChanged; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Occurs when a property value of the automation peer has changed.
|
||||
|
/// </summary>
|
||||
|
public event EventHandler<AutomationPropertyChangedEventArgs>? PropertyChanged; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Raises an event to notify the automation client the the children of the peer have changed.
|
||||
|
/// </summary>
|
||||
|
protected void RaiseChildrenChangedEvent() => ChildrenChanged?.Invoke(this, EventArgs.Empty); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Raises an event to notify the automation client of a changed property value.
|
||||
|
/// </summary>
|
||||
|
/// <param name="property">The property that changed.</param>
|
||||
|
/// <param name="oldValue">The previous value of the property.</param>
|
||||
|
/// <param name="newValue">The new value of the property.</param>
|
||||
|
public void RaisePropertyChangedEvent( |
||||
|
AutomationProperty property, |
||||
|
object? oldValue, |
||||
|
object? newValue) |
||||
|
{ |
||||
|
PropertyChanged?.Invoke(this, new AutomationPropertyChangedEventArgs(property, oldValue, newValue)); |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetLocalizedControlTypeCore() |
||||
|
{ |
||||
|
var controlType = GetAutomationControlType(); |
||||
|
|
||||
|
return controlType switch |
||||
|
{ |
||||
|
AutomationControlType.CheckBox => "check box", |
||||
|
AutomationControlType.ComboBox => "combo box", |
||||
|
AutomationControlType.ListItem => "list item", |
||||
|
AutomationControlType.MenuBar => "menu bar", |
||||
|
AutomationControlType.MenuItem => "menu item", |
||||
|
AutomationControlType.ProgressBar => "progress bar", |
||||
|
AutomationControlType.RadioButton => "radio button", |
||||
|
AutomationControlType.ScrollBar => "scroll bar", |
||||
|
AutomationControlType.StatusBar => "status bar", |
||||
|
AutomationControlType.TabItem => "tab item", |
||||
|
AutomationControlType.ToolBar => "toolbar", |
||||
|
AutomationControlType.ToolTip => "tooltip", |
||||
|
AutomationControlType.TreeItem => "tree item", |
||||
|
AutomationControlType.Custom => "custom", |
||||
|
AutomationControlType.DataGrid => "data grid", |
||||
|
AutomationControlType.DataItem => "data item", |
||||
|
AutomationControlType.SplitButton => "split button", |
||||
|
AutomationControlType.HeaderItem => "header item", |
||||
|
AutomationControlType.TitleBar => "title bar", |
||||
|
_ => controlType.ToString().ToLowerInvariant(), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected abstract void BringIntoViewCore(); |
||||
|
protected abstract string? GetAcceleratorKeyCore(); |
||||
|
protected abstract string? GetAccessKeyCore(); |
||||
|
protected abstract AutomationControlType GetAutomationControlTypeCore(); |
||||
|
protected abstract string? GetAutomationIdCore(); |
||||
|
protected abstract Rect GetBoundingRectangleCore(); |
||||
|
protected abstract IReadOnlyList<AutomationPeer> GetOrCreateChildrenCore(); |
||||
|
protected abstract string GetClassNameCore(); |
||||
|
protected abstract AutomationPeer? GetLabeledByCore(); |
||||
|
protected abstract string? GetNameCore(); |
||||
|
protected abstract AutomationPeer? GetParentCore(); |
||||
|
protected abstract bool HasKeyboardFocusCore(); |
||||
|
protected abstract bool IsContentElementCore(); |
||||
|
protected abstract bool IsControlElementCore(); |
||||
|
protected abstract bool IsEnabledCore(); |
||||
|
protected abstract bool IsKeyboardFocusableCore(); |
||||
|
protected abstract void SetFocusCore(); |
||||
|
protected abstract bool ShowContextMenuCore(); |
||||
|
|
||||
|
protected virtual AutomationControlType GetControlTypeOverrideCore() |
||||
|
{ |
||||
|
return GetAutomationControlTypeCore(); |
||||
|
} |
||||
|
|
||||
|
protected virtual object? GetProviderCore(Type providerType) |
||||
|
{ |
||||
|
return providerType.IsAssignableFrom(this.GetType()) ? this : null; |
||||
|
} |
||||
|
|
||||
|
protected internal abstract bool TrySetParent(AutomationPeer? parent); |
||||
|
|
||||
|
protected void EnsureEnabled() |
||||
|
{ |
||||
|
if (!IsEnabled()) |
||||
|
throw new ElementNotEnabledException(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,43 @@ |
|||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class ButtonAutomationPeer : ContentControlAutomationPeer, |
||||
|
IInvokeProvider |
||||
|
{ |
||||
|
public ButtonAutomationPeer(Button owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new Button Owner => (Button)base.Owner; |
||||
|
|
||||
|
public void Invoke() |
||||
|
{ |
||||
|
EnsureEnabled(); |
||||
|
(Owner as Button)?.PerformClick(); |
||||
|
} |
||||
|
|
||||
|
protected override string? GetAcceleratorKeyCore() |
||||
|
{ |
||||
|
var result = base.GetAcceleratorKeyCore(); |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(result)) |
||||
|
{ |
||||
|
result = Owner.HotKey?.ToString(); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.Button; |
||||
|
} |
||||
|
|
||||
|
protected override bool IsContentElementCore() => true; |
||||
|
protected override bool IsControlElementCore() => true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,137 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class ComboBoxAutomationPeer : SelectingItemsControlAutomationPeer, |
||||
|
IExpandCollapseProvider, |
||||
|
IValueProvider |
||||
|
{ |
||||
|
private UnrealizedSelectionPeer[]? _selection; |
||||
|
|
||||
|
public ComboBoxAutomationPeer(ComboBox owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new ComboBox Owner => (ComboBox)base.Owner; |
||||
|
|
||||
|
public ExpandCollapseState ExpandCollapseState => ToState(Owner.IsDropDownOpen); |
||||
|
public bool ShowsMenu => true; |
||||
|
public void Collapse() => Owner.IsDropDownOpen = false; |
||||
|
public void Expand() => Owner.IsDropDownOpen = true; |
||||
|
bool IValueProvider.IsReadOnly => true; |
||||
|
|
||||
|
string? IValueProvider.Value |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var selection = GetSelection(); |
||||
|
return selection.Count == 1 ? selection[0].GetName() : null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void IValueProvider.SetValue(string? value) => throw new NotSupportedException(); |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.ComboBox; |
||||
|
} |
||||
|
|
||||
|
protected override IReadOnlyList<AutomationPeer>? GetSelectionCore() |
||||
|
{ |
||||
|
if (ExpandCollapseState == ExpandCollapseState.Expanded) |
||||
|
return base.GetSelectionCore(); |
||||
|
|
||||
|
// If the combo box is not open then we won't have an ItemsPresenter so the default
|
||||
|
// GetSelectionCore implementation won't work. For this case we create a separate
|
||||
|
// peer to represent the unrealized item.
|
||||
|
if (Owner.SelectedItem is object selection) |
||||
|
{ |
||||
|
_selection ??= new[] { new UnrealizedSelectionPeer(this) }; |
||||
|
_selection[0].Item = selection; |
||||
|
return _selection; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
protected override void OwnerPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
base.OwnerPropertyChanged(sender, e); |
||||
|
|
||||
|
if (e.Property == ComboBox.IsDropDownOpenProperty) |
||||
|
{ |
||||
|
RaisePropertyChangedEvent( |
||||
|
ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, |
||||
|
ToState((bool)e.OldValue!), |
||||
|
ToState((bool)e.NewValue!)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private ExpandCollapseState ToState(bool value) |
||||
|
{ |
||||
|
return value ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed; |
||||
|
} |
||||
|
|
||||
|
private class UnrealizedSelectionPeer : UnrealizedElementAutomationPeer |
||||
|
{ |
||||
|
private readonly ComboBoxAutomationPeer _owner; |
||||
|
private object? _item; |
||||
|
|
||||
|
public UnrealizedSelectionPeer(ComboBoxAutomationPeer owner) |
||||
|
{ |
||||
|
_owner = owner; |
||||
|
} |
||||
|
|
||||
|
public object? Item |
||||
|
{ |
||||
|
get => _item; |
||||
|
set |
||||
|
{ |
||||
|
if (_item != value) |
||||
|
{ |
||||
|
var oldValue = GetNameCore(); |
||||
|
_item = value; |
||||
|
RaisePropertyChangedEvent( |
||||
|
AutomationElementIdentifiers.NameProperty, |
||||
|
oldValue, |
||||
|
GetNameCore()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override string? GetAcceleratorKeyCore() => null; |
||||
|
protected override string? GetAccessKeyCore() => null; |
||||
|
protected override string? GetAutomationIdCore() => null; |
||||
|
protected override string GetClassNameCore() => typeof(ComboBoxItem).Name; |
||||
|
protected override AutomationPeer? GetLabeledByCore() => null; |
||||
|
protected override AutomationPeer? GetParentCore() => _owner; |
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.ListItem; |
||||
|
|
||||
|
protected override string? GetNameCore() |
||||
|
{ |
||||
|
if (_item is Control c) |
||||
|
{ |
||||
|
var result = AutomationProperties.GetName(c); |
||||
|
|
||||
|
if (result is null && c is ContentControl cc && cc.Presenter?.Child is TextBlock text) |
||||
|
{ |
||||
|
result = text.Text; |
||||
|
} |
||||
|
|
||||
|
if (result is null) |
||||
|
{ |
||||
|
result = c.GetValue(ContentControl.ContentProperty)?.ToString(); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return _item?.ToString(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class ContentControlAutomationPeer : ControlAutomationPeer |
||||
|
{ |
||||
|
protected ContentControlAutomationPeer(ContentControl owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new ContentControl Owner => (ContentControl)base.Owner; |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Pane; |
||||
|
|
||||
|
protected override string? GetNameCore() |
||||
|
{ |
||||
|
var result = base.GetNameCore(); |
||||
|
|
||||
|
if (result is null && Owner.Presenter?.Child is TextBlock text) |
||||
|
{ |
||||
|
result = text.Text; |
||||
|
} |
||||
|
|
||||
|
if (result is null) |
||||
|
{ |
||||
|
result = Owner.Content?.ToString(); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
protected override bool IsContentElementCore() => false; |
||||
|
protected override bool IsControlElementCore() => false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,221 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An automation peer which represents a <see cref="Control"/> element.
|
||||
|
/// </summary>
|
||||
|
public class ControlAutomationPeer : AutomationPeer |
||||
|
{ |
||||
|
private IReadOnlyList<AutomationPeer>? _children; |
||||
|
private bool _childrenValid; |
||||
|
private AutomationPeer? _parent; |
||||
|
private bool _parentValid; |
||||
|
|
||||
|
public ControlAutomationPeer(Control owner) |
||||
|
{ |
||||
|
Owner = owner ?? throw new ArgumentNullException("owner"); |
||||
|
Initialize(); |
||||
|
} |
||||
|
|
||||
|
public Control Owner { get; } |
||||
|
|
||||
|
public AutomationPeer GetOrCreate(Control element) |
||||
|
{ |
||||
|
if (element == Owner) |
||||
|
return this; |
||||
|
return CreatePeerForElement(element); |
||||
|
} |
||||
|
|
||||
|
public static AutomationPeer CreatePeerForElement(Control element) |
||||
|
{ |
||||
|
return element.GetOrCreateAutomationPeer(); |
||||
|
} |
||||
|
|
||||
|
protected override void BringIntoViewCore() => Owner.BringIntoView(); |
||||
|
|
||||
|
protected override IReadOnlyList<AutomationPeer> GetOrCreateChildrenCore() |
||||
|
{ |
||||
|
var children = _children ?? Array.Empty<AutomationPeer>(); |
||||
|
|
||||
|
if (_childrenValid) |
||||
|
return children; |
||||
|
|
||||
|
var newChildren = GetChildrenCore() ?? Array.Empty<AutomationPeer>(); |
||||
|
|
||||
|
foreach (var peer in children.Except(newChildren)) |
||||
|
peer.TrySetParent(null); |
||||
|
foreach (var peer in newChildren) |
||||
|
peer.TrySetParent(this); |
||||
|
|
||||
|
_childrenValid = true; |
||||
|
return _children = newChildren; |
||||
|
} |
||||
|
|
||||
|
protected virtual IReadOnlyList<AutomationPeer>? GetChildrenCore() |
||||
|
{ |
||||
|
var children = ((IVisual)Owner).VisualChildren; |
||||
|
|
||||
|
if (children.Count == 0) |
||||
|
return null; |
||||
|
|
||||
|
var result = new List<AutomationPeer>(); |
||||
|
|
||||
|
foreach (var child in children) |
||||
|
{ |
||||
|
if (child is Control c && c.IsVisible) |
||||
|
{ |
||||
|
result.Add(GetOrCreate(c)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
protected override AutomationPeer? GetLabeledByCore() |
||||
|
{ |
||||
|
var label = AutomationProperties.GetLabeledBy(Owner); |
||||
|
return label is Control c ? GetOrCreate(c) : null; |
||||
|
} |
||||
|
|
||||
|
protected override string? GetNameCore() |
||||
|
{ |
||||
|
var result = AutomationProperties.GetName(Owner); |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(result) && GetLabeledBy() is AutomationPeer labeledBy) |
||||
|
{ |
||||
|
return labeledBy.GetName(); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
protected override AutomationPeer? GetParentCore() |
||||
|
{ |
||||
|
EnsureConnected(); |
||||
|
return _parent; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invalidates the peer's children and causes a re-read from <see cref="GetChildrenCore"/>.
|
||||
|
/// </summary>
|
||||
|
protected void InvalidateChildren() |
||||
|
{ |
||||
|
_childrenValid = false; |
||||
|
RaiseChildrenChangedEvent(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Invalidates the peer's parent.
|
||||
|
/// </summary>
|
||||
|
protected void InvalidateParent() |
||||
|
{ |
||||
|
_parent = null; |
||||
|
_parentValid = false; |
||||
|
} |
||||
|
|
||||
|
protected override bool ShowContextMenuCore() |
||||
|
{ |
||||
|
var c = Owner; |
||||
|
|
||||
|
while (c is object) |
||||
|
{ |
||||
|
if (c.ContextMenu is object) |
||||
|
{ |
||||
|
c.ContextMenu.Open(c); |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
c = c.Parent as Control; |
||||
|
} |
||||
|
|
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
protected internal override bool TrySetParent(AutomationPeer? parent) |
||||
|
{ |
||||
|
_parent = parent; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
protected override string? GetAcceleratorKeyCore() => AutomationProperties.GetAcceleratorKey(Owner); |
||||
|
protected override string? GetAccessKeyCore() => AutomationProperties.GetAccessKey(Owner); |
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Custom; |
||||
|
protected override string? GetAutomationIdCore() => AutomationProperties.GetAutomationId(Owner) ?? Owner.Name; |
||||
|
protected override Rect GetBoundingRectangleCore() => GetBounds(Owner.TransformedBounds); |
||||
|
protected override string GetClassNameCore() => Owner.GetType().Name; |
||||
|
protected override bool HasKeyboardFocusCore() => Owner.IsFocused; |
||||
|
protected override bool IsContentElementCore() => AutomationProperties.GetAccessibilityView(Owner) >= AccessibilityView.Content; |
||||
|
protected override bool IsControlElementCore() => AutomationProperties.GetAccessibilityView(Owner) >= AccessibilityView.Control; |
||||
|
protected override bool IsEnabledCore() => Owner.IsEnabled; |
||||
|
protected override bool IsKeyboardFocusableCore() => Owner.Focusable; |
||||
|
protected override void SetFocusCore() => Owner.Focus(); |
||||
|
|
||||
|
protected override AutomationControlType GetControlTypeOverrideCore() |
||||
|
{ |
||||
|
return AutomationProperties.GetControlTypeOverride(Owner) ?? GetAutomationControlTypeCore(); |
||||
|
} |
||||
|
|
||||
|
private static Rect GetBounds(TransformedBounds? bounds) |
||||
|
{ |
||||
|
return bounds?.Bounds.TransformToAABB(bounds!.Value.Transform) ?? default; |
||||
|
} |
||||
|
|
||||
|
private void Initialize() |
||||
|
{ |
||||
|
Owner.PropertyChanged += OwnerPropertyChanged; |
||||
|
var visualChildren = ((IVisual)Owner).VisualChildren; |
||||
|
visualChildren.CollectionChanged += VisualChildrenChanged; |
||||
|
} |
||||
|
|
||||
|
private void VisualChildrenChanged(object? sender, EventArgs e) => InvalidateChildren(); |
||||
|
|
||||
|
private void OwnerPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (e.Property == Visual.IsVisibleProperty) |
||||
|
{ |
||||
|
var parent = Owner.GetVisualParent(); |
||||
|
if (parent is Control c) |
||||
|
(GetOrCreate(c) as ControlAutomationPeer)?.InvalidateChildren(); |
||||
|
} |
||||
|
else if (e.Property == Visual.TransformedBoundsProperty) |
||||
|
{ |
||||
|
RaisePropertyChangedEvent( |
||||
|
AutomationElementIdentifiers.BoundingRectangleProperty, |
||||
|
GetBounds((TransformedBounds?)e.OldValue), |
||||
|
GetBounds((TransformedBounds?)e.NewValue)); |
||||
|
} |
||||
|
else if (e.Property == Visual.VisualParentProperty) |
||||
|
{ |
||||
|
InvalidateParent(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
private void EnsureConnected() |
||||
|
{ |
||||
|
if (!_parentValid) |
||||
|
{ |
||||
|
var parent = Owner.GetVisualParent(); |
||||
|
|
||||
|
while (parent is object) |
||||
|
{ |
||||
|
if (parent is Control c) |
||||
|
{ |
||||
|
var parentPeer = GetOrCreate(c); |
||||
|
parentPeer.GetChildren(); |
||||
|
} |
||||
|
|
||||
|
parent = parent.GetVisualParent(); |
||||
|
} |
||||
|
|
||||
|
_parentValid = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,54 @@ |
|||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class ItemsControlAutomationPeer : ControlAutomationPeer, IScrollProvider |
||||
|
{ |
||||
|
private bool _searchedForScrollable; |
||||
|
private IScrollProvider? _scroller; |
||||
|
|
||||
|
public ItemsControlAutomationPeer(ItemsControl owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new ItemsControl Owner => (ItemsControl)base.Owner; |
||||
|
public bool HorizontallyScrollable => _scroller?.HorizontallyScrollable ?? false; |
||||
|
public double HorizontalScrollPercent => _scroller?.HorizontalScrollPercent ?? -1; |
||||
|
public double HorizontalViewSize => _scroller?.HorizontalViewSize ?? 0; |
||||
|
public bool VerticallyScrollable => _scroller?.VerticallyScrollable ?? false; |
||||
|
public double VerticalScrollPercent => _scroller?.VerticalScrollPercent ?? -1; |
||||
|
public double VerticalViewSize => _scroller?.VerticalViewSize ?? 0; |
||||
|
|
||||
|
protected virtual IScrollProvider? Scroller |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (!_searchedForScrollable) |
||||
|
{ |
||||
|
if (Owner.GetValue(ListBox.ScrollProperty) is Control scrollable) |
||||
|
_scroller = GetOrCreate(scrollable) as IScrollProvider; |
||||
|
_searchedForScrollable = true; |
||||
|
} |
||||
|
|
||||
|
return _scroller; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.List; |
||||
|
} |
||||
|
|
||||
|
public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount) |
||||
|
{ |
||||
|
_scroller?.Scroll(horizontalAmount, verticalAmount); |
||||
|
} |
||||
|
|
||||
|
public void SetScrollPercent(double horizontalPercent, double verticalPercent) |
||||
|
{ |
||||
|
_scroller?.SetScrollPercent(horizontalPercent, verticalPercent); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.Controls.Selection; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class ListItemAutomationPeer : ContentControlAutomationPeer, |
||||
|
ISelectionItemProvider |
||||
|
{ |
||||
|
public ListItemAutomationPeer(ContentControl owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public bool IsSelected => Owner.GetValue(ListBoxItem.IsSelectedProperty); |
||||
|
|
||||
|
public ISelectionProvider? SelectionContainer |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (Owner.Parent is Control parent) |
||||
|
{ |
||||
|
var parentPeer = GetOrCreate(parent); |
||||
|
return parentPeer as ISelectionProvider; |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Select() |
||||
|
{ |
||||
|
EnsureEnabled(); |
||||
|
|
||||
|
if (Owner.Parent is SelectingItemsControl parent) |
||||
|
{ |
||||
|
var index = parent.ItemContainerGenerator.IndexFromContainer(Owner); |
||||
|
|
||||
|
if (index != -1) |
||||
|
parent.SelectedIndex = index; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ISelectionItemProvider.AddToSelection() |
||||
|
{ |
||||
|
EnsureEnabled(); |
||||
|
|
||||
|
if (Owner.Parent is ItemsControl parent && |
||||
|
parent.GetValue(ListBox.SelectionProperty) is ISelectionModel selectionModel) |
||||
|
{ |
||||
|
var index = parent.ItemContainerGenerator.IndexFromContainer(Owner); |
||||
|
|
||||
|
if (index != -1) |
||||
|
selectionModel.Select(index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void ISelectionItemProvider.RemoveFromSelection() |
||||
|
{ |
||||
|
EnsureEnabled(); |
||||
|
|
||||
|
if (Owner.Parent is ItemsControl parent && |
||||
|
parent.GetValue(ListBox.SelectionProperty) is ISelectionModel selectionModel) |
||||
|
{ |
||||
|
var index = parent.ItemContainerGenerator.IndexFromContainer(Owner); |
||||
|
|
||||
|
if (index != -1) |
||||
|
selectionModel.Deselect(index); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.ListItem; |
||||
|
} |
||||
|
|
||||
|
protected override bool IsContentElementCore() => true; |
||||
|
protected override bool IsControlElementCore() => true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class MenuItemAutomationPeer : ControlAutomationPeer |
||||
|
{ |
||||
|
public MenuItemAutomationPeer(MenuItem owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new MenuItem Owner => (MenuItem)base.Owner; |
||||
|
|
||||
|
protected override string? GetAccessKeyCore() |
||||
|
{ |
||||
|
var result = base.GetAccessKeyCore(); |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(result)) |
||||
|
{ |
||||
|
if (Owner.HeaderPresenter?.Child is AccessText accessText) |
||||
|
{ |
||||
|
result = accessText.AccessKey.ToString(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
protected override string? GetAcceleratorKeyCore() |
||||
|
{ |
||||
|
var result = base.GetAcceleratorKeyCore(); |
||||
|
|
||||
|
if (string.IsNullOrWhiteSpace(result)) |
||||
|
{ |
||||
|
result = Owner.InputGesture?.ToString(); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.MenuItem; |
||||
|
} |
||||
|
|
||||
|
protected override string? GetNameCore() |
||||
|
{ |
||||
|
var result = base.GetNameCore(); |
||||
|
|
||||
|
if (result is null && Owner.Header is string header) |
||||
|
{ |
||||
|
result = AccessText.RemoveAccessKeyMarker(header); |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An automation peer which represents an element that is exposed to automation as non-
|
||||
|
/// interactive or as not contributing to the logical structure of the application.
|
||||
|
/// </summary>
|
||||
|
public class NoneAutomationPeer : ControlAutomationPeer |
||||
|
{ |
||||
|
public NoneAutomationPeer(Control owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.None; |
||||
|
} |
||||
|
|
||||
|
protected override bool IsContentElementCore() => false; |
||||
|
protected override bool IsControlElementCore() => false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,48 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Diagnostics; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class PopupAutomationPeer : ControlAutomationPeer |
||||
|
{ |
||||
|
public PopupAutomationPeer(Popup owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
owner.Opened += PopupOpenedClosed; |
||||
|
owner.Closed += PopupOpenedClosed; |
||||
|
} |
||||
|
|
||||
|
protected override IReadOnlyList<AutomationPeer>? GetChildrenCore() |
||||
|
{ |
||||
|
var host = (IPopupHostProvider)Owner; |
||||
|
return host.PopupHost is Control c ? new[] { GetOrCreate(c) } : null; |
||||
|
} |
||||
|
|
||||
|
protected override bool IsContentElementCore() => false; |
||||
|
protected override bool IsControlElementCore() => false; |
||||
|
|
||||
|
private void PopupOpenedClosed(object? sender, EventArgs e) |
||||
|
{ |
||||
|
// This is golden. We're following WPF's automation peer API here where the
|
||||
|
// parent of a peer is set when another peer returns it as a child. We want to
|
||||
|
// add the popup root as a child of the popup, so we need to return it as a
|
||||
|
// child right? Yeah except invalidating children doesn't automatically cause
|
||||
|
// UIA to re-read the children meaning that the parent doesn't get set. So the
|
||||
|
// MAIN MECHANISM FOR PARENTING CONTROLS IS BROKEN WITH THE ONLY AUTOMATION API
|
||||
|
// IT WAS WRITTEN FOR. Luckily WPF provides an escape-hatch by exposing the
|
||||
|
// TrySetParent API internally to work around this. We're exposing it publicly
|
||||
|
// to shame whoever came up with this abomination of an API.
|
||||
|
GetPopupRoot()?.TrySetParent(this); |
||||
|
InvalidateChildren(); |
||||
|
} |
||||
|
|
||||
|
private AutomationPeer? GetPopupRoot() |
||||
|
{ |
||||
|
var popupRoot = ((IPopupHostProvider)Owner).PopupHost as Control; |
||||
|
return popupRoot is object ? GetOrCreate(popupRoot) : null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class PopupRootAutomationPeer : WindowBaseAutomationPeer |
||||
|
{ |
||||
|
public PopupRootAutomationPeer(PopupRoot owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
if (owner.IsVisible) |
||||
|
StartTrackingFocus(); |
||||
|
else |
||||
|
owner.Opened += OnOpened; |
||||
|
owner.Closed += OnClosed; |
||||
|
} |
||||
|
|
||||
|
protected override bool IsContentElementCore() => false; |
||||
|
protected override bool IsControlElementCore() => false; |
||||
|
|
||||
|
|
||||
|
protected override AutomationPeer? GetParentCore() |
||||
|
{ |
||||
|
var parent = base.GetParentCore(); |
||||
|
return parent; |
||||
|
} |
||||
|
|
||||
|
private void OnOpened(object? sender, EventArgs e) |
||||
|
{ |
||||
|
((PopupRoot)Owner).Opened -= OnOpened; |
||||
|
StartTrackingFocus(); |
||||
|
} |
||||
|
|
||||
|
private void OnClosed(object? sender, EventArgs e) |
||||
|
{ |
||||
|
((PopupRoot)Owner).Closed -= OnClosed; |
||||
|
StopTrackingFocus(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public abstract class RangeBaseAutomationPeer : ControlAutomationPeer, IRangeValueProvider |
||||
|
{ |
||||
|
public RangeBaseAutomationPeer(RangeBase owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
owner.PropertyChanged += OwnerPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
public new RangeBase Owner => (RangeBase)base.Owner; |
||||
|
public virtual bool IsReadOnly => false; |
||||
|
public double Maximum => Owner.Maximum; |
||||
|
public double Minimum => Owner.Minimum; |
||||
|
public double Value => Owner.Value; |
||||
|
public double SmallChange => Owner.SmallChange; |
||||
|
public double LargeChange => Owner.LargeChange; |
||||
|
|
||||
|
public void SetValue(double value) => Owner.Value = value; |
||||
|
|
||||
|
protected virtual void OwnerPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (e.Property == RangeBase.MinimumProperty) |
||||
|
RaisePropertyChangedEvent(RangeValuePatternIdentifiers.MinimumProperty, e.OldValue, e.NewValue); |
||||
|
else if (e.Property == RangeBase.MaximumProperty) |
||||
|
RaisePropertyChangedEvent(RangeValuePatternIdentifiers.MaximumProperty, e.OldValue, e.NewValue); |
||||
|
else if (e.Property == RangeBase.ValueProperty) |
||||
|
RaisePropertyChangedEvent(RangeValuePatternIdentifiers.ValueProperty, e.OldValue, e.NewValue); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,172 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Utilities; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class ScrollViewerAutomationPeer : ControlAutomationPeer, IScrollProvider |
||||
|
{ |
||||
|
public ScrollViewerAutomationPeer(ScrollViewer owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new ScrollViewer Owner => (ScrollViewer)base.Owner; |
||||
|
|
||||
|
public bool HorizontallyScrollable |
||||
|
{ |
||||
|
get => MathUtilities.GreaterThan(Owner.Extent.Width, Owner.Viewport.Width); |
||||
|
} |
||||
|
|
||||
|
public double HorizontalScrollPercent |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (!HorizontallyScrollable) |
||||
|
return ScrollPatternIdentifiers.NoScroll; |
||||
|
return (double)(Owner.Offset.X * 100.0 / (Owner.Extent.Width - Owner.Viewport.Width)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public double HorizontalViewSize |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (MathUtilities.IsZero(Owner.Extent.Width)) |
||||
|
return 100; |
||||
|
return Math.Min(100, Owner.Viewport.Width * 100.0 / Owner.Extent.Width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public bool VerticallyScrollable |
||||
|
{ |
||||
|
get => MathUtilities.GreaterThan(Owner.Extent.Height, Owner.Viewport.Height); |
||||
|
} |
||||
|
|
||||
|
public double VerticalScrollPercent |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (!VerticallyScrollable) |
||||
|
return ScrollPatternIdentifiers.NoScroll; |
||||
|
return (double)(Owner.Offset.Y * 100.0 / (Owner.Extent.Height - Owner.Viewport.Height)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public double VerticalViewSize |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
if (MathUtilities.IsZero(Owner.Extent.Height)) |
||||
|
return 100; |
||||
|
return Math.Min(100, Owner.Viewport.Height * 100.0 / Owner.Extent.Height); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.Pane; |
||||
|
} |
||||
|
|
||||
|
protected override bool IsContentElementCore() => false; |
||||
|
|
||||
|
protected override bool IsControlElementCore() |
||||
|
{ |
||||
|
// Return false if the control is part of a control template.
|
||||
|
return Owner.TemplatedParent is null && base.IsControlElementCore(); |
||||
|
} |
||||
|
|
||||
|
public void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount) |
||||
|
{ |
||||
|
if (!IsEnabled()) |
||||
|
throw new ElementNotEnabledException(); |
||||
|
|
||||
|
var scrollHorizontally = horizontalAmount != ScrollAmount.NoAmount; |
||||
|
var scrollVertically = verticalAmount != ScrollAmount.NoAmount; |
||||
|
|
||||
|
if (scrollHorizontally && !HorizontallyScrollable || scrollVertically && !VerticallyScrollable) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Operation cannot be performed"); |
||||
|
} |
||||
|
|
||||
|
switch (horizontalAmount) |
||||
|
{ |
||||
|
case ScrollAmount.LargeDecrement: |
||||
|
Owner.PageLeft(); |
||||
|
break; |
||||
|
case ScrollAmount.SmallDecrement: |
||||
|
Owner.LineLeft(); |
||||
|
break; |
||||
|
case ScrollAmount.SmallIncrement: |
||||
|
Owner.LineRight(); |
||||
|
break; |
||||
|
case ScrollAmount.LargeIncrement: |
||||
|
Owner.PageRight(); |
||||
|
break; |
||||
|
case ScrollAmount.NoAmount: |
||||
|
break; |
||||
|
default: |
||||
|
throw new InvalidOperationException("Operation cannot be performed"); |
||||
|
} |
||||
|
|
||||
|
switch (verticalAmount) |
||||
|
{ |
||||
|
case ScrollAmount.LargeDecrement: |
||||
|
Owner.PageUp(); |
||||
|
break; |
||||
|
case ScrollAmount.SmallDecrement: |
||||
|
Owner.LineUp(); |
||||
|
break; |
||||
|
case ScrollAmount.SmallIncrement: |
||||
|
Owner.LineDown(); |
||||
|
break; |
||||
|
case ScrollAmount.LargeIncrement: |
||||
|
Owner.PageDown(); |
||||
|
break; |
||||
|
case ScrollAmount.NoAmount: |
||||
|
break; |
||||
|
default: |
||||
|
throw new InvalidOperationException("Operation cannot be performed"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void SetScrollPercent(double horizontalPercent, double verticalPercent) |
||||
|
{ |
||||
|
if (!IsEnabled()) |
||||
|
throw new ElementNotEnabledException(); |
||||
|
|
||||
|
var scrollHorizontally = horizontalPercent != ScrollPatternIdentifiers.NoScroll; |
||||
|
var scrollVertically = verticalPercent != ScrollPatternIdentifiers.NoScroll; |
||||
|
|
||||
|
if (scrollHorizontally && !HorizontallyScrollable || scrollVertically && !VerticallyScrollable) |
||||
|
{ |
||||
|
throw new InvalidOperationException("Operation cannot be performed"); |
||||
|
} |
||||
|
|
||||
|
if (scrollHorizontally && (horizontalPercent < 0.0) || (horizontalPercent > 100.0)) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException("horizontalPercent"); |
||||
|
} |
||||
|
|
||||
|
if (scrollVertically && (verticalPercent < 0.0) || (verticalPercent > 100.0)) |
||||
|
{ |
||||
|
throw new ArgumentOutOfRangeException("verticalPercent"); |
||||
|
} |
||||
|
|
||||
|
var offset = Owner.Offset; |
||||
|
|
||||
|
if (scrollHorizontally) |
||||
|
{ |
||||
|
offset = offset.WithX((Owner.Extent.Width - Owner.Viewport.Width) * horizontalPercent * 0.01); |
||||
|
} |
||||
|
|
||||
|
if (scrollVertically) |
||||
|
{ |
||||
|
offset = offset.WithY((Owner.Extent.Height - Owner.Viewport.Height) * verticalPercent * 0.01); |
||||
|
} |
||||
|
|
||||
|
Owner.Offset = offset; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.Controls.Selection; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public abstract class SelectingItemsControlAutomationPeer : ItemsControlAutomationPeer, |
||||
|
ISelectionProvider |
||||
|
{ |
||||
|
private ISelectionModel _selection; |
||||
|
|
||||
|
protected SelectingItemsControlAutomationPeer(SelectingItemsControl owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
_selection = owner.GetValue(ListBox.SelectionProperty); |
||||
|
_selection.SelectionChanged += OwnerSelectionChanged; |
||||
|
owner.PropertyChanged += OwnerPropertyChanged; |
||||
|
} |
||||
|
|
||||
|
public bool CanSelectMultiple => GetSelectionModeCore().HasAllFlags(SelectionMode.Multiple); |
||||
|
public bool IsSelectionRequired => GetSelectionModeCore().HasAllFlags(SelectionMode.AlwaysSelected); |
||||
|
public IReadOnlyList<AutomationPeer> GetSelection() => GetSelectionCore() ?? Array.Empty<AutomationPeer>(); |
||||
|
|
||||
|
protected virtual IReadOnlyList<AutomationPeer>? GetSelectionCore() |
||||
|
{ |
||||
|
List<AutomationPeer>? result = null; |
||||
|
|
||||
|
if (Owner is SelectingItemsControl owner) |
||||
|
{ |
||||
|
var selection = Owner.GetValue(ListBox.SelectionProperty); |
||||
|
|
||||
|
foreach (var i in selection.SelectedIndexes) |
||||
|
{ |
||||
|
var container = owner.ItemContainerGenerator.ContainerFromIndex(i); |
||||
|
|
||||
|
if (container is Control c && ((IVisual)c).IsAttachedToVisualTree) |
||||
|
{ |
||||
|
var peer = GetOrCreate(c); |
||||
|
|
||||
|
if (peer is object) |
||||
|
{ |
||||
|
result ??= new List<AutomationPeer>(); |
||||
|
result.Add(peer); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
protected virtual SelectionMode GetSelectionModeCore() |
||||
|
{ |
||||
|
return (Owner as SelectingItemsControl)?.GetValue(ListBox.SelectionModeProperty) ?? SelectionMode.Single; |
||||
|
} |
||||
|
|
||||
|
protected virtual void OwnerPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (e.Property == ListBox.SelectionProperty) |
||||
|
{ |
||||
|
_selection.SelectionChanged -= OwnerSelectionChanged; |
||||
|
_selection = Owner.GetValue(ListBox.SelectionProperty); |
||||
|
_selection.SelectionChanged += OwnerSelectionChanged; |
||||
|
RaiseSelectionChanged(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected virtual void OwnerSelectionChanged(object? sender, SelectionModelSelectionChangedEventArgs e) |
||||
|
{ |
||||
|
RaiseSelectionChanged(); |
||||
|
} |
||||
|
|
||||
|
private void RaiseSelectionChanged() |
||||
|
{ |
||||
|
RaisePropertyChangedEvent(SelectionPatternIdentifiers.SelectionProperty, null, null); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class TextBlockAutomationPeer : ControlAutomationPeer |
||||
|
{ |
||||
|
public TextBlockAutomationPeer(TextBlock owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new TextBlock Owner => (TextBlock)base.Owner; |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.Text; |
||||
|
} |
||||
|
|
||||
|
protected override string? GetNameCore() => Owner.Text; |
||||
|
|
||||
|
protected override bool IsControlElementCore() |
||||
|
{ |
||||
|
// Return false if the control is part of a control template.
|
||||
|
return Owner.TemplatedParent is null && base.IsControlElementCore(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,23 @@ |
|||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class TextBoxAutomationPeer : ControlAutomationPeer, IValueProvider |
||||
|
{ |
||||
|
public TextBoxAutomationPeer(TextBox owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new TextBox Owner => (TextBox)base.Owner; |
||||
|
public bool IsReadOnly => Owner.IsReadOnly; |
||||
|
public string? Value => Owner.Text; |
||||
|
public void SetValue(string? value) => Owner.Text = value; |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.Edit; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,39 @@ |
|||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class ToggleButtonAutomationPeer : ContentControlAutomationPeer, IToggleProvider |
||||
|
{ |
||||
|
public ToggleButtonAutomationPeer(ToggleButton owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new ToggleButton Owner => (ToggleButton)base.Owner; |
||||
|
|
||||
|
ToggleState IToggleProvider.ToggleState |
||||
|
{ |
||||
|
get => Owner.IsChecked switch |
||||
|
{ |
||||
|
true => ToggleState.On, |
||||
|
false => ToggleState.Off, |
||||
|
null => ToggleState.Indeterminate, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
void IToggleProvider.Toggle() |
||||
|
{ |
||||
|
EnsureEnabled(); |
||||
|
Owner.PerformClick(); |
||||
|
} |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.Button; |
||||
|
} |
||||
|
|
||||
|
protected override bool IsContentElementCore() => true; |
||||
|
protected override bool IsControlElementCore() => true; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// An automation peer which represents an unrealized element
|
||||
|
/// </summary>
|
||||
|
public abstract class UnrealizedElementAutomationPeer : AutomationPeer |
||||
|
{ |
||||
|
public void SetParent(AutomationPeer? parent) => TrySetParent(parent); |
||||
|
protected override void BringIntoViewCore() => GetParent()?.BringIntoView(); |
||||
|
protected override Rect GetBoundingRectangleCore() => GetParent()?.GetBoundingRectangle() ?? default; |
||||
|
protected override IReadOnlyList<AutomationPeer> GetOrCreateChildrenCore() => Array.Empty<AutomationPeer>(); |
||||
|
protected override bool HasKeyboardFocusCore() => false; |
||||
|
protected override bool IsContentElementCore() => false; |
||||
|
protected override bool IsControlElementCore() => false; |
||||
|
protected override bool IsEnabledCore() => true; |
||||
|
protected override bool IsKeyboardFocusableCore() => false; |
||||
|
protected override void SetFocusCore() { } |
||||
|
protected override bool ShowContextMenuCore() => false; |
||||
|
protected internal override bool TrySetParent(AutomationPeer? parent) => false; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class WindowAutomationPeer : WindowBaseAutomationPeer |
||||
|
{ |
||||
|
public WindowAutomationPeer(Window owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
if (owner.IsVisible) |
||||
|
StartTrackingFocus(); |
||||
|
else |
||||
|
owner.Opened += OnOpened; |
||||
|
owner.Closed += OnClosed; |
||||
|
} |
||||
|
|
||||
|
public new Window Owner => (Window)base.Owner; |
||||
|
|
||||
|
protected override string? GetNameCore() => Owner.Title; |
||||
|
|
||||
|
private void OnOpened(object? sender, EventArgs e) |
||||
|
{ |
||||
|
Owner.Opened -= OnOpened; |
||||
|
StartTrackingFocus(); |
||||
|
} |
||||
|
|
||||
|
private void OnClosed(object? sender, EventArgs e) |
||||
|
{ |
||||
|
Owner.Closed -= OnClosed; |
||||
|
StopTrackingFocus(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
@ -0,0 +1,78 @@ |
|||||
|
using System; |
||||
|
using System.ComponentModel; |
||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.Automation.Peers |
||||
|
{ |
||||
|
public class WindowBaseAutomationPeer : ControlAutomationPeer, IRootProvider |
||||
|
{ |
||||
|
private Control? _focus; |
||||
|
|
||||
|
public WindowBaseAutomationPeer(WindowBase owner) |
||||
|
: base(owner) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public new WindowBase Owner => (WindowBase)base.Owner; |
||||
|
public ITopLevelImpl? PlatformImpl => Owner.PlatformImpl; |
||||
|
|
||||
|
public event EventHandler? FocusChanged; |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() |
||||
|
{ |
||||
|
return AutomationControlType.Window; |
||||
|
} |
||||
|
|
||||
|
public AutomationPeer? GetFocus() => _focus is object ? GetOrCreate(_focus) : null; |
||||
|
|
||||
|
public AutomationPeer? GetPeerFromPoint(Point p) |
||||
|
{ |
||||
|
var hit = Owner.GetVisualAt(p)?.FindAncestorOfType<Control>(includeSelf: true); |
||||
|
return hit is object ? GetOrCreate(hit) : null; |
||||
|
} |
||||
|
|
||||
|
protected void StartTrackingFocus() |
||||
|
{ |
||||
|
if (KeyboardDevice.Instance is not null) |
||||
|
{ |
||||
|
KeyboardDevice.Instance.PropertyChanged += KeyboardDevicePropertyChanged; |
||||
|
OnFocusChanged(KeyboardDevice.Instance.FocusedElement); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected void StopTrackingFocus() |
||||
|
{ |
||||
|
if (KeyboardDevice.Instance is not null) |
||||
|
KeyboardDevice.Instance.PropertyChanged -= KeyboardDevicePropertyChanged; |
||||
|
} |
||||
|
|
||||
|
private void OnFocusChanged(IInputElement? focus) |
||||
|
{ |
||||
|
var oldFocus = _focus; |
||||
|
|
||||
|
_focus = focus?.VisualRoot == Owner ? focus as Control : null; |
||||
|
|
||||
|
if (_focus != oldFocus) |
||||
|
{ |
||||
|
var peer = _focus is object ? |
||||
|
_focus == Owner ? this : |
||||
|
GetOrCreate(_focus) : null; |
||||
|
FocusChanged?.Invoke(this, EventArgs.Empty); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void KeyboardDevicePropertyChanged(object? sender, PropertyChangedEventArgs e) |
||||
|
{ |
||||
|
if (e.PropertyName == nameof(KeyboardDevice.FocusedElement)) |
||||
|
{ |
||||
|
OnFocusChanged(KeyboardDevice.Instance!.FocusedElement); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
@ -0,0 +1,33 @@ |
|||||
|
namespace Avalonia.Automation.Provider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Exposes methods and properties to support UI Automation client access to controls that
|
||||
|
/// visually expand to display content and collapse to hide content.
|
||||
|
/// </summary>
|
||||
|
public interface IExpandCollapseProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the state, expanded or collapsed, of the control.
|
||||
|
/// </summary>
|
||||
|
ExpandCollapseState ExpandCollapseState { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether expanding the element shows a menu of items to the user,
|
||||
|
/// such as drop-down list.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Used in OSX to enable the "Show Menu" action on the element.
|
||||
|
/// </remarks>
|
||||
|
bool ShowsMenu { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Displays all child nodes, controls, or content of the control.
|
||||
|
/// </summary>
|
||||
|
void Expand(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Hides all nodes, controls, or content that are descendants of the control.
|
||||
|
/// </summary>
|
||||
|
void Collapse(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
namespace Avalonia.Automation.Provider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Exposes methods and properties to support UI Automation client access to controls that
|
||||
|
/// initiate or perform a single, unambiguous action and do not maintain state when
|
||||
|
/// activated.
|
||||
|
/// </summary>
|
||||
|
public interface IInvokeProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Sends a request to activate a control and initiate its single, unambiguous action.
|
||||
|
/// </summary>
|
||||
|
void Invoke(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
namespace Avalonia.Automation.Provider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Exposes methods and properties to support access by a UI Automation client to controls
|
||||
|
/// that can be set to a value within a range.
|
||||
|
/// </summary>
|
||||
|
public interface IRangeValueProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the value of a control is read-only.
|
||||
|
/// </summary>
|
||||
|
bool IsReadOnly { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the minimum range value that is supported by the control.
|
||||
|
/// </summary>
|
||||
|
double Minimum { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the maximum range value that is supported by the control.
|
||||
|
/// </summary>
|
||||
|
double Maximum { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the value of the control.
|
||||
|
/// </summary>
|
||||
|
double Value { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the value that is added to or subtracted from the Value property when a large
|
||||
|
/// change is made, such as with the PAGE DOWN key.
|
||||
|
/// </summary>
|
||||
|
double LargeChange { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the value that is added to or subtracted from the Value property when a small
|
||||
|
/// change is made, such as with an arrow key.
|
||||
|
/// </summary>
|
||||
|
double SmallChange { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the value of the control.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">The value to set.</param>
|
||||
|
public void SetValue(double value); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Automation.Peers; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Automation.Provider |
||||
|
{ |
||||
|
public interface IRootProvider |
||||
|
{ |
||||
|
ITopLevelImpl? PlatformImpl { get; } |
||||
|
AutomationPeer? GetFocus(); |
||||
|
AutomationPeer? GetPeerFromPoint(Point p); |
||||
|
event EventHandler? FocusChanged; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
namespace Avalonia.Automation.Provider |
||||
|
{ |
||||
|
public enum ScrollAmount |
||||
|
{ |
||||
|
LargeDecrement, |
||||
|
SmallDecrement, |
||||
|
NoAmount, |
||||
|
LargeIncrement, |
||||
|
SmallIncrement, |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes methods and properties to support access by a UI Automation client to a control
|
||||
|
/// that acts as a scrollable container for a collection of child objects.
|
||||
|
/// </summary>
|
||||
|
public interface IScrollProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the control can scroll horizontally.
|
||||
|
/// </summary>
|
||||
|
bool HorizontallyScrollable { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current horizontal scroll position.
|
||||
|
/// </summary>
|
||||
|
double HorizontalScrollPercent { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current horizontal view size.
|
||||
|
/// </summary>
|
||||
|
double HorizontalViewSize { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the control can scroll vertically.
|
||||
|
/// </summary>
|
||||
|
bool VerticallyScrollable { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current vertical scroll position.
|
||||
|
/// </summary>
|
||||
|
double VerticalScrollPercent { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the vertical view size.
|
||||
|
/// </summary>
|
||||
|
double VerticalViewSize { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Scrolls the visible region of the content area horizontally and vertically.
|
||||
|
/// </summary>
|
||||
|
/// <param name="horizontalAmount">The horizontal increment specific to the control.</param>
|
||||
|
/// <param name="verticalAmount">The vertical increment specific to the control.</param>
|
||||
|
void Scroll(ScrollAmount horizontalAmount, ScrollAmount verticalAmount); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the horizontal and vertical scroll position as a percentage of the total content
|
||||
|
/// area within the control.
|
||||
|
/// </summary>
|
||||
|
/// <param name="horizontalPercent">
|
||||
|
/// The horizontal position as a percentage of the content area's total range.
|
||||
|
/// <see cref="ScrollPatternIdentifiers.NoScroll"/> should be passed in if the control
|
||||
|
/// cannot be scrolled in this direction.
|
||||
|
/// </param>
|
||||
|
/// <param name="verticalPercent">
|
||||
|
/// The vertical position as a percentage of the content area's total range.
|
||||
|
/// <see cref="ScrollPatternIdentifiers.NoScroll"/> should be passed in if the control
|
||||
|
/// cannot be scrolled in this direction.
|
||||
|
/// </param>
|
||||
|
void SetScrollPercent(double horizontalPercent, double verticalPercent); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
namespace Avalonia.Automation.Provider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Exposes methods and properties to support access by a UI Automation client to individual,
|
||||
|
/// selectable child controls of containers that implement <see cref="ISelectionProvider"/>.
|
||||
|
/// </summary>
|
||||
|
public interface ISelectionItemProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether an item is selected.
|
||||
|
/// </summary>
|
||||
|
bool IsSelected { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the UI Automation provider that implements <see cref="ISelectionProvider"/> and
|
||||
|
/// acts as the container for the calling object.
|
||||
|
/// </summary>
|
||||
|
ISelectionProvider? SelectionContainer { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Adds the current element to the collection of selected items.
|
||||
|
/// </summary>
|
||||
|
void AddToSelection(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Removes the current element from the collection of selected items.
|
||||
|
/// </summary>
|
||||
|
void RemoveFromSelection(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clears any existing selection and then selects the current element.
|
||||
|
/// </summary>
|
||||
|
void Select(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Automation.Peers; |
||||
|
|
||||
|
namespace Avalonia.Automation.Provider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Exposes methods and properties to support access by a UI Automation client to controls
|
||||
|
/// that act as containers for a collection of individual, selectable child items.
|
||||
|
/// </summary>
|
||||
|
public interface ISelectionProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the provider allows more than one child element
|
||||
|
/// to be selected concurrently.
|
||||
|
/// </summary>
|
||||
|
bool CanSelectMultiple { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the provider requires at least one child element
|
||||
|
/// to be selected.
|
||||
|
/// </summary>
|
||||
|
bool IsSelectionRequired { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Retrieves a provider for each child element that is selected.
|
||||
|
/// </summary>
|
||||
|
IReadOnlyList<AutomationPeer> GetSelection(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
namespace Avalonia.Automation.Provider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains values that specify the toggle state of a UI Automation element.
|
||||
|
/// </summary>
|
||||
|
public enum ToggleState |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The UI Automation element isn't selected, checked, marked, or otherwise activated.
|
||||
|
/// </summary>
|
||||
|
Off, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The UI Automation element is selected, checked, marked, or otherwise activated.
|
||||
|
/// </summary>
|
||||
|
On, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The UI Automation element is in an indeterminate state.
|
||||
|
/// </summary>
|
||||
|
Indeterminate, |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes methods and properties to support UI Automation client access to controls that can
|
||||
|
/// cycle through a set of states and maintain a particular state.
|
||||
|
/// </summary>
|
||||
|
public interface IToggleProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the toggle state of the control.
|
||||
|
/// </summary>
|
||||
|
ToggleState ToggleState { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Cycles through the toggle states of a control.
|
||||
|
/// </summary>
|
||||
|
void Toggle(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
namespace Avalonia.Automation.Provider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Exposes methods and properties to support access by a UI Automation client to controls
|
||||
|
/// that have an intrinsic value that does not span a range and that can be represented as
|
||||
|
/// a string.
|
||||
|
/// </summary>
|
||||
|
public interface IValueProvider |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a value that indicates whether the value of a control is read-only.
|
||||
|
/// </summary>
|
||||
|
bool IsReadOnly { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the value of the control.
|
||||
|
/// </summary>
|
||||
|
public string? Value { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the value of a control.
|
||||
|
/// </summary>
|
||||
|
/// <param name="value">
|
||||
|
/// The value to set. The provider is responsible for converting the value to the
|
||||
|
/// appropriate data type.
|
||||
|
/// </param>
|
||||
|
public void SetValue(string? value); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
using Avalonia.Automation.Provider; |
||||
|
|
||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains values used as identifiers by <see cref="IRangeValueProvider"/>.
|
||||
|
/// </summary>
|
||||
|
public static class RangeValuePatternIdentifiers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IRangeValueProvider.IsReadOnly"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty IsReadOnlyProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IRangeValueProvider.Minimum"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty MinimumProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IRangeValueProvider.Maximum"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty MaximumProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IRangeValueProvider.Value"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty ValueProperty { get; } = new AutomationProperty(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,45 @@ |
|||||
|
using Avalonia.Automation.Provider; |
||||
|
|
||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains values used as identifiers by <see cref="IScrollProvider"/>.
|
||||
|
/// </summary>
|
||||
|
public static class ScrollPatternIdentifiers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Specifies that scrolling should not be performed.
|
||||
|
/// </summary>
|
||||
|
public const double NoScroll = -1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IScrollProvider.HorizontallyScrollable"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty HorizontallyScrollableProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IScrollProvider.HorizontalScrollPercent"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty HorizontalScrollPercentProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IScrollProvider.HorizontalViewSize"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty HorizontalViewSizeProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IScrollProvider.VerticallyScrollable"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty VerticallyScrollableProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IScrollProvider.VerticalScrollPercent"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty VerticalScrollPercentProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="IScrollProvider.VerticalViewSize"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty VerticalViewSizeProperty { get; } = new AutomationProperty(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
using Avalonia.Automation.Provider; |
||||
|
|
||||
|
namespace Avalonia.Automation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Contains values used as identifiers by <see cref="ISelectionProvider"/>.
|
||||
|
/// </summary>
|
||||
|
public static class SelectionPatternIdentifiers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="ISelectionProvider.CanSelectMultiple"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty CanSelectMultipleProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies <see cref="ISelectionProvider.IsSelectionRequired"/> automation property.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty IsSelectionRequiredProperty { get; } = new AutomationProperty(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Identifies the property that gets the selected items in a container.
|
||||
|
/// </summary>
|
||||
|
public static AutomationProperty SelectionProperty { get; } = new AutomationProperty(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,160 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Linq; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using Avalonia.Automation; |
||||
|
using Avalonia.Automation.Peers; |
||||
|
using Avalonia.Automation.Provider; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Native.Interop; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
|
namespace Avalonia.Native |
||||
|
{ |
||||
|
internal class AvnAutomationPeer : NativeCallbackBase, IAvnAutomationPeer |
||||
|
{ |
||||
|
private static readonly ConditionalWeakTable<AutomationPeer, AvnAutomationPeer> s_wrappers = new(); |
||||
|
private readonly AutomationPeer _inner; |
||||
|
|
||||
|
private AvnAutomationPeer(AutomationPeer inner) |
||||
|
{ |
||||
|
_inner = inner; |
||||
|
_inner.ChildrenChanged += (_, _) => Node?.ChildrenChanged(); |
||||
|
if (inner is WindowBaseAutomationPeer window) |
||||
|
window.FocusChanged += (_, _) => Node?.FocusChanged(); |
||||
|
} |
||||
|
|
||||
|
~AvnAutomationPeer() => Node?.Dispose(); |
||||
|
|
||||
|
public IAvnAutomationNode? Node { get; private set; } |
||||
|
public IAvnString? AcceleratorKey => _inner.GetAcceleratorKey().ToAvnString(); |
||||
|
public IAvnString? AccessKey => _inner.GetAccessKey().ToAvnString(); |
||||
|
public AvnAutomationControlType AutomationControlType => (AvnAutomationControlType)_inner.GetAutomationControlType(); |
||||
|
public IAvnString? AutomationId => _inner.GetAutomationId().ToAvnString(); |
||||
|
public AvnRect BoundingRectangle => _inner.GetBoundingRectangle().ToAvnRect(); |
||||
|
public IAvnAutomationPeerArray Children => new AvnAutomationPeerArray(_inner.GetChildren()); |
||||
|
public IAvnString ClassName => _inner.GetClassName().ToAvnString(); |
||||
|
public IAvnAutomationPeer? LabeledBy => Wrap(_inner.GetLabeledBy()); |
||||
|
public IAvnString Name => _inner.GetName().ToAvnString(); |
||||
|
public IAvnAutomationPeer? Parent => Wrap(_inner.GetParent()); |
||||
|
|
||||
|
public int HasKeyboardFocus() => _inner.HasKeyboardFocus().AsComBool(); |
||||
|
public int IsContentElement() => _inner.IsContentElement().AsComBool(); |
||||
|
public int IsControlElement() => _inner.IsControlElement().AsComBool(); |
||||
|
public int IsEnabled() => _inner.IsEnabled().AsComBool(); |
||||
|
public int IsKeyboardFocusable() => _inner.IsKeyboardFocusable().AsComBool(); |
||||
|
public void SetFocus() => _inner.SetFocus(); |
||||
|
public int ShowContextMenu() => _inner.ShowContextMenu().AsComBool(); |
||||
|
|
||||
|
public IAvnAutomationPeer? RootPeer |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
var peer = _inner; |
||||
|
var parent = peer.GetParent(); |
||||
|
|
||||
|
while (peer is not IRootProvider && parent is not null) |
||||
|
{ |
||||
|
peer = parent; |
||||
|
parent = peer.GetParent(); |
||||
|
} |
||||
|
|
||||
|
return Wrap(peer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void SetNode(IAvnAutomationNode node) |
||||
|
{ |
||||
|
if (Node is not null) |
||||
|
throw new InvalidOperationException("The AvnAutomationPeer already has a node."); |
||||
|
Node = node; |
||||
|
} |
||||
|
|
||||
|
public int IsRootProvider() => (_inner is IRootProvider).AsComBool(); |
||||
|
|
||||
|
public IAvnWindowBase RootProvider_GetWindow() |
||||
|
{ |
||||
|
var window = (WindowBase)((ControlAutomationPeer)_inner).Owner; |
||||
|
return ((WindowBaseImpl)window.PlatformImpl!).Native; |
||||
|
} |
||||
|
|
||||
|
public IAvnAutomationPeer? RootProvider_GetFocus() => Wrap(((IRootProvider)_inner).GetFocus()); |
||||
|
|
||||
|
public IAvnAutomationPeer? RootProvider_GetPeerFromPoint(AvnPoint point) |
||||
|
{ |
||||
|
var result = ((IRootProvider)_inner).GetPeerFromPoint(point.ToAvaloniaPoint()); |
||||
|
|
||||
|
if (result is null) |
||||
|
return null; |
||||
|
|
||||
|
// The OSX accessibility APIs expect non-ignored elements when hit-testing.
|
||||
|
while (!result.IsControlElement()) |
||||
|
{ |
||||
|
var parent = result.GetParent(); |
||||
|
|
||||
|
if (parent is not null) |
||||
|
result = parent; |
||||
|
else |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return Wrap(result); |
||||
|
} |
||||
|
|
||||
|
public int IsExpandCollapseProvider() => (_inner is IExpandCollapseProvider).AsComBool(); |
||||
|
|
||||
|
public int ExpandCollapseProvider_GetIsExpanded() => ((IExpandCollapseProvider)_inner).ExpandCollapseState switch |
||||
|
{ |
||||
|
ExpandCollapseState.Expanded => 1, |
||||
|
ExpandCollapseState.PartiallyExpanded => 1, |
||||
|
_ => 0, |
||||
|
}; |
||||
|
|
||||
|
public int ExpandCollapseProvider_GetShowsMenu() => ((IExpandCollapseProvider)_inner).ShowsMenu.AsComBool(); |
||||
|
public void ExpandCollapseProvider_Expand() => ((IExpandCollapseProvider)_inner).Expand(); |
||||
|
public void ExpandCollapseProvider_Collapse() => ((IExpandCollapseProvider)_inner).Collapse(); |
||||
|
|
||||
|
public int IsInvokeProvider() => (_inner is IInvokeProvider).AsComBool(); |
||||
|
public void InvokeProvider_Invoke() => ((IInvokeProvider)_inner).Invoke(); |
||||
|
|
||||
|
public int IsRangeValueProvider() => (_inner is IRangeValueProvider).AsComBool(); |
||||
|
public double RangeValueProvider_GetValue() => ((IRangeValueProvider)_inner).Value; |
||||
|
public double RangeValueProvider_GetMinimum() => ((IRangeValueProvider)_inner).Minimum; |
||||
|
public double RangeValueProvider_GetMaximum() => ((IRangeValueProvider)_inner).Maximum; |
||||
|
public double RangeValueProvider_GetSmallChange() => ((IRangeValueProvider)_inner).SmallChange; |
||||
|
public double RangeValueProvider_GetLargeChange() => ((IRangeValueProvider)_inner).LargeChange; |
||||
|
public void RangeValueProvider_SetValue(double value) => ((IRangeValueProvider)_inner).SetValue(value); |
||||
|
|
||||
|
public int IsSelectionItemProvider() => (_inner is ISelectionItemProvider).AsComBool(); |
||||
|
public int SelectionItemProvider_IsSelected() => ((ISelectionItemProvider)_inner).IsSelected.AsComBool(); |
||||
|
|
||||
|
public int IsToggleProvider() => (_inner is IToggleProvider).AsComBool(); |
||||
|
public int ToggleProvider_GetToggleState() => (int)((IToggleProvider)_inner).ToggleState; |
||||
|
public void ToggleProvider_Toggle() => ((IToggleProvider)_inner).Toggle(); |
||||
|
|
||||
|
public int IsValueProvider() => (_inner is IValueProvider).AsComBool(); |
||||
|
public IAvnString ValueProvider_GetValue() => ((IValueProvider)_inner).Value.ToAvnString(); |
||||
|
public void ValueProvider_SetValue(string value) => ((IValueProvider)_inner).SetValue(value); |
||||
|
|
||||
|
[return: NotNullIfNotNull("peer")] |
||||
|
public static AvnAutomationPeer? Wrap(AutomationPeer? peer) |
||||
|
{ |
||||
|
return peer is null ? null : s_wrappers.GetValue(peer, x => new(peer)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal class AvnAutomationPeerArray : NativeCallbackBase, IAvnAutomationPeerArray |
||||
|
{ |
||||
|
private readonly AvnAutomationPeer[] _items; |
||||
|
|
||||
|
public AvnAutomationPeerArray(IReadOnlyList<AutomationPeer> items) |
||||
|
{ |
||||
|
_items = items.Select(x => AvnAutomationPeer.Wrap(x)).ToArray(); |
||||
|
} |
||||
|
|
||||
|
public uint Count => (uint)_items.Length; |
||||
|
public IAvnAutomationPeer Get(uint index) => _items[index]; |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue