| File: System\Windows\Automation\Peers\DataGridItemAutomationPeer.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections; using System.ComponentModel; using System.Windows.Automation.Provider; using System.Windows.Controls; using System.Windows.Data; using MS.Internal.Automation; namespace System.Windows.Automation.Peers { /// <summary> /// AutomationPeer for an item in a DataGrid /// This automation peer correspond to a row data item which may not have a visual container /// </summary> public sealed class DataGridItemAutomationPeer : ItemAutomationPeer, IInvokeProvider, IScrollItemProvider, ISelectionItemProvider, ISelectionProvider, IItemContainerProvider { #region Constructors /// <summary> /// AutomationPeer for an item in a DataGrid /// </summary> public DataGridItemAutomationPeer(object item, DataGridAutomationPeer dataGridPeer): base(item, dataGridPeer) { ArgumentNullException.ThrowIfNull(item); ArgumentNullException.ThrowIfNull(dataGridPeer); _dataGridAutomationPeer = dataGridPeer; } #endregion #region AutomationPeer Overrides ///// protected override AutomationControlType GetAutomationControlTypeCore() { return AutomationControlType.DataItem; } /// protected override List<AutomationPeer> GetChildrenCore() { AutomationPeer wrapperPeer = GetWrapperPeer(); if (wrapperPeer != null) { // We need to update children manually since wrapperPeer is not in the Automation Tree // When containers are recycled the visual (DataGridRow) will point to a new item. ForceEnsureChildren will just refresh children of this peer, // unlike UpdateSubtree which would raise property change events and recursively updates entire subtree. // WrapperPeer's children are the peers for DataGridRowHeader, DataGridCells and DataGridRowDetails. wrapperPeer.ForceEnsureChildren(); List<AutomationPeer> children = wrapperPeer.GetChildren(); return children; } return GetCellItemPeers(); } /// protected override string GetClassNameCore() { AutomationPeer wrapperPeer = GetWrapperPeer(); if (wrapperPeer != null) { return wrapperPeer.GetClassName(); } else { ThrowElementNotAvailableException(); } return string.Empty; } /// public override object GetPattern(PatternInterface patternInterface) { switch (patternInterface) { case PatternInterface.Invoke: if (!this.OwningDataGrid.IsReadOnly) { return this; } break; case PatternInterface.ScrollItem: case PatternInterface.Selection: case PatternInterface.ItemContainer: return this; case PatternInterface.SelectionItem: if (IsRowSelectionUnit) { return this; } break; } return base.GetPattern(patternInterface); } protected override AutomationPeer GetPeerFromPointCore(Point point) { if (!IsOffscreen()) { AutomationPeer rowHeaderAutomationPeer = RowHeaderAutomationPeer; if (rowHeaderAutomationPeer != null) { // Check DataGridRowHeader first AutomationPeer found = rowHeaderAutomationPeer.GetPeerFromPoint(point); if (found != null) { return found; } } } return base.GetPeerFromPointCore(point); } #endregion #region IItemContainerProvider IRawElementProviderSimple IItemContainerProvider.FindItemByProperty(IRawElementProviderSimple startAfter, int propertyId, object value) { ResetChildrenCache(); // Checks if propertyId is valid else throws ArgumentException to notify it as invalid argument is being passed if (propertyId != 0) { if (!SelectorAutomationPeer.IsPropertySupportedByControlForFindItemInternal(propertyId)) { throw new ArgumentException(SR.PropertyNotSupported); } } IList<DataGridColumn> columns = OwningDataGrid.Columns; if (columns != null && columns.Count > 0) { DataGridCellItemAutomationPeer startAfterItem = null; if (startAfter != null) { // get the peer corresponding to this provider startAfterItem = PeerFromProvider(startAfter) as DataGridCellItemAutomationPeer; } // startIndex refers to the index of the item just after startAfterItem int startIndex = 0; if (startAfterItem != null) { if (startAfterItem.Column == null) { throw new InvalidOperationException(SR.InavalidStartItem); } // To find the index of the item in items collection which occurs // immidiately after startAfterItem.Item startIndex = columns.IndexOf(startAfterItem.Column) + 1; if (startIndex == 0 || startIndex == columns.Count) return null; } if (propertyId == 0 && startIndex < columns.Count) { return (ProviderFromPeer(GetOrCreateCellItemPeer(columns[startIndex]))); } DataGridCellItemAutomationPeer currentItemPeer; object currentValue = null; for (int i = startIndex; i < columns.Count; i++) { currentItemPeer = GetOrCreateCellItemPeer(columns[i]); if (currentItemPeer == null) continue; try { currentValue = SelectorAutomationPeer.GetSupportedPropertyValueInternal(currentItemPeer, propertyId); } catch (Exception ex) { if (ex is ElementNotAvailableException) continue; } if (value == null || currentValue == null) { // Accept null as value corresponding to the property if it finds an item with null as the value of corresponding property else ignore. if (currentValue == null && value == null) return (ProviderFromPeer(currentItemPeer)); else continue; } // Match is found within the specified criterion of search if (value.Equals(currentValue)) return (ProviderFromPeer(currentItemPeer)); } } return null; } #endregion IItemContainerProvider #region IInvokeProvider // Invoking DataGrid item should commit the item if it is in edit mode // or BeginEdit if item is not in edit mode void IInvokeProvider.Invoke() { EnsureEnabled(); object item = Item; AutomationPeer wrapperPeer = GetWrapperPeer(); if (wrapperPeer == null) { this.OwningDataGrid.ScrollIntoView(item); } bool success = false; UIElement owningRow = GetWrapper(); if (owningRow != null) { IEditableCollectionView iecv = (IEditableCollectionView)this.OwningDataGrid.Items; if (iecv.CurrentEditItem == item) { success = this.OwningDataGrid.CommitEdit(); } else { if (this.OwningDataGrid.Columns.Count > 0) { DataGridCell cell = this.OwningDataGrid.TryFindCell(item, this.OwningDataGrid.Columns[0]); if (cell != null) { this.OwningDataGrid.UnselectAll(); cell.Focus(); success = this.OwningDataGrid.BeginEdit(); } } } } // Invoke on a NewItemPlaceholder row creates a new item. // BeginEdit on a NewItemPlaceholder row returns false. if (!success && !IsNewItemPlaceholder) { throw new InvalidOperationException(SR.DataGrid_AutomationInvokeFailed); } } #endregion #region IScrollItemProvider void IScrollItemProvider.ScrollIntoView() { this.OwningDataGrid.ScrollIntoView(Item); } #endregion #region ISelectionItemProvider bool ISelectionItemProvider.IsSelected { get { return this.OwningDataGrid.SelectedItems.Contains(Item); } } IRawElementProviderSimple ISelectionItemProvider.SelectionContainer { get { return ProviderFromPeer(_dataGridAutomationPeer); } } void ISelectionItemProvider.AddToSelection() { if (!IsRowSelectionUnit) { throw new InvalidOperationException(SR.DataGridRow_CannotSelectRowWhenCells); } // If item is already selected - do nothing object item = Item; if (this.OwningDataGrid.SelectedItems.Contains(item)) { return; } EnsureEnabled(); if (this.OwningDataGrid.SelectionMode == DataGridSelectionMode.Single && this.OwningDataGrid.SelectedItems.Count > 0) { throw new InvalidOperationException(); } if (this.OwningDataGrid.Items.Contains(item)) { this.OwningDataGrid.SelectedItems.Add(item); } } void ISelectionItemProvider.RemoveFromSelection() { if (!IsRowSelectionUnit) { throw new InvalidOperationException(SR.DataGridRow_CannotSelectRowWhenCells); } EnsureEnabled(); object item = Item; if (this.OwningDataGrid.SelectedItems.Contains(item)) { this.OwningDataGrid.SelectedItems.Remove(item); } } void ISelectionItemProvider.Select() { if (!IsRowSelectionUnit) { throw new InvalidOperationException(SR.DataGridRow_CannotSelectRowWhenCells); } EnsureEnabled(); this.OwningDataGrid.SelectedItem = Item; } #endregion #region ISelectionProvider bool ISelectionProvider.CanSelectMultiple { get { return this.OwningDataGrid.SelectionMode == DataGridSelectionMode.Extended; } } bool ISelectionProvider.IsSelectionRequired { get { return false; } } IRawElementProviderSimple[] ISelectionProvider.GetSelection() { DataGrid dataGrid = this.OwningDataGrid; if (dataGrid == null) { return null; } int rowIndex = dataGrid.Items.IndexOf(Item); // If row has selection if (rowIndex > -1 && dataGrid.SelectedCellsInternal.Intersects(rowIndex)) { List<IRawElementProviderSimple> selectedProviders = new List<IRawElementProviderSimple>(); for (int i = 0; i < this.OwningDataGrid.Columns.Count; i++) { // cell is selected if (dataGrid.SelectedCellsInternal.Contains(rowIndex, i)) { DataGridColumn column = dataGrid.ColumnFromDisplayIndex(i); DataGridCellItemAutomationPeer peer = GetOrCreateCellItemPeer(column); if (peer != null) { selectedProviders.Add(ProviderFromPeer(peer)); } } } if (selectedProviders.Count > 0) { return selectedProviders.ToArray(); } } return null; } #endregion #region Private Methods /// <summary> /// Realized Columns only /// </summary> /// <returns></returns> internal List<AutomationPeer> GetCellItemPeers() { List<AutomationPeer> children = null; ItemPeersStorage<DataGridCellItemAutomationPeer> newChildren = new ItemPeersStorage<DataGridCellItemAutomationPeer>(); IList childItems = null; bool usingItemsHost = false; DataGridRow row = GetWrapper() as DataGridRow; if (row != null) { if (row.CellsPresenter != null) { Panel itemHost = row.CellsPresenter.ItemsHost; if (itemHost != null) { childItems = itemHost.Children; usingItemsHost = true; } } } if (!usingItemsHost) { childItems = OwningDataGrid.Columns; } if (childItems != null) { children = new List<AutomationPeer>(childItems.Count); foreach (object childItem in childItems) { DataGridColumn column = null; if (usingItemsHost) { column = (childItem as DataGridCell).Column; } else { column = childItem as DataGridColumn; } if (column != null) { DataGridCellItemAutomationPeer peer = GetOrCreateCellItemPeer(column,/*addParentInfo*/ false ); children.Add(peer); newChildren[column] = peer; } } } // Cache children for reuse CellItemPeers = newChildren; return children; } internal DataGridCellItemAutomationPeer GetOrCreateCellItemPeer(DataGridColumn column) { return GetOrCreateCellItemPeer(column, /*addParentInfo*/ true); } /// <summary> /// It returns the CellItemAutomationPeer if it exist corresponding to the item otherwise it creates /// one and adds the Handle and parent info by calling AddParentInfo. /// </summary> /// <param name="column"></param> /// <param name="addParentInfo">only required when creating peers for virtualized cells</param> /// <returns></returns> private DataGridCellItemAutomationPeer GetOrCreateCellItemPeer(DataGridColumn column, bool addParentInfo) { // try to reuse old peer if it exists either in Current AT or in WeakRefStorage of Peers being sent to Client DataGridCellItemAutomationPeer peer = CellItemPeers[column]; if (peer == null) { peer = GetPeerFromWeakRefStorage(column); if (peer != null && !addParentInfo) { // As cached peer is getting used it must be invalidated. addParentInfo check ensures that call is coming from GetChildrenCore peer.AncestorsInvalid = false; peer.ChildrenValid = false; } } if (peer == null) { peer = new DataGridCellItemAutomationPeer(Item, column); if (addParentInfo && peer != null) { peer.TrySetParentInfo(this); } } //perform hookup so the events sourced from wrapper peer are fired as if from the data item AutomationPeer wrapperPeer = peer.OwningCellPeer; wrapperPeer?.EventsSource = peer; return peer; } // Provides Peer if exist in Weak Reference Storage private DataGridCellItemAutomationPeer GetPeerFromWeakRefStorage(object column) { DataGridCellItemAutomationPeer returnPeer = null; WeakReference weakRefEP = WeakRefElementProxyStorage[column]; if (weakRefEP != null) { ElementProxy provider = weakRefEP.Target as ElementProxy; if (provider != null) { returnPeer = PeerFromProvider(provider as IRawElementProviderSimple) as DataGridCellItemAutomationPeer; if (returnPeer == null) WeakRefElementProxyStorage.Remove(column); } else WeakRefElementProxyStorage.Remove(column); } return returnPeer; } // Called by DataGridCellItemAutomationPeer internal void AddProxyToWeakRefStorage(WeakReference wr, DataGridCellItemAutomationPeer cellItemPeer) { IList<DataGridColumn> columns = OwningDataGrid.Columns; if (columns != null && columns.Contains(cellItemPeer.Column)) { if (GetPeerFromWeakRefStorage(cellItemPeer.Column) == null) WeakRefElementProxyStorage[cellItemPeer.Column] = wr; } } private void EnsureEnabled() { if (!_dataGridAutomationPeer.IsEnabled()) { throw new ElementNotEnabledException(); } } #endregion #region Private Properties private bool IsRowSelectionUnit { get { return (this.OwningDataGrid != null && (this.OwningDataGrid.SelectionUnit == DataGridSelectionUnit.FullRow || this.OwningDataGrid.SelectionUnit == DataGridSelectionUnit.CellOrRowHeader)); } } private bool IsNewItemPlaceholder { get { object item = Item; return (item == CollectionView.NewItemPlaceholder) || (item == DataGrid.NewItemPlaceholder); } } internal AutomationPeer RowHeaderAutomationPeer { get { DataGridRowAutomationPeer owningRowPeer = GetWrapperPeer() as DataGridRowAutomationPeer; return owningRowPeer?.RowHeaderAutomationPeer; } } private DataGrid OwningDataGrid { get { DataGridAutomationPeer gridPeer = _dataGridAutomationPeer as DataGridAutomationPeer; return (DataGrid)gridPeer.Owner; } } /// <summary> /// Used to cache realized peers. We donot store references to virtualized peers. /// </summary> private ItemPeersStorage<DataGridCellItemAutomationPeer> CellItemPeers { get { return _dataChildren; } set { _dataChildren = value; } } private ItemPeersStorage<WeakReference> WeakRefElementProxyStorage { get { return _weakRefElementProxyStorage; } } #endregion #region Data private AutomationPeer _dataGridAutomationPeer; private ItemPeersStorage<DataGridCellItemAutomationPeer> _dataChildren = new ItemPeersStorage<DataGridCellItemAutomationPeer>(); private ItemPeersStorage<WeakReference> _weakRefElementProxyStorage = new ItemPeersStorage<WeakReference>(); #endregion } }