|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Windows;
using System.Windows.Automation.Provider;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using MS.Internal;
using MS.Internal.Data;
using MS.Win32;
namespace System.Windows.Automation.Peers
{
///<summary>
/// Earlier this class was returning the default value for all properties when there is no wrapper/when it is virtualized,
/// now it will throw ElementNotAvailableException (leaving some exceptions, like properties supported by container to find elements)
/// to notify the client that the full Element does not exist yet. Client may decide to use VirtualizedItemPattern to realize the full item
///</summary>
public abstract class ItemAutomationPeer : AutomationPeer, IVirtualizedItemProvider
{
///
protected ItemAutomationPeer(object item, ItemsControlAutomationPeer itemsControlAutomationPeer): base()
{
Item = item;
_itemsControlAutomationPeer = itemsControlAutomationPeer;
}
///
internal override bool AncestorsInvalid
{
get { return base.AncestorsInvalid; }
set
{
base.AncestorsInvalid = value;
if (value)
return;
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
{
wrapperPeer.AncestorsInvalid = false;
}
}
}
///
override public object GetPattern(PatternInterface patternInterface)
{
if (patternInterface == PatternInterface.VirtualizedItem)
{
if(VirtualizedItemPatternIdentifiers.Pattern != null)
{
if(GetWrapperPeer() == null)
return this;
else
{
// ItemsControlAutomationPeer can be null in case of TreeViewItems when parent TreeViewItem is also virtualized
// If the Item is in Automation Tree we consider it has Realized and need not return VirtualizeItem pattern.
if(ItemsControlAutomationPeer != null && !IsItemInAutomationTree())
{
return this;
}
if(ItemsControlAutomationPeer == null)
return this;
}
}
return null;
}
else if(patternInterface == PatternInterface.SynchronizedInput)
{
UIElementAutomationPeer peer = GetWrapperPeer() as UIElementAutomationPeer;
if(peer != null)
{
return peer.GetPattern(patternInterface);
}
}
return null;
}
internal UIElement GetWrapper()
{
UIElement wrapper = null;
ItemsControlAutomationPeer itemsControlAutomationPeer = ItemsControlAutomationPeer;
if (itemsControlAutomationPeer != null)
{
ItemsControl owner = (ItemsControl)(itemsControlAutomationPeer.Owner);
if (owner != null)
{
object item = RawItem;
if (item != DependencyProperty.UnsetValue)
{
if (((MS.Internal.Controls.IGeneratorHost)owner).IsItemItsOwnContainer(item))
wrapper = item as UIElement;
else
wrapper = owner.ItemContainerGenerator.ContainerFromItem(item) as UIElement;
}
}
}
return wrapper;
}
virtual internal AutomationPeer GetWrapperPeer()
{
AutomationPeer wrapperPeer = null;
UIElement wrapper = GetWrapper();
if(wrapper != null)
{
wrapperPeer = UIElementAutomationPeer.CreatePeerForElement(wrapper);
if(wrapperPeer == null) //fall back to default peer if there is no specific one
{
if(wrapper is FrameworkElement)
wrapperPeer = new FrameworkElementAutomationPeer((FrameworkElement)wrapper);
else
wrapperPeer = new UIElementAutomationPeer(wrapper);
}
}
return wrapperPeer;
}
/// <summary>
internal void ThrowElementNotAvailableException()
{
// To avoid the situation on legacy systems which may not have new unmanaged core. this check with old unmanaged core
// avoids throwing exception and provide older behavior returning default values for items which are virtualized rather than throwing exception.
if (VirtualizedItemPatternIdentifiers.Pattern != null && !(this is GridViewItemAutomationPeer) && !IsItemInAutomationTree())
throw new ElementNotAvailableException(SR.VirtualizedElement);
}
private bool IsItemInAutomationTree()
{
AutomationPeer parent = this.GetParent();
if(this.Index != -1 && parent != null && parent.Children != null && this.Index < parent.Children.Count && parent.Children[this.Index] == this)
return true;
else return false;
}
///
override internal bool IgnoreUpdatePeer()
{
// Ignore UpdatePeer if the we're no longer in the automation tree.
// There's no need to update such a peer, as it no longer
// participates in automation. And UpdatePeer actually throws exceptions
// in some cases.
if (!IsItemInAutomationTree())
{
return true;
}
return base.IgnoreUpdatePeer();
}
override internal bool IsDataItemAutomationPeer()
{
return true;
}
override internal void AddToParentProxyWeakRefCache()
{
ItemsControlAutomationPeer itemsControlAutomationPeer = ItemsControlAutomationPeer;
if(itemsControlAutomationPeer != null)
{
itemsControlAutomationPeer.AddProxyToWeakRefStorage(this.ElementProxyWeakReference, this);
}
}
/// <summary>
override internal Rect GetVisibleBoundingRectCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
{
return wrapperPeer.GetVisibleBoundingRectCore();
}
return GetBoundingRectangle();
}
///
override protected string GetItemTypeCore()
{
return string.Empty;
}
///
protected override List<AutomationPeer> GetChildrenCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
{
// The children needs to be updated before GetChildren call as ChildrenValid flag would already be true and GetChildren call won't update the children list.
wrapperPeer.ForceEnsureChildren();
List<AutomationPeer> children = wrapperPeer.GetChildren();
return children;
}
return null;
}
///
protected override Rect GetBoundingRectangleCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
{
return wrapperPeer.GetBoundingRectangle();
}
else
ThrowElementNotAvailableException();
return new Rect();
}
///
protected override bool IsOffscreenCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.IsOffscreen();
else
ThrowElementNotAvailableException();
return true;
}
///
protected override AutomationOrientation GetOrientationCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.GetOrientation();
else
ThrowElementNotAvailableException();
return AutomationOrientation.None;
}
///
override protected AutomationHeadingLevel GetHeadingLevelCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
AutomationHeadingLevel headingLevel = AutomationHeadingLevel.None;
if(wrapperPeer != null)
{
headingLevel = wrapperPeer.GetHeadingLevel();
}
else
{
ThrowElementNotAvailableException();
}
return headingLevel;
}
/// <summary>
/// Gets the position of an item within a set.
/// </summary>
/// <remarks>
/// If <see cref="AutomationProperties.PositionInSetProperty"/> hasn't been set
/// this method will calculate the position of an item based on its parent ItemsControl,
/// if the ItemsControl is grouping the position will be relative to the group containing this item.
/// </remarks>
/// <returns>
/// The value of <see cref="AutomationProperties.PositionInSetProperty"/> if it has been set, or it's position relative to the parent ItemsControl or GroupItem.
/// </returns>
protected override int GetPositionInSetCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
{
int position = wrapperPeer.GetPositionInSet();
if (position == AutomationProperties.AutomationPositionInSetDefault)
{
ItemsControl parentItemsControl = (ItemsControl)ItemsControlAutomationPeer.Owner;
position = GetPositionInSetFromItemsControl(parentItemsControl, Item);
}
return position;
}
else
{
ThrowElementNotAvailableException();
}
return AutomationProperties.AutomationPositionInSetDefault;
}
/// <summary>
/// Gets the size of a set that contains this item.
/// </summary>
/// <remarks>
/// If <see cref="AutomationProperties.SizeOfSetProperty"/> hasn't been set
/// this method will calculate the size of the set containing this item based on its parent ItemsControl,
/// if the ItemsControl is grouping the value will be representative of the group containing this item.
/// </remarks>
/// <returns>
/// The value of <see cref="AutomationProperties.SizeOfSetProperty"/> if it has been set, or the size of the parent ItemsControl or GroupItem.
/// </returns>
protected override int GetSizeOfSetCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
{
int size = wrapperPeer.GetSizeOfSet();
if (size == AutomationProperties.AutomationSizeOfSetDefault)
{
ItemsControl parentItemsControl = (ItemsControl)ItemsControlAutomationPeer.Owner;
size = GetSizeOfSetFromItemsControl(parentItemsControl, Item);
}
return size;
}
else
{
ThrowElementNotAvailableException();
}
return AutomationProperties.AutomationSizeOfSetDefault;
}
internal static int GetPositionInSetFromItemsControl(ItemsControl itemsControl, object item)
{
int position = AutomationProperties.AutomationPositionInSetDefault;
ItemCollection itemCollection = itemsControl.Items;
position = itemCollection.IndexOf(item);
if (itemsControl.IsGrouping)
{
int sizeOfGroup;
position = FindPositionInGroup(itemCollection.Groups, position, out sizeOfGroup);
}
return position + 1;
}
internal static int GetSizeOfSetFromItemsControl(ItemsControl itemsControl, object item)
{
int size = AutomationProperties.AutomationSizeOfSetDefault;
ItemCollection itemCollection = itemsControl.Items;
if (itemsControl.IsGrouping)
{
int position = itemCollection.IndexOf(item);
FindPositionInGroup(itemCollection.Groups, position, out size);
}
else
{
size = itemCollection.Count;
}
return size;
}
/// <summary>
/// Based on the position of an item in the owner's item collection determine which group the item belongs to
/// and return the relative position, also provide the size of the group
/// </summary>
/// <param name="collection">The top-level collection of groups</param>
/// <param name="position">The position of the item in the flattened item collection</param>
/// <param name="sizeOfGroup">out parameter to return the size of the group we found</param>
/// <returns>The position of the item relative to the found group</returns>
private static int FindPositionInGroup(ReadOnlyObservableCollection<object> collection, int position, out int sizeOfGroup)
{
CollectionViewGroupInternal currentGroup = null;
ReadOnlyObservableCollection<object> newCollection = null;
sizeOfGroup = AutomationProperties.AutomationSizeOfSetDefault;
do
{
newCollection = null;
foreach (CollectionViewGroupInternal group in collection)
{
if (position < group.ItemCount)
{
currentGroup = group;
if (currentGroup.IsBottomLevel)
{
newCollection = null;
sizeOfGroup = group.ItemCount;
}
else
{
newCollection = currentGroup.Items;
}
break;
}
else
{
position -= group.ItemCount;
}
}
} while ((collection = newCollection) != null);
return position;
}
///
protected override string GetItemStatusCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.GetItemStatus();
else
ThrowElementNotAvailableException();
return string.Empty;
}
///
protected override bool IsRequiredForFormCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.IsRequiredForForm();
else
ThrowElementNotAvailableException();
return false;
}
///
protected override bool IsKeyboardFocusableCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.IsKeyboardFocusable();
else
ThrowElementNotAvailableException();
return false;
}
///
protected override bool HasKeyboardFocusCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.HasKeyboardFocus();
else
ThrowElementNotAvailableException();
return false;
}
///
protected override bool IsEnabledCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.IsEnabled();
else
ThrowElementNotAvailableException();
return false;
}
///
protected override bool IsDialogCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.IsDialog();
else
ThrowElementNotAvailableException();
return false;
}
///
protected override bool IsPasswordCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.IsPassword();
else
ThrowElementNotAvailableException();
return false;
}
///
protected override string GetAutomationIdCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
string id = null;
object item;
if (wrapperPeer != null)
{
id = wrapperPeer.GetAutomationId();
}
else if ((item = Item) != null)
{
using (RecyclableWrapper recyclableWrapper = ItemsControlAutomationPeer.GetRecyclableWrapperPeer(item))
{
id = recyclableWrapper.Peer.GetAutomationId();
}
}
return id;
}
///
protected override string GetNameCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
string name = null;
object item = Item;
if (wrapperPeer != null)
{
name = wrapperPeer.GetName();
}
// see https://github.com/dotnet/wpf/issues/122
else if (item != null && ItemsControlAutomationPeer is { } itemsControlAutomationPeer)
{
using (RecyclableWrapper recyclableWrapper = itemsControlAutomationPeer.GetRecyclableWrapperPeer(item))
{
name = recyclableWrapper.Peer.GetName();
}
}
if (string.IsNullOrEmpty(name) && item != null)
{
// For FE we can't use ToString as that provides extraneous information than just the plain text
FrameworkElement fe = item as FrameworkElement;
if(fe != null)
name = fe.GetPlainText();
if(string.IsNullOrEmpty(name))
name = item.ToString();
}
return name;
}
///
protected override bool IsContentElementCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.IsContentElement();
return true;
}
///
protected override bool IsControlElementCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.IsControlElement();
return true;
}
///
protected override AutomationPeer GetLabeledByCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.GetLabeledBy();
else
ThrowElementNotAvailableException();
return null;
}
///
protected override AutomationLiveSetting GetLiveSettingCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.GetLiveSetting();
else
ThrowElementNotAvailableException();
return AutomationLiveSetting.Off;
}
///
protected override string GetHelpTextCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.GetHelpText();
else
ThrowElementNotAvailableException();
return string.Empty;
}
///
protected override string GetAcceleratorKeyCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.GetAcceleratorKey();
else
ThrowElementNotAvailableException();
return string.Empty;
}
///
protected override string GetAccessKeyCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.GetAccessKey();
else
ThrowElementNotAvailableException();
return string.Empty;
}
///
protected override Point GetClickablePointCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
return wrapperPeer.GetClickablePoint();
else
ThrowElementNotAvailableException();
return new Point(double.NaN, double.NaN);
}
///
protected override void SetFocusCore()
{
AutomationPeer wrapperPeer = GetWrapperPeer();
if (wrapperPeer != null)
wrapperPeer.SetFocus();
else
ThrowElementNotAvailableException();
}
virtual internal ItemsControlAutomationPeer GetItemsControlAutomationPeer()
{
return _itemsControlAutomationPeer;
}
///
public object Item
{
get
{
ItemWeakReference iwr = _item as ItemWeakReference;
return (iwr != null) ? iwr.Target : _item;
}
private set
{
if (value != null && !value.GetType().IsValueType &&
!FrameworkAppContextSwitches.ItemAutomationPeerKeepsItsItemAlive)
{
_item = new ItemWeakReference(value);
}
else
{
_item = value;
}
}
}
private object RawItem
{
get
{
ItemWeakReference iwr = _item as ItemWeakReference;
if (iwr != null)
{
object item = iwr.Target;
return (item == null) ? DependencyProperty.UnsetValue : item;
}
else
{
return _item;
}
}
}
// Rebuilding the "item" children of an ItemsControlAP or GroupItemAP re-uses
// ItemAutomationPeers for items that already had peers. Usually this happens
// when the items are the same (the same object), but it can also happen
// when the items are different objects but are equal in the Object.Equals sense.
// In the latter case, we need to update the weak reference to point to
// the new item, as the old item has a different lifetime.
internal void ReuseForItem(object item)
{
System.Diagnostics.Debug.Assert(Object.Equals(item, Item), "ItemPeer reuse for an unequal item is not supported");
ItemWeakReference iwr = _item as ItemWeakReference;
if (iwr != null)
{
if (!Object.ReferenceEquals(item, iwr.Target))
{
iwr.Target = item;
}
}
else
{
_item = item;
}
}
///
public ItemsControlAutomationPeer ItemsControlAutomationPeer
{
get
{
return GetItemsControlAutomationPeer();
}
internal set
{
_itemsControlAutomationPeer = value;
}
}
///
void IVirtualizedItemProvider.Realize()
{
RealizeCore();
}
virtual internal void RealizeCore()
{
ItemsControlAutomationPeer itemsControlAutomationPeer = ItemsControlAutomationPeer;
if (itemsControlAutomationPeer != null)
{
ItemsControl parent = itemsControlAutomationPeer.Owner as ItemsControl;
if (parent != null)
{
if (parent.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
if (AccessibilitySwitches.UseNetFx472CompatibleAccessibilityFeatures)
{
// Please note that this action must happen before the OnBringItemIntoView call because
// that is a call that synchronously flushes out layout and we want these realized peers
// cached before the UpdateSubtree kicks in OnLayoutUpdated.
if (VirtualizingPanel.GetIsVirtualizingWhenGrouping(parent))
{
itemsControlAutomationPeer.RecentlyRealizedPeers.Add(this);
}
}
parent.OnBringItemIntoView(Item);
}
else
{
// The items aren't generated, try at a later time
Dispatcher.BeginInvoke(DispatcherPriority.Loaded,
(DispatcherOperationCallback)delegate(object arg)
{
if (AccessibilitySwitches.UseNetFx472CompatibleAccessibilityFeatures)
{
// Please note that this action must happen before the OnBringItemIntoView call because
// that is a call that synchronously flushes out layout and we want these realized peers
// cached before the UpdateSubtree kicks in OnLayoutUpdated.
if (VirtualizingPanel.GetIsVirtualizingWhenGrouping(parent))
{
itemsControlAutomationPeer.RecentlyRealizedPeers.Add(this);
}
}
parent.OnBringItemIntoView(arg);
return null;
}, Item);
}
}
}
}
private object _item; // for value-types: item; for reference-types: IWR(item)
private ItemsControlAutomationPeer _itemsControlAutomationPeer;
// a weak reference that is distinguishable from System.WeakReference
private class ItemWeakReference : WeakReference
{
public ItemWeakReference(object o)
: base(o)
{}
}
}
}
|