Skip to content

10. Advanced: Caching

Descolada edited this page Feb 10, 2023 · 5 revisions

Introduction

Usually getting data from UIA happens in the following steps:

  1. We send a request to UIA, for example for element.Name or FindElement. Sending this call happens quite fast.
  2. UIA does a cross-process call to the actual window/process for the data. This action is quite slow.
  3. UIA returns the result to us. Again, this happens fast.

When doing multiple calls for properties or patterns, then the second step where the cross-process call happens may result in long delays, so we would like to avoid doing that as much as possible. One way is batching Find calls into one by creating a condition that returns all elements of interest to us at once. For example, if we need to use both Username and Password textboxes, then instead of FindElement({Type:"Edit", Name:"Username"}) and FindElement({Type:"Edit", Name:"Password"}), we could instead use FindElements({Type:"Edit", or:[{Name:"Username"}, {Name:"Password"}]}).

An another way is using caching, which means pre-fetching of data. This means that UIA will pre-fetch elements with only the properties of interest, which we can then search through much faster, since the data can be accessed without further cross-process communication (fast). Caching is typically used to retrieve properties and control patterns in bulk, and information is then retrieved from the cache as needed.

Read more about caching in Microsofts' documentation.

Using caching

  1. Create a new cache request with cacheRequest := UIA.CreateCacheRequest(properties?, patterns?, scope?, mode?, filter?), this returns a new CacheRequest object
  • properties can be an array of property names or UIA.Property values
  • patterns can be an array of pattern names or UIA.Pattern values
  • scope can be a UIA.TreeScope value, this defines the TreeScope of which elements to cache
  • mode can be a UIA.AutomationElementMode value. This defines whether to cache with a full reference to the element (UIA.AutomationElementMode.Full, which means that current values can be accessed as well, and patterns can be used normally), or no reference to the underlying element (UIA.AutomationElementMode.None, which means current values can't be accessed and pattern methods can't be used; this is usually gives a ~1.5-2 fold speed advantage)
  • filter can be a condition object, which selects which elements to cache
  1. Build the cache by providing a Find or TreeWalker method with the CacheRequest, for example cachedElement := FindElement(condition,,,, cacheRequest)
  2. Now cached properties (eg cachedElement.CachedName), patterns (eg cachedElement.CachedWindowPattern) and children (cachedElement.GetCachedChildren()) can be accessed.

You can also add properties and patterns you wish to cache to the CacheRequest with CacheRequest.AddPattern(pattern) or CacheRequest.AddProperty(property), specify the scope (UIA.TreeScope) of the cache request with CacheRequest.TreeScope := scope, AutomationElementMode with CacheRequest.AutomationElementMode, and TreeFilter with CacheRequest.TreeFilter.

Cached tree

When using caching, UIA is essentially containing two trees: one tree for Current elements (the "live" elements), and another tree for Cached elements. These trees can look the same, or they can be totally different depending of the TreeFilter used. For example, if CacheRequest.TreeFilter := {Type:"Button"} is used, then the tree will consist of only Button-type elements.

This also means that we can't look for elements simultaneously in both trees. FindElement(s) is the search method for finding Current elements, and it won't use Cached properties to do that. FindCachedElement(s) on the other hand will use only Cached properties and will throw an error if the Cached property isn't available.

Scope and filtering of caching

You can specify the elements whose properties and patterns you want to cache by using CacheRequest.TreeScope := scope before activating the request, or by setting the scope argument when creating the CacheRequest. The scope is relative to the elements that are retrieved while the request is active: for example, if you set only UIA.TreeScope.Children, and then retrieve an element, the properties and patterns of children of that element are cached, but not those of the element itself, nor elements that are children of the children. It is not possible to set the scope to Parent or Ancestors.

The extent of caching is also affected by the CacheRequest.TreeFilter property. By default, caching is performed only for elements that appear in the control view of the UIA tree. However, you can change this property with CacheRequest.TreeFilter := condition to apply caching to all elements, or only to elements that match the condition.

Strength of the element references

When you retrieve an element, by default you also have access to all Current properties and patterns of that element, including those that were not cached. However, for greater efficiency you can specify that the reference to the element refers to cached data only, by setting the CacheRequest.AutomationElementMode property of the CacheRequest to UIA.AutomationElementMode.None. In this case, you do not have access to any non-cached properties and patterns of retrieved elements. This means that you cannot access any properties or patterns without the "Cached" prefix. On cached patterns, you can call methods that retrieve array properties, such as SelectionPattern.GetSelection, but not any that perform actions on the control, such as InvokePattern.Invoke.

An example of an application that might not need full references to objects is a screen reader, which would prefetch the Name and Type properties of elements in a window, but would not need the element objects themselves.

Updating the cache

The cache is valid only as long as nothing changes in the UI. Your application is responsible for updating the cache, typically in response to events.

If you subscribe to an event while a CacheRequest is active, you obtain an Element with an updated cache as the source of the event whenever the event-handler function is called. You can also update cached information for an element by calling Element := Element.BuildUpdatedCache(cacheRequest). BuildUpdatedCache will return a new element containing the updated Cached properties.

Examples

CachedDump

#Include <UIA>

cacheRequest := UIA.CreateCacheRequest()
cacheRequest.TreeScope := 5 ; Set TreeScope to include the starting element and all descendants as well
; Add all the necessary properties that DumpAll uses: ControlType, LocalizedControlType, AutomationId, Name, Value, ClassName, AcceleratorKey
cacheRequest.AddProperty("ControlType") 
cacheRequest.AddProperty("LocalizedControlType")
cacheRequest.AddProperty("AutomationId")
cacheRequest.AddProperty("Name")
cacheRequest.AddProperty("Value")
cacheRequest.AddProperty("ClassName")
cacheRequest.AddProperty("AcceleratorKey")

; To use cached patterns, first add the pattern
cacheRequest.AddPattern("Window")
; Also need to add any pattern properties we wish to use
cacheRequest.AddProperty("WindowCanMaximize")

Run "notepad.exe"
WinWaitActive "ahk_exe notepad.exe"

; Get element and also build the cache
npEl:= UIA.ElementFromHandle("ahk_exe notepad.exe", cacheRequest)
; We now have a cached "snapshot" of the window from which we can access our desired elements faster.
MsgBox npEl.CachedDump()
MsgBox npEl.CachedWindowPattern.CachedCanMaximize