diff --git a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs index 5464458358a..61c8a21d6a1 100644 --- a/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs +++ b/src/System.Windows.Forms.Primitives/src/System/LocalAppContextSwitches/LocalAppContextSwitches.cs @@ -20,12 +20,14 @@ internal static partial class LocalAppContextSwitches internal const string ServicePointManagerCheckCrlSwitchName = "System.Windows.Forms.ServicePointManagerCheckCrl"; internal const string TrackBarModernRenderingSwitchName = "System.Windows.Forms.TrackBarModernRendering"; private const string DoNotCatchUnhandledExceptionsSwitchName = "System.Windows.Forms.DoNotCatchUnhandledExceptions"; + internal const string DataGridViewUIAStartRowCountAtZeroSwitchName = "System.Windows.Forms.DataGridViewUIAStartRowCountAtZero"; private static int s_scaleTopLevelFormMinMaxSizeForDpi; private static int s_anchorLayoutV2; private static int s_servicePointManagerCheckCrl; private static int s_trackBarModernRendering; private static int s_doNotCatchUnhandledExceptions; + private static int s_dataGridViewUIAStartRowCountAtZero; private static FrameworkName? s_targetFrameworkName; @@ -151,4 +153,12 @@ public static bool ServicePointManagerCheckCrl [MethodImpl(MethodImplOptions.AggressiveInlining)] get => GetCachedSwitchValue(ServicePointManagerCheckCrlSwitchName, ref s_servicePointManagerCheckCrl); } + + public static bool DataGridViewUIAStartRowCountAtZero + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => GetCachedSwitchValue(DataGridViewUIAStartRowCountAtZeroSwitchName, ref s_dataGridViewUIAStartRowCountAtZero); + } + + internal static void SetDataGridViewUIAStartRowCountAtZero(bool value) => s_dataGridViewUIAStartRowCountAtZero = value ? 1 : 0; } diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewCell.DataGridViewCellAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewCell.DataGridViewCellAccessibleObject.cs index 10977b3de4d..dd9274e74b7 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewCell.DataGridViewCellAccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewCell.DataGridViewCellAccessibleObject.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Drawing; +using System.Windows.Forms.Primitives; using static Interop; namespace System.Windows.Forms; @@ -55,9 +56,9 @@ public override string? Name int rowIndex = _owner.DataGridView is null ? -1 - : _owner.DataGridView.Rows.GetVisibleIndex(_owner.OwningRow); + : _owner.DataGridView.Rows.GetVisibleIndex(_owner.OwningRow) + RowStartIndex; - string name = string.Format(SR.DataGridView_AccDataGridViewCellName, _owner.OwningColumn.HeaderText, rowIndex); + string name = string.Format(SR.DataGridView_AccDataGridViewCellName, _owner.OwningColumn.HeaderText, rowIndex).Trim(); if (_owner.OwningColumn.SortMode != DataGridViewColumnSortMode.NotSortable) { @@ -113,6 +114,8 @@ private AccessibleObject? ParentPrivate public override AccessibleRole Role => AccessibleRole.Cell; + private static int RowStartIndex => LocalAppContextSwitches.DataGridViewUIAStartRowCountAtZero ? 0 : 1; + public override AccessibleStates State { get diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewRow.DataGridViewRowAccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewRow.DataGridViewRowAccessibleObject.cs index e99742fb6a5..9c7e00f1450 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewRow.DataGridViewRowAccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/DataGridViewRow.DataGridViewRowAccessibleObject.cs @@ -4,6 +4,7 @@ using System.Drawing; using System.Globalization; using System.Text; +using System.Windows.Forms.Primitives; using static Interop; namespace System.Windows.Forms; @@ -94,7 +95,7 @@ public override string Name } int index = _owningDataGridViewRow is { Visible: true, DataGridView: { } } - ? _owningDataGridViewRow.DataGridView.Rows.GetVisibleIndex(_owningDataGridViewRow) + ? _owningDataGridViewRow.DataGridView.Rows.GetVisibleIndex(_owningDataGridViewRow) + RowStartIndex : -1; return string.Format(SR.DataGridView_AccRowName, index.ToString(CultureInfo.CurrentCulture)); @@ -132,6 +133,8 @@ private AccessibleObject? ParentPrivate public override AccessibleRole Role => AccessibleRole.Row; + private static int RowStartIndex => LocalAppContextSwitches.DataGridViewUIAStartRowCountAtZero ? 0 : 1; + internal override int[] RuntimeId => _runtimeId ??= new int[] { diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewCellAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewCellAccessibleObjectTests.cs index 4400d0e22f0..6f57931eb7d 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewCellAccessibleObjectTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewCellAccessibleObjectTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; +using System.Windows.Forms.Primitives; using Moq; using Moq.Protected; using static Interop; @@ -207,6 +208,7 @@ public void DataGridViewCellAccessibleObject_Name_ReturnExpected_IfDataGridViewN Assert.Equal(expected, accessibleObject.Name); } + // Whether UIA row indexing is 1-based or 0-based, is controlled by the DataGridViewUIAStartRowCountAtZero switch [WinFormsFact] public void DataGridViewCellAccessibleObject_Name_ReturnExpected() { @@ -218,12 +220,13 @@ public void DataGridViewCellAccessibleObject_Name_ReturnExpected() dataGridView.Rows.Add("3"); AccessibleObject accessibleObject = dataGridView.Rows[2].Cells[0].AccessibilityObject; - string expected = string.Format(SR.DataGridView_AccDataGridViewCellName, column.HeaderText, 2); + string expected = string.Format(SR.DataGridView_AccDataGridViewCellName, column.HeaderText, 3); Assert.Equal(expected, accessibleObject.Name); Assert.False(dataGridView.IsHandleCreated); } + // Whether UIA row indexing is 1-based or 0-based, is controlled by the DataGridViewUIAStartRowCountAtZero switch [WinFormsFact] public void DataGridViewCellAccessibleObject_Name_ReturnExpected_IfOneRowHidden() { @@ -236,12 +239,13 @@ public void DataGridViewCellAccessibleObject_Name_ReturnExpected_IfOneRowHidden( dataGridView.Rows[0].Visible = false; AccessibleObject accessibleObject = dataGridView.Rows[2].Cells[0].AccessibilityObject; - string expected = string.Format(SR.DataGridView_AccDataGridViewCellName, column.HeaderText, 1); + string expected = string.Format(SR.DataGridView_AccDataGridViewCellName, column.HeaderText, 2); Assert.Equal(expected, accessibleObject.Name); Assert.False(dataGridView.IsHandleCreated); } + // Whether UIA row indexing is 1-based or 0-based, is controlled by the DataGridViewUIAStartRowCountAtZero switch [WinFormsFact] public void DataGridViewCellAccessibleObject_Name_ReturnExpected_IfTwoRowsHidden() { @@ -255,7 +259,7 @@ public void DataGridViewCellAccessibleObject_Name_ReturnExpected_IfTwoRowsHidden dataGridView.Rows[1].Visible = false; AccessibleObject accessibleObject = dataGridView.Rows[2].Cells[0].AccessibilityObject; - string expected = string.Format(SR.DataGridView_AccDataGridViewCellName, column.HeaderText, 0); + string expected = string.Format(SR.DataGridView_AccDataGridViewCellName, column.HeaderText, 1); Assert.Equal(expected, accessibleObject.Name); Assert.False(dataGridView.IsHandleCreated); @@ -1432,6 +1436,23 @@ private DataGridView CreateDataGridView(int columnCount, bool createControl = tr return dataGridView; } + // Unit test for https://github.com/dotnet/winforms/issues/7154 + [WinFormsFact] + public void DataGridView_SwitchConfigured_AdjustsCellRowStartIndices() + { + LocalAppContextSwitches.SetDataGridViewUIAStartRowCountAtZero(true); + + using DataGridView dataGridView = new(); + dataGridView.Columns.Add(new DataGridViewTextBoxColumn()); + dataGridView.Rows.Add(new DataGridViewRow()); + + Assert.Equal($"{string.Format(SR.DataGridView_AccRowName, 0)}, Not sorted.", dataGridView.Rows[0].Cells[0].AccessibilityObject.Name); + + LocalAppContextSwitches.SetDataGridViewUIAStartRowCountAtZero(false); + + Assert.Equal($"{string.Format(SR.DataGridView_AccRowName, 1)}, Not sorted.", dataGridView.Rows[0].Cells[0].AccessibilityObject.Name); + } + private class SubDataGridViewCell : DataGridViewCell { } diff --git a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewRowAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewRowAccessibleObjectTests.cs index 7528c223886..861e8d05f8b 100644 --- a/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewRowAccessibleObjectTests.cs +++ b/src/System.Windows.Forms/tests/UnitTests/System/Windows/Forms/DataGridViewRowAccessibleObjectTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; +using System.Windows.Forms.Primitives; using static Interop; namespace System.Windows.Forms.Tests; @@ -71,6 +72,7 @@ public void DataGridViewRowAccessibleObject_Name_Get_ReturnsExpected_IfDataGridV Assert.Equal(string.Format(SR.DataGridView_AccRowName, -1), accessibilityObject.Name); } + // Whether UIA row indexing is 1-based or 0-based, is controlled by the DataGridViewUIAStartRowCountAtZero switch [Fact] public void DataGridViewRowAccessibleObject_Name_Get_ReturnsExpected() { @@ -84,12 +86,13 @@ public void DataGridViewRowAccessibleObject_Name_Get_ReturnsExpected() AccessibleObject accessibleObject2 = dataGridView.Rows[1].AccessibilityObject; AccessibleObject accessibleObject3 = dataGridView.Rows[2].AccessibilityObject; - Assert.Equal(string.Format(SR.DataGridView_AccRowName, 0), accessibleObject1.Name); - Assert.Equal(string.Format(SR.DataGridView_AccRowName, 1), accessibleObject2.Name); - Assert.Equal(string.Format(SR.DataGridView_AccRowName, 2), accessibleObject3.Name); + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 1), accessibleObject1.Name); + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 2), accessibleObject2.Name); + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 3), accessibleObject3.Name); Assert.False(dataGridView.IsHandleCreated); } + // Whether UIA row indexing is 1-based or 0-based, is controlled by the DataGridViewUIAStartRowCountAtZero switch [Fact] public void DataGridViewRowAccessibleObject_Name_Get_ReturnsExpected_IfFirstRowHidden() { @@ -105,11 +108,12 @@ public void DataGridViewRowAccessibleObject_Name_Get_ReturnsExpected_IfFirstRowH AccessibleObject accessibleObject3 = dataGridView.Rows[2].AccessibilityObject; Assert.Equal(string.Format(SR.DataGridView_AccRowName, -1), accessibleObject1.Name); - Assert.Equal(string.Format(SR.DataGridView_AccRowName, 0), accessibleObject2.Name); - Assert.Equal(string.Format(SR.DataGridView_AccRowName, 1), accessibleObject3.Name); + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 1), accessibleObject2.Name); + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 2), accessibleObject3.Name); Assert.False(dataGridView.IsHandleCreated); } + // Whether UIA row indexing is 1-based or 0-based, is controlled by the DataGridViewUIAStartRowCountAtZero switch [Fact] public void DataGridViewRowAccessibleObject_Name_Get_ReturnsExpected_IfSecondRowHidden() { @@ -124,12 +128,13 @@ public void DataGridViewRowAccessibleObject_Name_Get_ReturnsExpected_IfSecondRow AccessibleObject accessibleObject2 = dataGridView.Rows[1].AccessibilityObject; AccessibleObject accessibleObject3 = dataGridView.Rows[2].AccessibilityObject; - Assert.Equal(string.Format(SR.DataGridView_AccRowName, 0), accessibleObject1.Name); + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 1), accessibleObject1.Name); Assert.Equal(string.Format(SR.DataGridView_AccRowName, -1), accessibleObject2.Name); - Assert.Equal(string.Format(SR.DataGridView_AccRowName, 1), accessibleObject3.Name); + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 2), accessibleObject3.Name); Assert.False(dataGridView.IsHandleCreated); } + // Whether UIA row indexing is 1-based or 0-based, is controlled by the DataGridViewUIAStartRowCountAtZero switch [Fact] public void DataGridViewRowAccessibleObject_Name_Get_ReturnsExpected_IfLastRowHidden() { @@ -144,8 +149,8 @@ public void DataGridViewRowAccessibleObject_Name_Get_ReturnsExpected_IfLastRowHi AccessibleObject accessibleObject2 = dataGridView.Rows[1].AccessibilityObject; AccessibleObject accessibleObject3 = dataGridView.Rows[2].AccessibilityObject; - Assert.Equal(string.Format(SR.DataGridView_AccRowName, 0), accessibleObject1.Name); - Assert.Equal(string.Format(SR.DataGridView_AccRowName, 1), accessibleObject2.Name); + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 1), accessibleObject1.Name); + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 2), accessibleObject2.Name); Assert.Equal(string.Format(SR.DataGridView_AccRowName, -1), accessibleObject3.Name); Assert.False(dataGridView.IsHandleCreated); } @@ -2379,6 +2384,23 @@ public void DataGridViewRowAccessibleObject_GetPropertyValue_ValueValuePropertyI Assert.False(dataGridView.IsHandleCreated); } + // Unit test for https://github.com/dotnet/winforms/issues/7154 + [WinFormsFact] + public void DataGridView_SwitchConfigured_AdjustsRowStartIndices() + { + LocalAppContextSwitches.SetDataGridViewUIAStartRowCountAtZero(true); + + using DataGridView dataGridView = new(); + dataGridView.Columns.Add(new DataGridViewTextBoxColumn()); + dataGridView.Rows.Add(new DataGridViewRow()); + + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 0), dataGridView.Rows[0].AccessibilityObject.Name); + + LocalAppContextSwitches.SetDataGridViewUIAStartRowCountAtZero(false); + + Assert.Equal(string.Format(SR.DataGridView_AccRowName, 1), dataGridView.Rows[0].AccessibilityObject.Name); + } + private class SubDataGridViewCell : DataGridViewCell { }