I have created a subclass for ValueListItemUIElement to handle some rare exceptions that occur in a mixed WinForms/WPF environment (I have an override for InitAppearance that calls the base class and catches exceptions that occur in this call chain). This all works pretty well so far.
However, this seems to have broken UI automation and this is probably related to the way how I create and initialize the new instances. I used a CreationFilter (IUIElementCreationFilter) and in there I have the following section:
public void AfterCreateChildElements(UIElement parent) { switch (parent) { ... case ValueListItemContainerUIElement container: { // replace standard ValueListItemUIElement with our custom class that catches // null reference exceptions var newChildElements = parent.ChildElements.Select( c => { if (c is ValueListItemUIElement valueListItemUIElement) { var newValueListItemUIElement = new SafeValueListItemUIElement( container, valueListItemUIElement.ValueListItem, valueListItemUIElement.IsMruItem, valueListItemUIElement.ListIndex) { Rect = valueListItemUIElement.Rect }; return newValueListItemUIElement; } return null; }) .Where(i => i != null) .ToList(); parent.ChildElements.Clear(); parent.ChildElements.AddRange(newChildElements); parent.DirtyChildElements(true); break; }
I assume something is missing in my initialization of the new instances. In the debugger I noticed that the AccessibilityInstance of the new entities has an empty (0,0,0,0) BoundingRectangle and Bounds property.
Does anyone have any clue what might be missing here?
Yes - makes sense. And it's easy to do. I just added it to the sample program and it works as expected.
One thing you might consider is what to do if the cache gets too big. That could happen, in theory, if you created a lot of new ValueListItems, either in a bunch of different ValueLists, or maybe because you ended up re-creating them for some reason. It might not be a terrible idea to trap the grid's CellListDropDown event and call a method on the CreationFilter that clears the cached every time the ValueList drops down - just to be safe.
I don't think you did - I added tracepoints in my test program and can see that items are created only once and then retrieved when the dropdown is expanded again.
Since the dictionary takes a ValueListItem as a key there shouldn't be an issue with multiple drop downs in the same grid. And the creation filter is per grid instance i.e. multiple grids in an application should not be an issue either.
Looks like this time it's really solved. This issue took me through some ups and downs. I hope this time we can stay on the up level (for a while at least).
Thanks again for your continued help and perseverance in this matter!
Yes, I see my mistake there. Sorry about that.
This doesn't work because by the time AfterCreateChildElements has fired, the parent element is throwing away the existing MyValueListItemUIElement, and putting a regular ValueListItemUIElement in it's place. So your creation filter ends up swapping them out again. The code I put in to find an existing one never finds it, because it's already been removed. So I think we can get around this by caching the MyValueListItemUIElements in the CreationFilter.
public class ValueItemsCreationFilter : IUIElementCreationFilter { private Dictionary<ValueListItem, MyValueListItemUIElement> myValueListItemUIElements = new Dictionary<ValueListItem, MyValueListItemUIElement>(); public void AfterCreateChildElements(UIElement parent) { if (parent is ValueListItemContainerUIElement) { for (int i = parent.ChildElements.Count - 1; i >= 0; i--) { var valueListItemUIElement = parent.ChildElements[i] as ValueListItemUIElement; if (valueListItemUIElement != null && !(valueListItemUIElement is MyValueListItemUIElement)) { // Try to find an existing MyValueListItemUIElement and re-use it if possible. var valueListItem = valueListItemUIElement.ValueListItem; MyValueListItemUIElement newValueListItemUIElement; bool success = this.myValueListItemUIElements.TryGetValue(valueListItem, out newValueListItemUIElement); if (!success) { newValueListItemUIElement = new MyValueListItemUIElement( parent as ValueListItemContainerUIElement, valueListItem, valueListItemUIElement.IsMruItem, valueListItemUIElement.ListIndex) { Rect = valueListItemUIElement.Rect }; parent.ChildElements.RemoveAt(i); parent.ChildElements.Insert(i, newValueListItemUIElement); myValueListItemUIElements[valueListItem] = newValueListItemUIElement; } else { newValueListItemUIElement.Initialize( valueListItem, valueListItemUIElement.IsMruItem, valueListItemUIElement.ListIndex ); } } } //parent.DirtyChildElements(true); var height = parent.ChildElements.Count * parent.ChildElements[0].Rect.Height; parent.Rect = new Rectangle(parent.Rect.X, parent.Rect.Y, parent.Rect.Width, height); } } public bool BeforeCreateChildElements(UIElement parent) { return false; } }
This seems to work - it only replaces the elements the first time you drop down the list and then doesn't replace them again while the dropdown is open. Unless I made some other logical error.
Hi Mike - while I confirmed that your code fixes the automation it will not fix my problem because it effectively NEVER replaces the UIItems with the subclassed ones because of an error in the logic in your code. With your code my debugger never breaks in the section where you create a new instance. So the behavior is like the creation filter didn't exist. I think the code should look like this instead:
... // Try to find an existing MyValueListItemUIElement and re-use it if possible. MyValueListItemUIElement newValueListItemUIElement = parent.GetDescendant(typeof(MyValueListItemUIElement), valueListItemUIElement.ValueListItem) as MyValueListItemUIElement; // only create new UI item if we didn't find a subclassed one if (newValueListItemUIElement == null) { newValueListItemUIElement = new MyValueListItemUIElement( parent as ValueListItemContainerUIElement, valueListItemUIElement.ValueListItem, valueListItemUIElement.IsMruItem, valueListItemUIElement.ListIndex) { Rect = valueListItemUIElement.Rect }; } // replace with existing or new ui item parent.ChildElements.RemoveAt(i); parent.ChildElements.Insert(i, newValueListItemUIElement); ...
However, once I come back to actually replacing the items IU automation no longer works (i.e. Inspect no longer highlights the items in the drop down). So it looks like the journey is not yet over ...