diff --git a/src/Common/src/NativeMethods.cs b/src/Common/src/NativeMethods.cs index c502bb7f140..180a3ee3157 100644 --- a/src/Common/src/NativeMethods.cs +++ b/src/Common/src/NativeMethods.cs @@ -696,10 +696,12 @@ public const int LB_SETSEL = 0x0185, LB_SETCURSEL = 0x0186, LB_GETSEL = 0x0187, + LB_SETCARETINDEX = 0x019E, LB_GETCARETINDEX = 0x019F, LB_GETCURSEL = 0x0188, LB_GETTEXT = 0x0189, LB_GETTEXTLEN = 0x018A, + LB_GETCOUNT = 0x018B, LB_GETTOPINDEX = 0x018E, LB_FINDSTRING = 0x018F, LB_GETSELCOUNT = 0x0190, diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs b/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs index a7ae29b1cee..c6151d274da 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/AccessibleObject.cs @@ -809,12 +809,17 @@ int UnsafeNativeMethods.IServiceProvider.QueryService(ref Guid service, ref Guid return hr; } - object UnsafeNativeMethods.IAccessibleEx.GetObjectForChild(int idChild) + internal virtual object GetObjectForChildInternal(int idChild) { // No need to implement this for patterns and properties return null; } + object UnsafeNativeMethods.IAccessibleEx.GetObjectForChild(int idChild) + { + return GetObjectForChildInternal(idChild); + } + // This method is never called int UnsafeNativeMethods.IAccessibleEx.GetIAccessiblePair(out object ppAcc, out int pidChild) { diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/ListBox.cs b/src/System.Windows.Forms/src/System/Windows/Forms/ListBox.cs index 44c1d553876..b586f81d0a2 100644 --- a/src/System.Windows.Forms/src/System/Windows/Forms/ListBox.cs +++ b/src/System.Windows.Forms/src/System/Windows/Forms/ListBox.cs @@ -23,6 +23,7 @@ namespace System.Windows.Forms { using System.Drawing; using Microsoft.Win32; using System.Text; + using System.Runtime.Serialization; /// /// @@ -2260,6 +2261,8 @@ public ItemArray(ListControl listControl) { this.listControl = listControl; } + internal Entry[] Entries { get { return entries; } } + /// /// The version of this array. This number changes with each /// change to the item list. @@ -2578,7 +2581,7 @@ int IComparer.Compare(object item1, object item2) { /// /// This is a single entry in our item array. /// - private class Entry { + internal class Entry { public object item; public int state; @@ -2696,6 +2699,9 @@ public ObjectCollection(ListBox owner, object[] value) { this.AddRange(value); } + internal ItemArray EntryArray { get { return items; } } + + /// /// /// Retrieves the number of items. /// @@ -3890,6 +3896,162 @@ public void Remove(object value) { } } } + + + /// + /// + protected override AccessibleObject CreateAccessibilityInstance() + { + return new ListBoxAccessibleObject(this); + } + + /// + /// ListBox control accessible object with UI Automation provider functionality. + /// This inherits from the base ListBoxExAccessibleObject and ListBoxAccessibleObject + /// to have all base functionality. + /// + [ComVisible(true)] + internal class ListBoxAccessibleObject : ControlAccessibleObject + { + private readonly ListBox _owningListBox; + private readonly ListBoxItemAccessibleObjectCollection _itemAccessibleObjects; + + /// + /// Initializes new instance of ListBoxAccessibleObject. + /// + /// The owning ListBox control. + public ListBoxAccessibleObject(ListBox owningListBox) : base(owningListBox) + { + _owningListBox = owningListBox; + _itemAccessibleObjects = new ListBoxItemAccessibleObjectCollection(owningListBox); + } + + internal override object GetObjectForChildInternal(int idChild) + { + return _itemAccessibleObjects[_owningListBox.Items.EntryArray.Entries[idChild - 1]]; + } + + internal override bool IsIAccessibleExSupported() + { + if (_owningListBox != null) + { + return true; + } + + return base.IsIAccessibleExSupported(); + } + } + + internal class ListBoxItemAccessibleObjectCollection : Hashtable + { + private readonly ListBox _owningListBoxBox; + private readonly ObjectIDGenerator _idGenerator = new ObjectIDGenerator(); + + public ListBoxItemAccessibleObjectCollection(ListBox owningListBoxBox) + { + _owningListBoxBox = owningListBoxBox; + } + + public override object this[object key] + { + get + { + int id = GetId(key); + if (!ContainsKey(id)) + { + var itemAccessibleObject = new ListBoxItemAccessibleObject(_owningListBoxBox, key); + base[id] = itemAccessibleObject; + } + + return base[id]; + } + + set + { + int id = GetId(key); + base[id] = value; + } + } + + public int GetId(object item) + { + return unchecked((int)_idGenerator.GetId(item, out var _)); + } + } + + /// + /// ListBox control accessible object with UI Automation provider functionality. + /// This inherits from the base ListBoxExAccessibleObject and ListBoxAccessibleObject + /// to have all base functionality. + /// + [ComVisible(true)] + internal class ListBoxItemAccessibleObject : AccessibleObject + { + private readonly ListBox _owningListBox; + private readonly object _item; + + public ListBoxItemAccessibleObject(ListBox owningListBox, object item) + { + _owningListBox = owningListBox; + _item = item; + } + + internal override void ScrollIntoView() + { + var itemId = Array.IndexOf(_owningListBox.Items.EntryArray.Entries, _item); + + if (_owningListBox.SelectedIndex == -1) //no item selected + { + _owningListBox.SendMessage(NativeMethods.LB_SETCARETINDEX, itemId, 0); + + return; + } + + int firstVisibleIndex = _owningListBox.SendMessage(NativeMethods.LB_GETTOPINDEX, 0, 0).ToInt32(); + + if (itemId < firstVisibleIndex) + { + _owningListBox.SendMessage(NativeMethods.LB_SETTOPINDEX, itemId, 0); + + return; + } + + int itemsHeightSum = 0; + int visibleItemsCount = 0; + int listBoxHeight = _owningListBox.Bounds.Height; + int itemsCount = _owningListBox.SendMessage(NativeMethods.LB_GETCOUNT, 0, 0).ToInt32(); + + for (int i = firstVisibleIndex; i < itemsCount; i++) + { + int itemHeight = _owningListBox.SendMessage(NativeMethods.LB_GETITEMHEIGHT, i, 0).ToInt32(); + + if ((itemsHeightSum += itemHeight) <= listBoxHeight) + { + continue; + } + + int lastVisibleIndex = i - 1; // - 1 because last "i" index is invisible + visibleItemsCount = lastVisibleIndex - firstVisibleIndex + 1; // + 1 because array indexes begin since 0 + + if (itemId > lastVisibleIndex) + { + _owningListBox.SendMessage(NativeMethods.LB_SETTOPINDEX, itemId - visibleItemsCount + 1, 0); + } + + break; + } + } + + internal override bool IsPatternSupported(int patternId) + { + if (patternId == NativeMethods.UIA_ScrollItemPatternId) + { + return true; + } + + return base.IsPatternSupported(patternId); + } + } } } diff --git a/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/ListBoxAccessibleObjectTests.cs b/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/ListBoxAccessibleObjectTests.cs new file mode 100644 index 00000000000..294182dc1c4 --- /dev/null +++ b/src/System.Windows.Forms/tests/UnitTests/AccessibleObjects/ListBoxAccessibleObjectTests.cs @@ -0,0 +1,33 @@ +// 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 Xunit; + +namespace System.Windows.Forms.Tests.AccessibleObjects +{ + public class ListBoxAccessibleObjectTests + { + [Fact] + public void ListBoxAccessibleObjectTests_Ctor_Default() + { + ListBox listBox = new ListBox(); + listBox.Items.AddRange(new object[] { + "a", + "b", + "c", + "d", + "e", + "f", + "g"}); + + var childCount = listBox.AccessibilityObject.GetChildCount(); + + for (int i = 1; i <= childCount; i++) + { + var child = (ListBox.ListBoxAccessibleObject)listBox.AccessibilityObject.GetObjectForChildInternal(i); + Assert.True(child.IsPatternSupported(NativeMethods.UIA_ScrollItemPatternId)); + } + } + } +}