private static void RaiseEventsOnClient(IntPtr hwnd, int eventId, object idProp, int idObject, int idChild) { ProxySimple el = null; WindowsListView wlv = new WindowsListView (hwnd, null, -1); AutomationProperty automationProperty = idProp as AutomationProperty; AutomationEvent automationEvent = idProp as AutomationEvent; if (eventId == NativeMethods.EventObjectSelectionRemove && automationProperty == SelectionItemPattern.IsSelectedProperty) { el = wlv.CreateListViewItemCheckIfInGroup(idChild - 1); if (el != null) { el.DispatchEvents(eventId, idProp, idObject, idChild); return; } } else if (eventId == NativeMethods.EventObjectSelection || eventId == NativeMethods.EventObjectSelectionRemove || eventId == NativeMethods.EventObjectSelectionAdd) { // This is the speced behavior for events and selection // // The following rule should be used to decide when to fire Selected vs Add/Remove events: // If the result of a SelectElement or an AddElementToSelection is that a single item is selected, // then send a Select for that element; otherwise send Add/Removes as appropriate. // Note that this rule does not depend on whether the selection container is single- or multi- select, // or on what method was used to change the selection. Only the result matters. // Overall message to clients (test automation and assistive technologies) // // 1) If you receive ElementSelectedEvent this guarantees that the element that raised the // event is the only selected element in that container. // 2) If you receive ElementAddedToSelectionEvent this guarantees that those items are added // to selection and the end result of the selection is more than one item. // 3) If you receive ElementRemovedFromSelectionEvent this guarantees that those items are // deselected and the end result is NOT one item selected. // // For the listview adhering to the spec is not possible because of an ambiguity with the winevents that // are fired. This code is trying to map the winevents received to the UIAutomaiton events that are expected. // These are the two cases that are ambiguous: // // Case 1: // The user clicks two different items in succession. // We get an EventObjectSelectionRemove WinEvent for each item that loses // selection and in addition an EventObjectSelection for the new item that got selection. // In this case we want to disregard the EventObjectSelectionRemove (not raise UIA // ElementRemovedFromSelectionEvent) because the end result of this scenario is that only one item is selected. // // Case 2: // If the ListView is multi-select and there are two items selected and the user cntl clicks on one (unselects it). // The listview fires only one WinEvent (an EventObjectSelectionRemove) for the // item that was removed. In this case since there is only one // item left selected UIA should raise ElementSelectedEvent for the remaining item. // // If we turn the EventObjectSelectionRemove WinEvent in case 2 into a ElementSelected // event for the remaining element we would end up firing two events for each click in a multi // select list. This is because the EventObjectSelectionRemove in case 1 is just like // the one case 2. Except that in case 1 we also get a EventObjectSelection separately so we // would end up firing another selected event. // // It has been decided that it is preferred to receive extra EventObjectSelection events // then receiving EventObjectSelectionRemove events at the wrong time. If two items are selected // in a listview and the user clicks on one of them the only event winevent we get is a remove so // we have to convert removed winevent to selected event or we would miss an event. So it better // to have multiple events in some case than none when there should be ([....] 9/8/2004). // if (eventId == NativeMethods.EventObjectSelectionRemove && GetSelectedItemCount(hwnd) == 1) { if (MultiSelected(hwnd)) { // Change the EventObjectSelectionRemove to an EventObjectSelection. eventId = NativeMethods.EventObjectSelection; idProp = SelectionItemPattern.ElementSelectedEvent; // Change the child id to the selected child. int item = GetStartOfSelectedItems(hwnd); if (item > -1) { idChild = item + 1; } } else { // Since case 2 does not apply to single selection listviews, suppress the // EventObjectSelectionRemove. return; } } el = wlv.CreateListViewItemCheckIfInGroup(idChild - 1); } // GridItem case else if (eventId == NativeMethods.EventObjectReorder && (automationProperty == GridItemPattern.ColumnProperty || automationProperty == GridItemPattern.RowProperty)) { // GridItem case. We need to recursively call all of the list items for (el = wlv.GetFirstChild(); el != null; el = wlv.GetNextSibling(el)) { el.DispatchEvents(eventId, idProp, idObject, idChild); } return; } // Map the WinEvent NameChange to ValueChange to go through the dispatch else if (eventId == NativeMethods.EventObjectNameChange) { el = wlv.CreateListViewItemCheckIfInGroup(idChild - 1); eventId = NativeMethods.EventObjectValueChange; } // Change of state for the check box must generates a StateChange Win Events. // Map it to of ObjectChange for the checkbox else if (eventId == NativeMethods.EventObjectStateChange && CheckBoxes(hwnd)) { el = wlv.CreateListViewItemCheckIfInGroup(idChild - 1); el = ((ProxyFragment)el).GetFirstChild(); eventId = NativeMethods.EventObjectValueChange; // Assert if the assumption that the first child is a check box is false System.Diagnostics.Debug.Assert(el is ListViewItemCheckbox); } // Special case for logical element change for a list view item else if ((eventId == NativeMethods.EventObjectDestroy || eventId == NativeMethods.EventObjectCreate) && automationEvent == AutomationElement.StructureChangedEvent) { ProxySimple parent = wlv; bool fGroupView = IsGroupViewEnabled(hwnd); // Allways disable the groups as one may have been created or // destroyed if (fGroupView) { // remove groupmanager from collection _groupsCollection.Remove(hwnd); // If it is an object creation, create the element and picks // its parent to invalidate. The parent can be either a group // or the listview itself. if (eventId == NativeMethods.EventObjectCreate && fGroupView) { // Get the item with the resetted collection of groups (may be null if the group is empty) ProxySimple lvi = wlv.CreateListViewItemCheckIfInGroup(idChild - 1); if (lvi != null) parent = lvi.GetParent(); } } // If the element destroyed is in a group invalidate the whole listview as we have no // idea the element was part of before // Since children are referenced by position in the tree, addition and removal // of items leads to different results when asking properties for the same element // On removal, item + 1 is now item! // Use Children Invalidated to let the client knows that all the cached children are invalid AutomationInteropProvider.RaiseStructureChangedEvent( parent, new StructureChangedEventArgs( StructureChangeType.ChildrenInvalidated, parent.MakeRuntimeId() ) ); return; } else { el = wlv; } if (el != null) { el.DispatchEvents(eventId, idProp, idObject, idChild); } return; }
internal static IRawElementProviderSimple Create(IntPtr hwnd, int idChild) { WindowsListView lv = new WindowsListView(hwnd, null, 0); if( idChild == 0 ) return lv; else return lv.CreateListViewItemCheckIfInGroup (idChild - 1); }