Windows 7 UI Automation Client API C# sample (hyperlink processor) Version 1.0

A few weeks ago I demo’d the sample C++ app which uses the Windows 7 UI Automation Client API to interact with hyperlinks in a browser, (http://code.msdn.microsoft.com/Windows-7-UI-Automation-9131f729), at the CSUN 2011 conference in San Diego. I got feedback that a similar C# sa

C# (303.8 KB)
 
 
 
 
 
(1)
2,695 times
Add to favorites
5/2/2011
E-mail Twitter del.icio.us Digg Facebook
//*******************************************************************************
//   Copyright 2011 Guy Barker
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//*******************************************************************************

// LinkProcessor.cs : Retrieve and interact with a list of hyperlinks in a browser application.

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using interop.UIAutomationCore; // Generated by VS 2010 as the interop layer between the C# sample code and the unmanaged Windows 7 UIA API.

namespace Win7UIAClientManaged
{
    // LinkProcessor makes all the calls to UIA to build of a list of hyperlinks in a browser window.
    class LinkProcessor
    {
        // This sample retrieves hyperlinks from an Internet Explorer window.
        private string strBrowserWindowClass = "IEFrame";

        private IUIAutomation _automation; // Main UIA object required by any UIA client app.
        private ListView _listViewLinks; // Listview shown in the UI. All updates to this are made on the main UI thread.
        private Highlight _highlight; // Highlight object used to highlight hyperlinks in the browser window.
        private bool _fRefreshInProgress = false; // Prevent reentrant attempts to update the list of hyperlinks.

        private Thread _threadBackground; // Background MTA thread on which all calls to UIA are made.
        private AutoResetEvent _autoEventMsg; // Event used to notify the background thread that action is required.
        private AutoResetEvent _autoEventInit; // Event used to allow background thread to take any required initialization action.

        // SampleMsgType and SampleMsgData are used to allow the main UI thread that specific action is required by the background thread.
        private enum SampleMsgType
        {
            msgNull,
            msgBuildList,
            msgHighlightLink,
            msgInvokeLink,
            msgCloseDown
        };

        private struct SampleMsgData
        {
            public SampleMsgType msgType;
            public IUIAutomationElement element;
            public bool fUseCache;
        };

        // This sample doesn't really need to support a queue of messages for action required by the 
        // backgroung thread, but use a queue anyway in case requirements change in the future.
        private Queue<SampleMsgData> _msgQueue = new Queue<SampleMsgData>();

        // LinkItem is used to allow the background thread to build up a list of hyperlink results data for the main UI thread to process.
        public class LinkItem
        {
            public string linkName;
            public object element;
        }

        private List<LinkItem> _linkItems = new List<LinkItem>();

        // _UIUpdateDelegate is used to invoke the main UI thread to update the list of hyperlinks.
        private Win7UIAClientForm_LinkProcessor.SampleUIListUpdateDelegate _UIUpdateDelegate; 

        // Note that UIAutomationClient.UIA_PatternIds.UIA_InvokePatternId could be 
        // supplied to calls to UIA in this file, but that would require building the 
        // sample using settings other than the VS defaults. So instead supply a value 
        // equal to UIAutomationClient.UIA_PatternIds.UIA_InvokePatternId;
        private int _patternIdInvoke = 10000;

        // Similarly for other ids.
        private int _propertyIdBoundingRectangle = 30001;
        private int _propertyIdControlType = 30003;
        private int _propertyIdName = 30005;
        private int _controlTypeIdHyperlink = 50005;
        private int _patternIdValue = 10002;

        /////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // Initialize()
        //
        // In order to prevent any possibility of the calls to UIA below leading to delays while a 
        // a UIA event handler is also running, all calls to UIA below will run on a background MTA 
        // thread. This means that the app's main UI thread makes no calls to UIA at all.
        //
        // Ths method runs on the main UI thread.
        //
        /////////////////////////////////////////////////////////////////////////////////////////////////
        public void Initialize(Win7UIAClientForm_LinkProcessor.SampleUIListUpdateDelegate UIUpdateDelegate, ListView listViewLinks, Highlight highlight)
        {
            _UIUpdateDelegate = UIUpdateDelegate;
            _listViewLinks = listViewLinks;
            _highlight = highlight;

            // The object will call UIA on a background MTA thread. This sample doesn't
            // expect to enter here with a background thread already initialized.
            if (_threadBackground != null)
            {
                return;
            }

            // The background thread will wait for notifications when action is required.
            _autoEventMsg = new AutoResetEvent(false);

            // Create the background thread, and wait until it's ready to start working.
            _autoEventInit = new AutoResetEvent(false);
            ParameterizedThreadStart paramThreadStart = new ParameterizedThreadStart(s_DoWork);

            _threadBackground = new Thread(paramThreadStart);
            _threadBackground.SetApartmentState(ApartmentState.MTA);
            _threadBackground.Start(this);

            _autoEventInit.WaitOne();
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // Uninitialize()
        //
        // Close the background thread down.
        //
        // Runs on the main UI thread.
        //
        /////////////////////////////////////////////////////////////////////////////////////////////////
        public void Uninitialize()
        {
            // Tell the background thread to close down.
            if (_threadBackground != null)
            {
                SampleMsgData msgData = new SampleMsgData();
                msgData.msgType = SampleMsgType.msgCloseDown;
                AddMsgToQueue(msgData);
            }
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // BuildListOfHyperlinksFromWindow()
        //
        // Signals the background thread that the list of hyperlinks needs to be refreshed.
        // 
        // Run on the main UI thread.
        //
        /////////////////////////////////////////////////////////////////////////////////////////////////
        public void BuildListOfHyperlinksFromWindow(bool fUseCache, bool fSearchLinkChildren)
        {
            // Let the background thread know it must generate the list of hyperlinks.
            SampleMsgData msgData = new SampleMsgData();
            msgData.msgType = SampleMsgType.msgBuildList;
            AddMsgToQueue(msgData);
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // HighlightLink()
        //
        // Signals the background thread that the a hyperlink needs to be highlighted.
        // 
        // Run on the main UI thread.
        //
        /////////////////////////////////////////////////////////////////////////////////////////////////
        public void HighlightLink(int idxLink, bool fUseCache)
        {
            // It's ok to access the listview directly here, as we're running on the main UI thread.
            if ((idxLink >= 0) && (idxLink < _listViewLinks.Items.Count))
            {
                IUIAutomationElement element = (IUIAutomationElement)_listViewLinks.Items[idxLink].Tag;
                if (element != null)
                {
                    // Let the background thread know it must highlight a hyperlink.
                    SampleMsgData msgData = new SampleMsgData();
                    msgData.msgType = SampleMsgType.msgHighlightLink;
                    msgData.element = element;
                    msgData.fUseCache = fUseCache;
                    AddMsgToQueue(msgData);
                }
            }
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // InvokeLink()
        //
        // Signals the background thread that a hyperlink needs to be invoked.
        // 
        // Run on the main UI thread.
        //
        /////////////////////////////////////////////////////////////////////////////////////////////////
        public void InvokeLink(int idxLink, bool fUseCache)
        {
            // It's ok to access the listview directly here, as we're running on the main UI thread.
            if ((idxLink >= 0) && (idxLink < _listViewLinks.Items.Count))
            {
                IUIAutomationElement element = (IUIAutomationElement)_listViewLinks.Items[idxLink].Tag;
                if (element != null)
                {
                    // Let the background thread know it must invoke the hyperlink.
                    SampleMsgData msgData = new SampleMsgData();
                    msgData.msgType = SampleMsgType.msgInvokeLink;
                    msgData.element = element;
                    msgData.fUseCache = fUseCache;
                    AddMsgToQueue(msgData);
                }
            }
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // AddMsgToQueue()
        //
        // Update a list of messages waiting to be processed by the background thread.
        // 
        // Run on the main UI thread.
        //
        /////////////////////////////////////////////////////////////////////////////////////////////////
        private void AddMsgToQueue(SampleMsgData msgData)
        {
            // Request the lock, and block until it is obtained.
            Monitor.Enter(_msgQueue);
            try
            {
                // When the lock is obtained, add an element.
                _msgQueue.Enqueue(msgData);
            }
            finally
            {
                // Ensure that the lock is released.
                Monitor.Exit(_msgQueue);
            }

            // Let the background thread know there's some action to be taken.
            _autoEventMsg.Set();
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // s_DoWork()
        //
        // Static entry point for the background thread on which the calls to UIA will be made.
        //
        /////////////////////////////////////////////////////////////////////////////////////////////////
        private static void s_DoWork(object data)
        {
            LinkProcessor linkProcessor = (LinkProcessor)data;
            linkProcessor.ThreadProc();
        }

        /////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // ThreadProc()
        //
        // Instance-based entry point for the background thread on which the calls to UIA will be made.
        //
        /////////////////////////////////////////////////////////////////////////////////////////////////
        private void ThreadProc()
        {
            // The first step in calling UIA, is getting a CUIAutomation object.
            _automation = new CUIAutomation();

            // *** Note: The thread on which the UIA calls are made below must be MTA.

            // Let the main thread know this thread is ready to start processing.
            // This event isn't required for this sample, but a shipping app may 
            // have work to do at the start of the background thread before the 
            // main thread is to continue.
            _autoEventInit.Set();

            // fCloseDown will be set true when the thread is to close down.
            bool fCloseDown = false;

            while (!fCloseDown)
            {
                // Wait here until we're told we have some work to do.
                _autoEventMsg.WaitOne();

                while (true)
                {
                    SampleMsgData msgData;

                    // Note that none of the queue or message related action here is specific to UIA.
                    // Rather it is only a means for the main UI thread and the background MTA thread
                    // to communicate.

                    // Get a message from the queue of action-related messages.
                    Monitor.Enter(_msgQueue);
                    try
                    {
                        // An exception is thrown when the queue is empty.
                        msgData = _msgQueue.Dequeue();
                    }
                    catch (InvalidOperationException)
                    {
                        // InvalidOperationException is thrown if the queue is empty.
                        msgData.msgType = SampleMsgType.msgNull;
                        msgData.element = null;
                        msgData.fUseCache = false;

                        break;
                    }
                    finally
                    {
                        // Ensure that the lock is released.
                        Monitor.Exit(_msgQueue);
                    }

                    if (msgData.msgType == SampleMsgType.msgBuildList)
                    {
                        // We will be here following a press of the Refresh button by the user.
                        BuildListOfHyperlinksFromWindowInternal(false, false);
                    }
                    if (msgData.msgType == SampleMsgType.msgHighlightLink)
                    {
                        // We will be here following a change in the selected hyperlink in the list of hyperlinks.
                        HighlightLinkInternal(msgData.element, msgData.fUseCache);
                    }
                    else if (msgData.msgType == SampleMsgType.msgInvokeLink)
                    {
                        // We will be here following a press of the Invoke button by the user.
                        InvokeLinkInternal(msgData.element, msgData.fUseCache);
                    }
                    else if (msgData.msgType == SampleMsgType.msgCloseDown)
                    {
                        // The main UI thread is telling this background thread to close down.
                        fCloseDown = true;

                        break;
                    }
                }
            }
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // BuildListOfHyperlinksFromWindowInternal()
        //
        // Retrieve a list of hyperlinks from a browser window.
        //
        // Runs on the background thread.
        //
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        private void BuildListOfHyperlinksFromWindowInternal(bool fUseCache, 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;

            // Get a handle to the window of interest.
            IntPtr hwnd = Win32.FindWindow(strBrowserWindowClass, null);
            if (hwnd != IntPtr.Zero)
            {
                // Get a UIAutomationElement associated with this browser window. Note that the IUIAutomation 
                // interface has other useful functions for retrieving automation elements. For example:
                //
                //   ElementFromPoint() - convenient when getting the UIA element beneath the mouse cursor.
                //   GetFocusedElement() - convenient when you need the UIA element for whatever control 
                //                         currently has keyboard focus.
                //
                // All these functions have cache-related equivalents which can reduce the 
                // time it takes to work with the element once it's been retrieved.

                IUIAutomationElement element = _automation.ElementFromHandle(hwnd);
                if (element != null)
                {
                    // If all we needed to do is get a few properties of the element we now have, we could 
                    // make the get_Current* calls such shown below.) But these would incur the time cost of 
                    // additional cross-proc calls.)

                    string strName = element.CurrentName;
                    // Do something with the name...

                    tagRECT rect = element.CurrentBoundingRectangle;
                    // Do something with the bounding rect...

                    // Rather than doing the above, a shipping app might choose to request the name
                    // and bounding rect of the browser when it retrieves the browser element. It
                    // could do this by calling ElementFromHandleBuildCache() supplying a cache
                    // request which included the properties it needs. By doing that, there would 
                    // only be one cross-proc call rather than the three involved with the above steps.

                    // For this sample, we'll build up a list of all hyperlinks in the browser window.
                    BuildListOfHyperlinksFromElement(element, fUseCache, fSearchLinkChildren);
                }
            }

            // Allow another refresh to be performed now.
            _fRefreshInProgress = false;
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // BuildListOfHyperlinksFromElement()
        //
        // Retrieve a list of hyperlinks from a UIAutomation element. Notifies the main 
        // UI thread when it's found all the hyperlinks to be added to the app's list of 
        // hyperlinks.
        //
        // Runs on the background thread.
        //
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        private void BuildListOfHyperlinksFromElement(IUIAutomationElement elementBrowser,
                                                      bool fUseCache, bool fSearchLinkChildren)
        {
            IUIAutomationCacheRequest cacheRequest = null;

            // If a cache is used, then for each of the elements returned to us after a search 
            // for elements, specific properties (and patterns), can be cached with the element. 
            // This means that when we access one of the properties later, a cross-proc call 
            // does not have to be made. (But it also means that when such a call is made, we
            // don't learn whether the original element still exists.)
            if (fUseCache)
            {
                // Create a cache request containing all the properties and patterns
                // we will need once we've retrieved the hyperlinks. By using this
                // cache, when can avoid time-consuming cross-process calls when
                // getting hyperlink properties later.
                cacheRequest = _automation.CreateCacheRequest();
                
                // We'll need the hyperlink's name and bounding rectangle later.
                // A full list of Automation element properties can be found at 
                // http://msdn.microsoft.com/en-us/library/ee684017(v=VS.85).aspx.

                cacheRequest.AddProperty(_propertyIdName);
                cacheRequest.AddProperty(_propertyIdBoundingRectangle);

                // The target of the hyperlink might be stored in the Value property of 
                // the hyperlink. The Value property is only avaliable if an element
                // supports the Value pattern. This sample doesn't use the Value, but
                // if it did, it would call the following here.
                //  hr = pCacheRequest->AddProperty(UIA_ValueValuePropertyId);
                // It's ok to attempt to cache a property on a pattern which might not 
                // exist on the cached elements. In that case, the property just won't
                // be available when we try to retrieve it from the cache later.

                // Note that calling AddPattern() does not cache the properties 
                // associated with a pattern. The pattern's properties must be
                // added explicitly to the cache if required.

                // Cache the Invoke pattern too. This means when we prepare to invoke a link later,
                // we won't need to make a cross-proc call during that preparation. (A cross-proc
                // call will occur at the time Invoke() is actually called.) A full list of patterns
                // can be found at http://msdn.microsoft.com/en-us/library/ee684023(v=VS.85).aspx.

                cacheRequest.AddPattern(_patternIdInvoke);

                // The next step is to specify for which elements we want to have the properties, (and 
                // pattern) cached. By default, caching will occur on each element found in the search 
                // below. But we can also request that the data is  cached for direct children of the
                // elements found, or even all the descendants of the elements founds. (A scope of 
                // Parent or Ancestors cannot be used in a cached request.)

                // So in this sample, if TreeScope_Element is used as the scope here, (which is the 
                // default), then only properties for the found hyperlinks will be cached. The sample
                // optionally caches the properties for the direct children of the hyperlinks too. 
                // This means that if we find a hyperlink with no name, we can search the hyperlink's
                // children to see if one of the child elements has a name we can use. (Searching the 
                // children could be done without using the cache, but it would incur the time cost of
                // making cross-proc calls.) 

                TreeScope scope = TreeScope.TreeScope_Element;

                if (fSearchLinkChildren)
                {
                    scope = scope | TreeScope.TreeScope_Children;
                }

                cacheRequest.TreeScope = scope;

                // Note: By default the cache request has a Mode of Full. This means a reference to the 
                // target element is included in the cache, as well as whatever properties and patterns
                // we specified should be in the cache. With a reference to the target element, we can:
                //
                // (i) Retrieve a property later for an element which we didn't request should be in 
                //     the cache. Eg we could call get_CurrentHasKeyboardFocus().
                //
                // (ii) We can call a method of a pattern that the element supports. Eg if Full mode was
                //      not used here, we would not be able to call Invoke() on the hyperlink later.

                // If we specified a Mode of None for the cache request here, then the results only include
                // cached data, with no connection at all after the call returns to the source elements. If 
                // only data is required, then it would be preference to use a Mode of None, as less work is 
                // required by UIA. (Also, if a reference to the element is returned in the cache and kept 
                // around for a non-trivial time, then it increases the chances that the target process 
                // attempts to free the element, but it can't do so in a clean manner as it would like, 
                // due to the client app here holding a reference to it.) To specify that we want a Mode of 
                // None, we'd make this call here:

                // cacheRequest.AutomationElementMode = AutomationElementMode.AutomationElementMode_None;
            }

            // Now regardless of whether we're using a cache, we need to specify which elements
            // we're interested in during our search for elements. We do this by building up a
            // property condition. This property condition tells UIA which properties must be 
            // satisfied by an element for it to be included in the search results. We can 
            // combine a number of properties with AND and OR logic.

            // We shall first say that we're only interested in elements that exist in the Control view. 
            // By default, a property condition uses the Raw view, which means that every element in the 
            // target browser's UIA tree will be examined. The Control view is a subset of the Raw view, 
            // and only includes elements which present some useful UI. (The Raw view might include
            // container elements which simply group elements logically together, but the containers 
            // themselves might have no visual representation on the screen.)

            IUIAutomationCondition conditionControlView = _automation.ControlViewCondition;
            IUIAutomationCondition conditionHyperlink = _automation.CreatePropertyCondition(_propertyIdControlType, _controlTypeIdHyperlink);

            // Now combine these two properties such that the search results only contain
            // elements that are in the Control view AND are hyperlinks. We would get the
            // same results here if we didn't include the Control view clause, (as hyperlinks
            // won't exist only in the Raw view), but by specifying that we're only interested
            // in the Control view, UIA won't bother checking all the elements that only exist
            // in the Raw view to see if they're hyperlinks.
            IUIAutomationCondition condition = _automation.CreateAndCondition(conditionControlView, conditionHyperlink);

            // Now retrieve all the hyperlinks in the browser. We must specify a scope in the Find calls here,
            // to control how far UIA will go in looking for elements to include in the search results. For
            // this sample, we must check all descendants of the browser window. 

            // *** Note: use TreeScope_Descendants with care, as depending on what you're searching for, UIA may
            // have to process potentially thousands of elements. For example, if you only need to find top level 
            // windows on your desktop, you would search for TreeScope_Children of the root of the UIA tree. (The 
            // root element can be found with a call to IUIAutomation::GetRootElement().)

            // *** Note: If the following searches included searching for elements in the client app's own UI,
            // then the calls must be made on a background thread. (ie not your main UI thread.) Once event
            // handlers are used, then it's preferable to have all UIA calls made on a background thread
            // regardless of whether the app interacts with its own UI.

            IUIAutomationElementArray elementArray;

            if (fUseCache)
            {
                elementArray = elementBrowser.FindAllBuildCache(TreeScope.TreeScope_Descendants, condition, cacheRequest);
            }
            else
            {
                elementArray = elementBrowser.FindAll(TreeScope.TreeScope_Descendants, condition);
            }

            // Build up a list of items to be passed to the main thread in order for it to
            // populate the list of hyperlinks shown in the UI.

            _linkItems.Clear();

            if (elementArray != null)
            {
                // Find the number of hyperlinks returned by the search. (The number of hyperlinks 
                // found might be zero if the browser window is minimized.)
                int cLinks = elementArray.Length;

                // Process each returned hyperlink element.
                for (int idxLink = 0; idxLink < cLinks; ++idxLink)
                {
                    IUIAutomationElement elementLink = elementArray.GetElement(idxLink);

                    // Get the name property for the hyperlink element. How we get this depends
                    // on whether we requested that the property should be cached or not.

                    string strLinkName = null;

                    if (fUseCache)
                    {
                        strLinkName = GetCachedDataFromElement(elementLink, fSearchLinkChildren);
                    }
                    else
                    {
                        strLinkName = GetCurrentDataFromElement(elementLink, fSearchLinkChildren);
                    }

                    // If we have non-null name, add the link to the list. (This sample does not check
                    // for names that only contains whitespace.)
                    if (strLinkName != null)
                    {
                        strLinkName = strLinkName.Trim();

                        LinkItem item = new LinkItem();
                        item.linkName = strLinkName;
                        item.element = elementLink;

                        _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);
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // 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;
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // GetCurrentDataFromElement()
        //
        // Get the current name from a UIA element, (incurring the time cost of various a cross-proc calls). 
        // If the element doesn't have a name, optionally try to find a name from the children of the element
        // by using a TreeWalker.
        //
        // Runs on the background thread.
        //
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        private string GetCurrentDataFromElement(IUIAutomationElement element, bool fSearchLinkChildren)
        {
            string strName = null;

            // Call back to the target app to retrieve the bounds of the hyperlink element.
            tagRECT rectBounds = element.CurrentBoundingRectangle;

            // If we're the hyperlink has a zero-size bounding rect, ignore the element.
            if ((rectBounds.right > rectBounds.left) && (rectBounds.bottom > rectBounds.top))
            {
                // Get the name of the element, (again incurring a cross-proc call). This name will often 
                // be the text shown on the screen.
                string strNameFound = element.CurrentName;
                if ((strNameFound != null) && (strNameFound.Length > 0))
                {
                    // We have a usable name.
                    strName = strNameFound;
                }
                else
                {
                    // The hyperlink has no usable name. Consider using the name of a child element of the hyperlink.
                    if (fSearchLinkChildren)
                    {
                        // Use a Tree Walker here to try to find a child element of the hyperlink 
                        // that has a name. Tree walking is a time consuming action, so would be
                        // avoided by a shipping app if alternatives like FindFirst, FindAll, or 
                        // BuildUpdatedCache could get the required data.

                        IUIAutomationTreeWalker controlWalker = _automation.ControlViewWalker;
                        
                        IUIAutomationElement elementChild = controlWalker.GetFirstChildElement(element);
                        while (elementChild != null)
                        {
                            string strNameChild = elementChild.CurrentName;
                            if ((strNameChild != null) && (strNameChild.Length > 0))
                            {
                                // Use the name of this child element.
                                strName = strNameChild;
                                break;
                            }

                            // Continue to the next child element.
                            elementChild = controlWalker.GetNextSiblingElement(elementChild);
                        }
                    }
                }
            }

            // While this sample doesn't use the destination of the hyperlink, if it wanted 
            // to get the destination, that might be available as the Value property of the 
            // element. The Value property is part of the Value pattern, and so is accessed
            // through the Value pattern.

            // Check first whether the element supports the Value pattern.
            IUIAutomationValuePattern valuePattern = (IUIAutomationValuePattern)element.GetCurrentPattern(_patternIdValue);
            if (valuePattern != null)
            {
                string strValue = valuePattern.CurrentValue;

                // This is where the destination of the link would be used...
            }

            return strName;
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // HighlightLink()
        //
        // Highlight the location of a hyperlink in the browser window on the screen.
        //
        // Runs on the background thread.
        //
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        private void HighlightLinkInternal(IUIAutomationElement elementLink, bool fUseCache)
        {
            try
            {
                tagRECT rect = elementLink.CurrentBoundingRectangle;

                Rectangle rectHighlight = new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);

                _highlight.HighlightRect(rectHighlight);
            }
            catch
            {
                // If an exception is throw trying to access the element, do nothing. This will 
                // occur if the element no longer exists, (eg the browser window has been closed.) 
            }
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // InvokeLinkInternal()
        //
        // Invoke a hyperlink in the browser window.
        //
        // Runs on the background thread.
        //
        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        private void InvokeLinkInternal(IUIAutomationElement elementLink, bool fUseCache)
        {
            if (elementLink != null)
            {
                IUIAutomationInvokePattern pattern = null;

                int iPatternId = _patternIdInvoke;

                // Will we be calling the Invoke() method through the Invoke pattern. 
                // So first get the pattern for the hyperlink element.
                if (fUseCache)
                {
                    // This does not result in a cross-proc call here, and should not fail.
                    pattern = (IUIAutomationInvokePattern)elementLink.GetCachedPattern(iPatternId);
                }
                else
                {
                    // This will fail if the element no longer exists.
                    try
                    {
                        pattern = (IUIAutomationInvokePattern)elementLink.GetCurrentPattern(iPatternId);
                    }
                    catch
                    {
                        // If an exception is throw trying to access the element, do nothing. This will 
                        // occur if the element no longer exists, (eg the browser window has been closed.) 
                    }
                }

                if (pattern != null)
                {
                    pattern.Invoke();
                }
            }
        }

        //////////////////////////////////////////////////////////////////////////////////////////////////////////////
        //
        // 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;
        }
    }
}