|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using System.Drawing;
using Windows.Win32.System.Com;
using Windows.Win32.System.Variant;
using Windows.Win32.UI.Accessibility;
namespace System.Windows.Forms;
public abstract partial class DataGridViewCell
{
protected class DataGridViewCellAccessibleObject : AccessibleObject
{
private int[] _runtimeId = null!; // Used by UIAutomation
private AccessibleObject? _child;
private DataGridViewCell? _owner;
public DataGridViewCellAccessibleObject()
{
}
public DataGridViewCellAccessibleObject(DataGridViewCell? owner)
{
_owner = owner;
}
public override Rectangle Bounds => GetAccessibleObjectBounds(GetAccessibleObjectParent());
public override string DefaultAction
{
get
{
if (Owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
return !Owner.ReadOnly ? SR.DataGridView_AccCellDefaultAction : string.Empty;
}
}
internal override bool CanGetDefaultActionInternal => false;
public override string? Name
{
get
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
if (_owner.OwningColumn is null || _owner.OwningRow is null)
{
return string.Empty;
}
int rowIndex = _owner.DataGridView is null
? -1
: _owner.DataGridView.Rows.GetVisibleIndex(_owner.OwningRow) + RowStartIndex;
string name = string.Format(SR.DataGridView_AccDataGridViewCellName, _owner.OwningColumn.HeaderText, rowIndex).Trim();
if (_owner.OwningColumn.SortMode != DataGridViewColumnSortMode.NotSortable)
{
DataGridViewCell dataGridViewCell = _owner;
DataGridView? dataGridView = dataGridViewCell.DataGridView;
if (dataGridView is not null &&
dataGridViewCell.OwningColumn is not null &&
dataGridViewCell.OwningColumn == dataGridView.SortedColumn)
{
name += ", " + (dataGridView.SortOrder == SortOrder.Ascending
? SR.SortedAscendingAccessibleStatus
: SR.SortedDescendingAccessibleStatus);
}
else
{
name += $", {SR.NotSortedAccessibleStatus}";
}
}
return name;
}
}
internal override bool CanGetNameInternal => false;
public DataGridViewCell? Owner
{
get => _owner;
set
{
if (_owner is not null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerAlreadySet);
}
_owner = value;
}
}
public override AccessibleObject? Parent => ParentPrivate;
private AccessibleObject? ParentPrivate
{
get
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
return _owner.OwningRow?.AccessibilityObject;
}
}
public override AccessibleRole Role => AccessibleRole.Cell;
private static int RowStartIndex => AppContextSwitches.DataGridViewUIAStartRowCountAtZero ? 0 : 1;
public override AccessibleStates State
{
get
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
AccessibleStates state = AccessibleStates.Selectable | AccessibleStates.Focusable;
if (_owner.DataGridView is not null && _owner == _owner.DataGridView.CurrentCell)
{
state |= AccessibleStates.Focused;
}
if (_owner.Selected)
{
state |= AccessibleStates.Selected;
}
if (_owner.ReadOnly)
{
state |= AccessibleStates.ReadOnly;
}
if (_owner.DataGridView?.IsHandleCreated != true)
{
return state;
}
Rectangle cellBounds;
if (_owner.OwningColumn is not null && _owner.OwningRow is not null)
{
cellBounds = _owner.DataGridView.GetCellDisplayRectangle(_owner.OwningColumn.Index, _owner.OwningRow.Index, cutOverflow: false);
}
else if (_owner.OwningRow is not null)
{
cellBounds = _owner.DataGridView.GetCellDisplayRectangle(-1, _owner.OwningRow.Index, cutOverflow: false);
}
else if (_owner.OwningColumn is not null)
{
cellBounds = _owner.DataGridView.GetCellDisplayRectangle(_owner.OwningColumn.Index, -1, cutOverflow: false);
}
else
{
cellBounds = _owner.DataGridView.GetCellDisplayRectangle(-1, -1, cutOverflow: false);
}
if (!cellBounds.IntersectsWith(_owner.DataGridView.ClientRectangle))
{
state |= AccessibleStates.Offscreen;
}
return state;
}
}
public override string? Value
{
get
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
object? formattedValue = _owner.FormattedValue;
string? formattedValueAsString = formattedValue as string;
if (formattedValue is null || (formattedValueAsString is not null && string.IsNullOrEmpty(formattedValueAsString)))
{
return SR.DataGridView_AccNullValue;
}
else if (formattedValueAsString is not null)
{
return formattedValueAsString;
}
else if (_owner.OwningColumn is not null)
{
TypeConverter? converter = _owner.FormattedValueTypeConverter;
if (converter is not null && converter.CanConvertTo(typeof(string)))
{
return converter.ConvertToString(formattedValue);
}
else
{
return formattedValue.ToString();
}
}
else
{
return string.Empty;
}
}
set
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
if (_owner is DataGridViewHeaderCell || _owner.ReadOnly || _owner.DataGridView is null || _owner.OwningRow is null)
{
return;
}
if (_owner.DataGridView.IsCurrentCellInEditMode)
{
// EndEdit before setting the accessible object value.
// This way the value being edited is validated.
_owner.DataGridView.EndEdit();
}
DataGridViewCellStyle dataGridViewCellStyle = _owner.InheritedStyle;
// Format string "True" to boolean True.
object? formattedValue = _owner.GetFormattedValue(
value,
_owner.OwningRow.Index,
ref dataGridViewCellStyle,
valueTypeConverter: null,
formattedValueTypeConverter: null,
DataGridViewDataErrorContexts.Formatting);
// Parse the formatted value and push it into the back end.
_owner.Value = _owner.ParseFormattedValue(
formattedValue,
dataGridViewCellStyle,
formattedValueTypeConverter: null,
valueTypeConverter: null);
}
}
internal override bool CanGetValueInternal => false;
internal override bool CanSetValueInternal => false;
public override void DoDefaultAction()
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
DataGridViewCell dataGridViewCell = _owner;
DataGridView? dataGridView = dataGridViewCell.DataGridView;
if (dataGridViewCell is DataGridViewHeaderCell || dataGridView?.IsHandleCreated != true)
{
return;
}
if (dataGridViewCell.RowIndex == -1)
{
throw new InvalidOperationException(SR.DataGridView_InvalidOperationOnSharedCell);
}
Select(AccessibleSelection.TakeFocus | AccessibleSelection.TakeSelection);
Debug.Assert(dataGridView.CurrentCell == dataGridViewCell, "the result of selecting the cell should have made this cell the current cell");
if (dataGridViewCell.ReadOnly)
{
// don't edit if the cell is read only
return;
}
if (dataGridViewCell.EditType is not null)
{
if (dataGridView.InBeginEdit || dataGridView.InEndEdit)
{
// don't enter or exit editing mode if the control
// is in the middle of doing that already.
return;
}
if (dataGridView.IsCurrentCellInEditMode)
{
// stop editing
dataGridView.EndEdit();
}
else if (dataGridView.EditMode != DataGridViewEditMode.EditProgrammatically)
{
// start editing
dataGridView.BeginEdit(selectAll: true);
}
}
}
internal Rectangle GetAccessibleObjectBounds(AccessibleObject? parentAccObject)
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
if (parentAccObject is null
|| _owner.DataGridView is null
|| !_owner.DataGridView.IsHandleCreated
|| _owner.OwningColumn is null)
{
return Rectangle.Empty;
}
Rectangle rowRect = parentAccObject.Bounds;
Rectangle cellRect = rowRect;
Rectangle columnRect = _owner.DataGridView.RectangleToScreen(
_owner.DataGridView.GetColumnDisplayRectangle(_owner.ColumnIndex, cutOverflow: false));
int cellRight = columnRect.Left + columnRect.Width;
int cellLeft = columnRect.Left;
int rightToLeftRowHeadersWidth = 0;
int leftToRightRowHeadersWidth = 0;
if (_owner.DataGridView.RowHeadersVisible)
{
if (_owner.DataGridView.RightToLeft == RightToLeft.Yes)
{
rightToLeftRowHeadersWidth = _owner.DataGridView.RowHeadersWidth;
}
else
{
leftToRightRowHeadersWidth = _owner.DataGridView.RowHeadersWidth;
}
}
if (cellLeft < rowRect.Left + leftToRightRowHeadersWidth)
{
cellLeft = rowRect.Left + leftToRightRowHeadersWidth;
}
cellRect.X = cellLeft;
if (cellRight > rowRect.Right - rightToLeftRowHeadersWidth)
{
cellRight = rowRect.Right - rightToLeftRowHeadersWidth;
}
if ((cellRight - cellLeft) >= 0)
{
cellRect.Width = cellRight - cellLeft;
}
else
{
cellRect.Width = 0;
}
return cellRect;
}
private AccessibleObject? GetAccessibleObjectParent()
{
// If this is one of our types, use the shortcut provided by ParentPrivate property.
// Otherwise, use the Parent property.
if (_owner is DataGridViewButtonCell
or DataGridViewCheckBoxCell
or DataGridViewComboBoxCell
or DataGridViewImageCell
or DataGridViewLinkCell
or DataGridViewTextBoxCell)
{
return ParentPrivate;
}
else
{
return Parent;
}
}
public override AccessibleObject? GetChild(int index)
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
if (_owner.DataGridView is not null &&
_owner.DataGridView.EditingControl is not null &&
_owner.DataGridView.IsCurrentCellInEditMode &&
_owner.DataGridView.CurrentCell == _owner &&
index == 0)
{
return _owner.DataGridView.EditingControl.AccessibilityObject;
}
else
{
return null;
}
}
public override int GetChildCount()
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
if (_owner.DataGridView is not null &&
_owner.DataGridView.EditingControl is not null &&
_owner.DataGridView.IsCurrentCellInEditMode &&
_owner.DataGridView.CurrentCell == _owner)
{
return 1;
}
else
{
return 0;
}
}
public override AccessibleObject? GetFocused() => null;
public override AccessibleObject? GetSelected() => null;
public override AccessibleObject? Navigate(AccessibleNavigation navigationDirection)
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
if (_owner.DataGridView?.IsHandleCreated != true || _owner.OwningColumn is null || _owner.OwningRow is null)
{
return null;
}
switch (navigationDirection)
{
case AccessibleNavigation.Right:
return _owner.DataGridView.RightToLeft == RightToLeft.No
? NavigateForward(wrapAround: true)
: NavigateBackward(wrapAround: true);
case AccessibleNavigation.Next:
return NavigateForward(wrapAround: false);
case AccessibleNavigation.Left:
return _owner.DataGridView.RightToLeft == RightToLeft.No
? NavigateBackward(wrapAround: true)
: NavigateForward(wrapAround: true);
case AccessibleNavigation.Previous:
return NavigateBackward(wrapAround: false);
case AccessibleNavigation.Up:
if (_owner.OwningRow.Index == _owner.DataGridView.Rows.GetFirstRow(DataGridViewElementStates.Visible))
{
return _owner.DataGridView.ColumnHeadersVisible
? _owner.OwningColumn.HeaderCell.AccessibilityObject // Return the column header accessible object
: null;
}
else
{
int previousVisibleRow = _owner.DataGridView.Rows.GetPreviousRow(_owner.OwningRow.Index, DataGridViewElementStates.Visible);
return _owner.DataGridView.Rows[previousVisibleRow].Cells[_owner.OwningColumn.Index].AccessibilityObject;
}
case AccessibleNavigation.Down:
if (_owner.OwningRow.Index == _owner.DataGridView.Rows.GetLastRow(DataGridViewElementStates.Visible))
{
return null;
}
else
{
int nextVisibleRow = _owner.DataGridView.Rows.GetNextRow(_owner.OwningRow.Index, DataGridViewElementStates.Visible);
return _owner.DataGridView.Rows[nextVisibleRow].Cells[_owner.OwningColumn.Index].AccessibilityObject;
}
default:
return null;
}
}
private AccessibleObject? NavigateBackward(bool wrapAround)
{
Debug.Assert(_owner is not null);
Debug.Assert(_owner.DataGridView is not null);
Debug.Assert(_owner.OwningColumn is not null);
Debug.Assert(_owner.OwningRow is not null);
if (_owner.OwningColumn == _owner.DataGridView.Columns.GetFirstColumn(DataGridViewElementStates.Visible))
{
if (wrapAround)
{
// Return the last accessible object in the previous row
AccessibleObject? previousRow = _owner.OwningRow.AccessibilityObject.Navigate(AccessibleNavigation.Previous);
if (previousRow is null)
{
return null;
}
int childCount = previousRow.GetChildCount();
return childCount > 0 ? previousRow.GetChild(childCount - 1) : null;
}
else
{
// return the row header cell if the row headers are visible.
return _owner.DataGridView.RowHeadersVisible ? _owner.OwningRow.AccessibilityObject.GetChild(0) : null;
}
}
else
{
int previousVisibleColumnIndex = _owner.DataGridView.Columns.GetPreviousColumn(
_owner.OwningColumn,
DataGridViewElementStates.Visible,
DataGridViewElementStates.None)!.Index;
return _owner.OwningRow.Cells[previousVisibleColumnIndex].AccessibilityObject;
}
}
private AccessibleObject? NavigateForward(bool wrapAround)
{
Debug.Assert(_owner is not null);
Debug.Assert(_owner.DataGridView is not null);
Debug.Assert(_owner.OwningColumn is not null);
Debug.Assert(_owner.OwningRow is not null);
if (_owner.OwningColumn == _owner.DataGridView.Columns.GetLastColumn(
DataGridViewElementStates.Visible,
DataGridViewElementStates.None))
{
if (wrapAround)
{
// Return the first cell in the next visible row.
AccessibleObject? nextRow = _owner.OwningRow.AccessibilityObject.Navigate(AccessibleNavigation.Next);
if (nextRow is not null && nextRow.GetChildCount() > 0)
{
return _owner.DataGridView.RowHeadersVisible ? nextRow.GetChild(1) : nextRow.GetChild(0);
}
}
return null;
}
else
{
int nextVisibleColumnIndex = _owner.DataGridView.Columns.GetNextColumn(
_owner.OwningColumn,
DataGridViewElementStates.Visible,
DataGridViewElementStates.None)!.Index;
return _owner.OwningRow.Cells[nextVisibleColumnIndex].AccessibilityObject;
}
}
public override void Select(AccessibleSelection flags)
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
if (_owner.DataGridView?.IsHandleCreated != true)
{
return;
}
if ((flags & AccessibleSelection.TakeFocus) == AccessibleSelection.TakeFocus)
{
_owner.DataGridView.Focus();
}
if ((flags & AccessibleSelection.TakeSelection) == AccessibleSelection.TakeSelection)
{
_owner.Selected = true;
_owner.DataGridView.CurrentCell = _owner; // Do not change old selection
}
if ((flags & AccessibleSelection.AddSelection) == AccessibleSelection.AddSelection)
{
// it seems that in any circumstances a cell can become selected
_owner.Selected = true;
}
if ((flags & AccessibleSelection.RemoveSelection) == AccessibleSelection.RemoveSelection &&
(flags & (AccessibleSelection.AddSelection | AccessibleSelection.TakeSelection)) == 0)
{
_owner.Selected = false;
}
}
/// <summary>
/// Sets the detachable child accessible object which may be added or removed to/from hierarchy nodes.
/// </summary>
/// <param name="child">The child accessible object.</param>
internal override void SetDetachableChild(AccessibleObject? child)
{
_child = child;
}
internal override void SetFocus()
{
if (_owner?.DataGridView?.IsHandleCreated != true)
{
return;
}
base.SetFocus();
RaiseAutomationEvent(UIA_EVENT_ID.UIA_AutomationFocusChangedEventId);
}
internal override int[] RuntimeId => _runtimeId ??=
[
RuntimeIDFirstItem,
GetHashCode()
];
private protected override string AutomationId
{
get => string.Concat(RuntimeId);
}
internal override bool IsIAccessibleExSupported() => true;
#region IRawElementProviderFragment Implementation
internal override IRawElementProviderFragmentRoot.Interface? FragmentRoot => _owner?.DataGridView?.AccessibilityObject;
internal override IRawElementProviderFragment.Interface? FragmentNavigate(NavigateDirection direction)
{
if (_owner is null)
{
throw new InvalidOperationException(SR.DataGridViewCellAccessibleObject_OwnerNotSet);
}
if (_owner.DataGridView?.IsHandleCreated != true || _owner.OwningColumn is null || _owner.OwningRow is null)
{
return null;
}
switch (direction)
{
case NavigateDirection.NavigateDirection_Parent:
return _owner.OwningRow.AccessibilityObject;
case NavigateDirection.NavigateDirection_NextSibling:
return NavigateForward(wrapAround: false);
case NavigateDirection.NavigateDirection_PreviousSibling:
return NavigateBackward(wrapAround: false);
case NavigateDirection.NavigateDirection_FirstChild:
case NavigateDirection.NavigateDirection_LastChild:
if (_owner.DataGridView.CurrentCell == _owner &&
_owner.DataGridView.IsCurrentCellInEditMode &&
_owner.DataGridView.EditingControl is not null)
{
return _child;
}
break;
default:
return null;
}
return null;
}
#endregion
#region IRawElementProviderSimple Implementation
internal override unsafe VARIANT GetPropertyValue(UIA_PROPERTY_ID propertyID)
=> propertyID switch
{
UIA_PROPERTY_ID.UIA_ControlTypePropertyId => (VARIANT)(int)UIA_CONTROLTYPE_ID.UIA_DataItemControlTypeId,
UIA_PROPERTY_ID.UIA_GridItemContainingGridPropertyId
=> (VARIANT)ComHelpers.GetComPointer<IUnknown>(_owner?.DataGridView?.AccessibilityObject),
UIA_PROPERTY_ID.UIA_HasKeyboardFocusPropertyId => (VARIANT)State.HasFlag(AccessibleStates.Focused), // Announce the cell when focusing.
UIA_PROPERTY_ID.UIA_IsEnabledPropertyId => (VARIANT)(_owner?.DataGridView?.Enabled ?? false),
UIA_PROPERTY_ID.UIA_IsKeyboardFocusablePropertyId => (VARIANT)State.HasFlag(AccessibleStates.Focusable),
_ => base.GetPropertyValue(propertyID),
};
internal override bool IsPatternSupported(UIA_PATTERN_ID patternId)
{
if (patternId is UIA_PATTERN_ID.UIA_LegacyIAccessiblePatternId or
UIA_PATTERN_ID.UIA_InvokePatternId or
UIA_PATTERN_ID.UIA_ValuePatternId)
{
return true;
}
if ((patternId == UIA_PATTERN_ID.UIA_TableItemPatternId || patternId == UIA_PATTERN_ID.UIA_GridItemPatternId)
// We don't want to implement patterns for header cells
&& _owner?.ColumnIndex != -1 && _owner?.RowIndex != -1)
{
return true;
}
return base.IsPatternSupported(patternId);
}
#endregion
internal override IRawElementProviderSimple.Interface[]? GetRowHeaderItems()
{
if (_owner is { OwningRow.HasHeaderCell: true, DataGridView: { IsHandleCreated: true, RowHeadersVisible: true } })
{
return [_owner.OwningRow.HeaderCell.AccessibilityObject];
}
return null;
}
internal override IRawElementProviderSimple.Interface[]? GetColumnHeaderItems()
{
if (_owner is { OwningColumn.HasHeaderCell: true, DataGridView: { IsHandleCreated: true, ColumnHeadersVisible: true } })
{
return [_owner.OwningColumn.HeaderCell.AccessibilityObject];
}
return null;
}
internal override int Row
=> _owner?.OwningRow?.Visible is true && _owner.DataGridView is not null
? _owner.DataGridView.Rows.GetVisibleIndex(_owner.OwningRow)
: -1;
internal override int Column
=> _owner?.OwningColumn?.Visible is true && _owner.DataGridView is not null
? _owner.DataGridView.Columns.GetVisibleIndex(_owner.OwningColumn)
: -1;
internal override IRawElementProviderSimple.Interface? ContainingGrid => _owner?.DataGridView?.AccessibilityObject;
internal override bool IsReadOnly => _owner?.ReadOnly ?? false;
}
}
|