private static void ThrowUnableToFindChildException(string path, IUIAutomationElement item) { // if not found, build a list of available children for debugging purposes var validChildren = new List <string>(); try { var children = item.GetCachedChildren(); for (var i = 0; i < children.Length; i++) { validChildren.Add(SimpleControlTypeName(children.GetElement(i))); } } catch (InvalidOperationException) { // if the cached children can't be enumerated, don't blow up trying to display debug info } throw new InvalidOperationException( string.Format( "Unable to find a child named {0}. Possible values: ({1}).", path, string.Join(", ", validChildren) ) ); }
////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // This function is not used by the sample, but it shows an alternative approach to building up // the cache of hyperlinks. Depending on the action taken by the target app when providing data // to be stored in the cache of results, different approaches taken by the UIA client can have // different performance benefits. // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// private void BuildListOfHyperlinksFromWindowInternalAlternateApproach(bool fSearchLinkChildren) { // If we're already building up a list of links, ignore this request to refresh the list. // (A shipping app might consider queueing this request in order to refresh the list again // once the in-progress refreshing action is complete.) if (_fRefreshInProgress) { return; } _fRefreshInProgress = true; // First build a cache request in the same way as done elsewhere in the sample. // This means that for each element returned following the search, they will // have their name, bounding rect and Invoke pattern cached. IUIAutomationCacheRequest cacheRequest = _automation.CreateCacheRequest(); cacheRequest.AddProperty(_propertyIdName); cacheRequest.AddProperty(_propertyIdBoundingRectangle); cacheRequest.AddPattern(_patternIdInvoke); // At this point elsewhere in the sample, we specified that we only wanted data // for the hyperlink elements (and optionally their children) cached. In this // approach here, we will say that we want data for ALL descendants of the // found elements cached. cacheRequest.TreeScope = TreeScope.TreeScope_Descendants; // Now create a property condition as we've done elsewhere, to say that we're only // interested in elements which are in the Control view and are hyperlinks. IUIAutomationCondition conditionControlView = _automation.ControlViewCondition; IUIAutomationCondition conditionHyperlink = _automation.CreatePropertyCondition(_propertyIdControlType, _controlTypeIdHyperlink); IUIAutomationCondition condition = _automation.CreateAndCondition(conditionControlView, conditionHyperlink); // Now unlike steps we took elsewhere, specify that the cache request should // have an additional filter of the property condition we just created. cacheRequest.TreeFilter = condition; // Elsewhere in the sample, we called FindAllBuildCache(). This returned an array of hyperlink // elements with their data cached, (and optinally their children with cache data too.) This // CacheLinksFromWindow() function takes a different approach. The element that is returned // from the call to BuildUpdatedCache() below is the browser element with some data cached. // But the returned element will have an array of cached child elements, and each of those // elements will by the hyperlinks we need. So the cache request here has specified through // its tree filter that we're only interest in hyperlinks, whereas elsewhere in this sample, // we supplied that condition in the search call we made. How much difference this makes to // the performance of calls depends on the action taken by the target application. // *** Note, using this appoach, we can't also cache data for the direct children of the // hyperlinks as we did elsewhere in the sample. So whether this approach is practical // depends on the needs of the client application. // Note that with a property conditions, it's not possible to request that the set of elements // returned from a search are all elements which have a control type of hyperlink OR whose parent // has a control type of hyperlink. // Get a handle to the window of interest. IntPtr hwnd = Win32.FindWindow(strBrowserWindowClass, null); if (hwnd != IntPtr.Zero) { IUIAutomationElement elementBrowser = _automation.ElementFromHandleBuildCache(hwnd, cacheRequest); if (elementBrowser != null) { _linkItems.Clear(); IUIAutomationElementArray arrayChildren = elementBrowser.GetCachedChildren(); if (arrayChildren != null) { int cElements = arrayChildren.Length; // Process each returned hyperlink element. for (int idxElement = 0; idxElement < cElements; idxElement++) { IUIAutomationElement elementChild = arrayChildren.GetElement(idxElement); // Take the same action elsewhere in the sample to present the hyperlink // in the sample app UI. string strLinkName = GetCachedDataFromElement(elementChild, fSearchLinkChildren); if (strLinkName != null) { strLinkName = strLinkName.Trim(); LinkItem item = new LinkItem(); item.linkName = strLinkName; item.element = elementChild; _linkItems.Add(item); } } // Notify the main UI thread that a list of links is ready for processing. Do not block in this call. _listViewLinks.BeginInvoke(_UIUpdateDelegate, _linkItems); } } } // Allow another refresh to be performed now. _fRefreshInProgress = false; }
////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // GetCachedDataFromElement() // // Get the cached name from a UIA element. If the element doesn't have a name, // optionally try to find a name from the cached children of the element. // // Runs on the background thread. // ////////////////////////////////////////////////////////////////////////////////////////////////////////////// private string GetCachedDataFromElement(IUIAutomationElement element, bool fSearchLinkChildren) { // (A shipping app would do parameter checking here.) string strName = null; // Get the bounding rectangle for the hyperlink. By retrieving this from the // cache, we avoid the time-consuming cross-process call to get the data. tagRECT rectBounds = element.CachedBoundingRectangle; // If the hyperlink has a zero-size bounding rect, ignore the element. This // might happen if the hyperlink has scrolled out of view. (We could also // investigate whether using the IsOffscreen property tells us that the link // can be ignored. In fact, if the IsOffscreen property is reliable, we could // have included a property condition of IsOffcreen is false in the original // search, and not check whether the link's visible here.) if ((rectBounds.right > rectBounds.left) && (rectBounds.bottom > rectBounds.top)) { // Get the name of the element. This will often be the text shown on the screen. // Note that the set of get_Cached* functions (or get_Current*), are convenient // ways of retrieving the same data that could be retrieved through the functions // GetCachedPropertyValue() (or GetCurrentPropertValue().) In the case of get_CachedName(), // the alternative would be to call GetCachedPropertyValue() with UIA_NamePropertyId. string strNameFound = element.CachedName; if (strNameFound.Length > 0) { // A shipping app would check for more than an empty string. (A link might // just have " " for a name.) strName = strNameFound; } else { // The hyperlink has no usable name. Consider using the name of a child element of the hyperlink. if (fSearchLinkChildren) { // Given that hyperlink element has no name, use the name of first child // element that does have a name. (In some cases the hyperlink element might // contain an image or text element that does have a useful name.) We can take // this action here because we supplied TreeScope_Children as the scope of the // cache request that we passed in the call to FindAllBuildCache(). IUIAutomationElementArray elementArrayChildren = element.GetCachedChildren(); if (elementArrayChildren != null) { int cChildren = elementArrayChildren.Length; // For each child element found... for (int idxChild = 0; idxChild < cChildren; ++idxChild) { IUIAutomationElement elementChild = elementArrayChildren.GetElement(idxChild); if (elementChild != null) { string strNameChild = elementChild.CachedName; // Check the name of the child elements here. We don't // care what type of element it is in this sample app. if (strNameChild.Length > 0) { // We have a usable name. strName = strNameChild; break; } // Try the next child element of the hyperlink... } } } } } } return(strName); }