Skip to content
Closed
2 changes: 2 additions & 0 deletions src/Common/src/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
164 changes: 163 additions & 1 deletion src/System.Windows.Forms/src/System/Windows/Forms/ListBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace System.Windows.Forms {
using System.Drawing;
using Microsoft.Win32;
using System.Text;
using System.Runtime.Serialization;

/// <summary>
///
Expand Down Expand Up @@ -2260,6 +2261,8 @@ public ItemArray(ListControl listControl) {
this.listControl = listControl;
}

internal Entry[] Entries { get { return entries; } }

/// <summary>
/// The version of this array. This number changes with each
/// change to the item list.
Expand Down Expand Up @@ -2578,7 +2581,7 @@ int IComparer.Compare(object item1, object item2) {
/// <summary>
/// This is a single entry in our item array.
/// </summary>
private class Entry {
internal class Entry {
public object item;
public int state;

Expand Down Expand Up @@ -2696,6 +2699,9 @@ public ObjectCollection(ListBox owner, object[] value) {
this.AddRange(value);
}

internal ItemArray EntryArray { get { return items; } }

/// <include file='doc\ListBox.uex' path='docs/doc[@for="ListBox.ObjectCollection.Count"]/*' />
/// <summary>
/// Retrieves the number of items.
/// </summary>
Expand Down Expand Up @@ -3890,6 +3896,162 @@ public void Remove(object value) {
}
}
}


/// <devdoc>
/// </devdoc>
protected override AccessibleObject CreateAccessibilityInstance()
{
return new ListBoxAccessibleObject(this);
}

/// <summary>
/// ListBox control accessible object with UI Automation provider functionality.
/// This inherits from the base ListBoxExAccessibleObject and ListBoxAccessibleObject
/// to have all base functionality.
/// </summary>
[ComVisible(true)]
internal class ListBoxAccessibleObject : ControlAccessibleObject
{
private readonly ListBox _owningListBox;
private readonly ListBoxItemAccessibleObjectCollection _itemAccessibleObjects;

/// <summary>
/// Initializes new instance of ListBoxAccessibleObject.
/// </summary>
/// <param name="owningListBox">The owning ListBox control.</param>
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 _));
}
}

/// <summary>
/// ListBox control accessible object with UI Automation provider functionality.
/// This inherits from the base ListBoxExAccessibleObject and ListBoxAccessibleObject
/// to have all base functionality.
/// </summary>
[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);
}
}
}
}

Original file line number Diff line number Diff line change
@@ -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));
}
}
}
}