Accv2库+遍历工具.ahk2
#Requires AutoHotkey v2 /* Accessibility library for AHK v2 Authors: Sean, jethrow, Sancarn (v1 code), Descolada Special thanks to Lexikos for many tips about v2 Short introduction: Acc v2 in not a port of v1, but instead a complete redesign to incorporate more object-oriented approaches. Notable changes: 1) All Acc elements are now array-like objects, where the "Length" property contains the number of children, any nth children can be accessed with element[n], and children can be iterated over with for loops. 2) Acc main functions are contained in the global Acc object 3) Element methods are contained inside element objects 4) Element properties can be used without the "acc" prefix 5) ChildIds have been removed (are handled in the backend), but can be accessed through Element.ChildId 6) Additional methods have been added for elements, such as FindElement, FindElements, Click 7) Acc constants are included in the main Acc object 8) AccViewer is built into the library: when ran directly the AccViewer will show, when included in another script then it won't show (but can be opened by calling Acc.Viewer()) Acc constants/properties: Constants can be accessed as properties (eg Acc.ObjId.Caret), or the property name can be fetched by getting as an item (eg Acc.ObjId[0xFFFFFFF8]) ObjId - object identifiers that identify categories of accessible objects within a window. State - used to describe the state of objects in an application UI. These are returned by Element.State or Element.StateText. Role - used to describe the roles of various UI objects in an application. These are returned by Element.Role or Element.RoleText. NavDir - indicate the spatial (up, down, left, and right) or logical (first child, last, next, and previous) direction used with Element.Navigate() to navigate from one user interface element to another within the same container. SelectionFlag - used to specify how an accessible object becomes selected or takes the focus. These are used by Element.Select(). Event - events that are generated by the operating system and by applications. These are used when dealing with RegisterWinEvent. More thorough explanations for the constants are available in Microsoft documentation: https://docs.microsoft.com/en-us/windows/win32/winauto/constants-and-enumerated-types Acc methods: ElementFromPoint(x:=unset, y:=unset, activateChromium := True) Gets an Acc element from screen coordinates X and Y (NOT relative to the active window). ElementFromHandle(hWnd:="", idObject := "Window", activateChromium := True) Gets an Acc element from a WinTitle, by default the Last Found Window. Additionally idObject can be specified from Acc.ObjId constants (eg to get the Caret location). ElementFromChromium(hWnd:="", activateChromium := True) Gets an Acc element for the Chromium render control, by default for the Last Found Window. GetRootElement() Gets the Acc element for the Desktop ActivateChromiumAccessibility(hWnd:="") Sends the WM_GETOBJECT message to the Chromium document element and waits for the app to be accessible to Acc. This is called when ElementFromPoint or ElementFromHandle activateChromium flag is set to True. A small performance increase may be gotten if that flag is set to False when it is not needed. RegisterWinEvent(event, callback, PID:=0) RegisterWinEvent(eventMin, eventMax, callback, PID:=0) Registers an event or event range from Acc.Event to a callback function and returns a new object containing the WinEventHook EventMax is an optional variable: if only eventMin and callback are provided, then only that single event is registered. If all three arguments are provided, then an event range from eventMin to eventMax are registered to the callback function. The callback function needs to have two arguments: CallbackFunction(oAcc, EventInfo) When the callback function is called: oAcc will be the Acc element that called the event EventInfo will be an object containing the following properties: Event - an Acc.Event constant EventTime - when the event was triggered in system time WinID - handle of the window that sent the event ControlID - handle of the control that sent the event, which depending on the window will be the window itself or a control ObjId - the object Id (Acc.ObjId) the event was called with PID is the Process ID of the process/window the events will be registered from. By default events from all windows are registered. Unhooking of the event handler will happen once the returned object is destroyed (either when overwritten by a constant, or when the script closes). ClearHighlights() Removes all highlights created by IAccessible.Highlight Legacy methods: SetWinEventHook(eventMin, eventMax, pCallback) UnhookWinEvent(hHook) ElementFromPath(ChildPath, hWnd:="A") => Same as ElementFromHandle[comma-separated path] GetRoleText(nRole) => Same as element.RoleText GetStateText(nState) => Same as element.StateText Query(pAcc) => For internal use IAccessible element properties: Element[n] => Gets the nth element. Multiple of these can be used like a path: Element[4,1,4] will select 4th childs 1st childs 4th child Path string can also be used with comma-separated numbers or RoleText Element["4,window,4"] will select 4th childs first RoleText=window childs 4th child Element["4,window2,4"] will select the second RoleText=window With a path string "p" followed by a number n will return the nth parent. Element["p2,2"] will get the parent parents second child Conditions (see ValidateCondition) are supported: Element[4,{Name:"Something"}] will select the fourth childs first child matching the name "Something" Conditions also accept an index (or i) parameter to select from multiple similar elements Element[{Name:"Something", i:3}] selects the third element of elements with name "Something" Negative index will select from the last element Element[{Name:"Something", i:-1}] selects the last element of elements with name "Something" Since index/i needs to be a key-value pair, then to use it with an "or" condition it must be inside an object ("and" condition), for example with key "or": Element[{or:[{Name:"Something"},{Name:"Something else"}], i:2}] Name => Gets or sets the name. All objects support getting this property. Value => Gets or sets the value. Not all objects have a value. Role => Gets the Role of the specified object in integer form. All objects support this property. RoleText => Role converted into text form. All objects support this property. Help => Retrieves the Help property string of an object. Not all objects support this property. KeyboardShortcut => Retrieves the specified object's shortcut key or access key. Not all objects support this property. State => Retrieves the current state in integer form. All objects support this property. StateText => State converted into text form Description => Retrieves a string that describes the visual appearance of the specified object. Not all objects have a description. DefaultAction => Retrieves a string that indicates the object's default action. Not all objects have a default action. Focus => Returns the focused child element (or itself). If no child is focused, an error is thrown Selection => Retrieves the selected children of this object. All objects that support selection must support this property. Parent => Returns the parent element. All objects support this property. IsChild => Checks whether the current element is of child type Length => Returns the number of children the element has Location => Returns the object's current screen location in an object {x,y,w,h} Children => Returns all children as an array (usually not required) Exists => Checks whether the element is still alive and accessible ControlID => ID (hwnd) of the control associated with the element WinID => ID (hwnd) of the window the element belongs to accessible => ComObject of the underlying IAccessible (for internal use) childId => childId of the underlying IAccessible (for internal use) IAccessible element methods: Select(flags) Modifies the selection or moves the keyboard focus of the specified object. flags can be any of the Acc.SelectionFlag constants DoDefaultAction() Performs the specified object's default action. Not all objects have a default action. GetNthChild(n) This is equal to element[n] GetPath(oTarget) Returns the path from the current element to oTarget element. The returned path is a comma-separated list of integers corresponding to the order the Acc tree needs to be traversed to access oTarget element from this element. If no path is found then an empty string is returned. GetLocation(relativeTo:="") Returns an object containing the x, y coordinates and width and height: {x:x coordinate, y:y coordinate, w:width, h:height}. relativeTo can be client, window or screen, default is A_CoordModeMouse. IsEqual(oCompare) Checks whether the element is equal to another element (oCompare) FindElement(condition, scope:=4, index:=1, order:=0, depth:=-1) Finds the first element matching the condition (see description under ValidateCondition). The returned element also has the "Path" property with the found elements path. Condition: A condition object (see ValidateCondition). This condition object can also contain named argument values: FindElement({Name:"Something", scope:"Subtree"}) Scope: the search scope (Acc.TreeScope value): Element, Children, Family (Element+Children), Descendants, SubTree (Element+Descendants). Default is Descendants. Index: can be used to search for i-th element. Like the other parameters, this can also be supplied in the condition with index or i: FindElement({Name:"Something", i:3}) finds the third element with name "Something" Negative index reverses the search direction: FindElement({Name:"Something", i:-1}) finds the last element with name "Something" Since index/i needs to be a key-value pair, then to use it with an "or" condition it must be inside an object ("and" condition), for example with key "or": FindElement({or:[{Name:"Something"}, {Name:"Something else"}], index:2}) Order: defines the order of tree traversal (Acc.TreeTraversalOptions value): Default, PostOrder, LastToFirst. Default is FirstToLast and PreOrder. FindElements(condition:=True, scope:=4, depth:=-1) Returns an array of elements matching the condition (see description under ValidateCondition) The returned elements also have the "Path" property with the found elements path By default matches any condition. WaitElement(conditionOrPath, timeOut:=-1, scope:=4, index:=1, order:=0, depth:=-1) Waits an element to be detectable in the Acc tree. This doesn't mean that the element is visible or interactable, use WaitElementExist for that. Timeout less than 1 waits indefinitely, otherwise is the wait time in milliseconds A timeout returns 0. WaitElementExist(conditionOrPath, timeOut:=-1, scope:=4, index:=1, order:=0, depth:=-1) Waits an element exist that matches a condition or a path. Timeout less than 1 waits indefinitely, otherwise is the wait time in milliseconds A timeout returns 0. WaitNotExist(timeOut:=-1) Waits for the element to not exist. Timeout less than 1 waits indefinitely, otherwise is the wait time in milliseconds A timeout returns 0. Normalize(condition) Checks whether the current element or any of its ancestors match the condition, and returns that element. If no element is found, an error is thrown. ValidateCondition(condition) Checks whether the element matches a provided condition. Everything inside {} is an "and" condition, or a singular condition with options Everything inside [] is an "or" condition "not" key creates a not condition "matchmode" key (short form: "mm") defines the MatchMode: StartsWith, Substring, Exact, RegEx (Acc.MATCHMODE values) "casesensitive" key (short form: "cs") defines case sensitivity: True=case sensitive; False=case insensitive Any other key (but recommended is "or") can be used to use "or" condition inside "and" condition. Additionally, when matching for location then partial matching can be used (eg only width and height) and relative mode (client, window, screen) can be specified with "relative" or "r". An empty object {} is used as "unset" or "N/A" value. For methods which use this condition, it can also contain named arguments: oAcc.FindElement({Name:"Something", scope:"Subtree", order:"LastToFirst"}) is equivalent to FindElement({Name:"Something"}, "Subtree",, "LastToFirst") is equivalent to FindElement({Name:"Something"}, Acc.TreeScope.SubTree,, Acc.TreeTraversalOptions.LastToFirst) is equivalent to FindElement({Name:"Something"}, 7,, 1) {Name:"Something"} => Name must match "Something" (case sensitive) {Name:"Something", matchmode:"SubString", casesensitive:False} => Name must contain "Something" anywhere inside the Name, case insensitive. matchmode:"SubString" == matchmode:2 == matchmode:Acc.MatchMode.SubString {Name:"Something", RoleText:"something else"} => Name must match "Something" and RoleText must match "something else" [{Name:"Something", Role:42}, {Name:"Something2", RoleText:"something else"}] => Name=="Something" and Role==42 OR Name=="Something2" and RoleText=="something else" {Name:"Something", not:[{RoleText:"something", mm:"Substring"}, {RoleText:"something else", cs:1}]} => Name must match "something" and RoleText cannot match "something" (with matchmode=Substring == matchmode=2) nor "something else" (casesensitive matching) {or:[{Name:"Something"},{Name:"Something else"}], or2:[{Role:20},{Role:42}]} {Location:{w:200, h:100, r:"client"}} => Location must match width 200 and height 100 relative to client Dump(scope:=1, delimiter:=" ", depth:=-1) {Name:{}} => Matches for no defined name (outputted by Dump as N/A) Outputs relevant information about the element (Name, Value, Location etc) Scope is the search scope (Acc.TreeScope value): Element, Children, Family (Element+Children), Descendants, SubTree (Element+Descendants). Default is Element. DumpAll(delimiter:=" ", depth:=-1) Outputs relevant information about the element and all descendants of the element. This is equivalent to Dump(7) Highlight(showTime:=unset, color:="Red", d:=2) Highlights the element for a chosen period of time Possible showTime values: Unset - highlights for 2 seconds, or removes the highlighting 0 - Indefinite highlighting. If the element object gets destroyed, so does the highlighting. Positive integer (eg 2000) - will highlight and pause for the specified amount of time in ms Negative integer - will highlight for the specified amount of time in ms, but script execution will continue "clear" - removes the highlight color can be any of the Color names or RGB values d sets the border width Click(WhichButton:="left", ClickCount:=1, DownOrUp:="", Relative:="", NoActivate:=False) Click the center of the element. If WhichButton is a number, then Sleep will be called with that number. Eg Click(200) will sleep 200ms after clicking If ClickCount is a number >=10, then Sleep will be called with that number. To click 10+ times and sleep after, specify "ClickCount SleepTime". Ex: Click("left", 200) will sleep 200ms after clicking. Ex: Click("left", "20 200") will left-click 20 times and then sleep 200ms. Relative can be offset values for the click (eg "-5 10" would offset the click from the center, X by -5 and Y by +10). NoActivate will cause the window not to be brought to focus before clicking if the clickable point is not visible on the screen. ControlClick(WhichButton:="left", ClickCount:=1, Options:="") ControlClicks the element after getting relative coordinates with GetLocation("client"). If WhichButton is a number, then a Sleep will be called afterwards. Ex: ControlClick(200) will sleep 200ms after clicking. Same for ControlClick("ahk_id 12345", 200) Navigate(navDir) Navigates in one of the directions specified by Acc.NavDir constants. Not all elements implement this method. HitTest(x, y) Retrieves the child element or child object that is displayed at a specific point on the screen. This shouldn't be used, since Acc.ElementFromPoint uses this internally Comments about design choices: 1) In this library accessing non-existant properties will cause an error to be thrown. This means that if AccViewer reports N/A as a value then the property doesn't exist, which is different from the property being "" or 0. Because of this, we have another way of differentiating/filtering elements, which may or may not be useful. 2) Methods that have Hwnd as an argument have the default value of Last Found Window. This is to be consistent with AHK overall. 3) Methods that will return a starting point Acc element usually have the activateChromium option set to True. This is because Chromium-based applications are quite common, but interacting with them via Acc require accessibility to be turned on. It makes sense to detect those windows automatically and activate by default (if needed). 4) ElementFromHandle: in AHK it may make more sense for idObject to have the default value of -4 (Acc.ObjId.Client), but using that by default will prevent access from the window title bar, which might be useful to have. Usually though, ObjId.Client will be enough, and when using control Hwnds then it must be used (otherwise the object for the window will be returned). */ #DllLoad oleacc ;DllCall("SetThreadDpiAwarenessContext", "ptr", -4, "ptr") ; For multi-monitor setups, otherwise coordinates might be reported wrong. if (!A_IsCompiled and A_LineFile=A_ScriptFullPath) Acc.Viewer() class Acc { static PropertyFromValue(obj, value) { for k, v in obj.OwnProps() if value == v return k throw UnsetItemError("Property item `"" value "`" not found!", -1) } static PropertyValueGetter := {get: (obj, value) => Acc.PropertyFromValue(obj, value)} static RegisteredWinEvents := Map() ; MatchMode constants used in condition objects static MatchMode := { StartsWith:1, Substring:2, Exact:3, RegEx:"Regex" } ; Used wherever the scope variable is needed (eg Dump, FindElement, FindElements) static TreeScope := { Element:1, Children:2, Family:3, Descendants:4, Subtree:7 } Static TreeTraversalOptions := { Default:0, PostOrder:1, LastToFirst:2, PostOrderLastToFirst:3 } ;Https://Msdn.Microsoft.Com/En-Us/Library/Windows/Desktop/Dd373606(V=Vs.85).Aspx Static ObjId := { Window:0x00000000, SysMenu:0xffffffff, TitleBar:0xfffffffe, Menu:0xfffffffd, Client:0xfffffffc, VScroll:0xfffffffb, HScroll:0xfffffffa, SizeGrip:0xfffffff9, Caret:0xfffffff8, Cursor:0xfffffff7, Alert:0xfffffff6, Sound:0xfffffff5, QueryClassNameIdx:0xfffffff4, NativeOM:0xfffffff0 }.Defineprop("__Item", This.PropertyValueGetter) ;Https://Msdn.Microsoft.Com/En-Us/Library/Windows/Desktop/Dd373609(V=Vs.85).Aspx Static State := { Normal:0, Unavailable:0x1, Selected:0x2, Focused:0x4, Pressed:0x8, Checked:0x10, Mixed:0x20, Indeterminate:0x20, ReadOnly:0x40, HotTracked:0x80, Default:0x100, Expanded:0x200, Collapsed:0x400, Busy:0x800, Floating:0x1000, Marqueed:0x2000, Animated:0x4000, Invisible:0x8000, Offscreen:0x10000, Sizeable:0x20000, Moveable:0x40000, SelfVoicing:0x80000, Focusable:0x100000, Selectable:0x200000, Linked:0x400000, Traversed:0x800000, MultiSelectable:0x1000000, ExtSelectable:0x2000000, Alert_Low:0x4000000, Alert_Medium:0x8000000, Alert_High:0x10000000, Protected:0x20000000, Valid:0x7fffffff }.Defineprop("__Item", This.PropertyValueGetter) ;Https://Msdn.Microsoft.Com/En-Us/Library/Windows/Desktop/Dd373608(V=Vs.85).Aspx Static Role := { TitleBar:0x1, MenuBar:0x2, ScrollBar:0x3, Grip:0x4, Sound:0x5, Cursor:0x6, Caret:0x7, Alert:0x8, Window:0x9, Client:0xa, MenuPopup:0xb, MenuItem:0xc, ToolTip:0xd, Application:0xe, Document:0xf, Pane:0x10, Chart:0x11, Dialog:0x12, Border:0x13, Grouping:0x14, Separator:0x15, Toolbar:0x16, StatusBar:0x17, Table:0x18, ColumnHeader:0x19, RowHeader:0x1a, Column:0x1b, Row:0x1c, Cell:0x1d, Link:0x1e, HelpBalloon:0x1f, Character:0x20, List:0x21, ListItem:0x22, Outline:0x23, OutlineItem:0x24, PageTab:0x25, PropertyPage:0x26, Indicator:0x27, Graphic:0x28, StaticText:0x29, Text:0x2a, PushButton:0x2b, CheckButton:0x2c, RadioButton:0x2d, ComboBox:0x2e, Droplist:0x2f, Progressbar:0x30, Dial:0x31, HotkeyField:0x32, Slider:0x33, SpinButton:0x34, Diagram:0x35, Animation:0x36, Equation:0x37, ButtonDropdown:0x38, ButtonMenu:0x39, ButtonDropdownGrid:0x3a, Whitespace:0x3b, PageTabList:0x3c, Clock:0x3d, SplitButton:0x3e, IPAddress:0x3f, OutlineButton:0x40 }.Defineprop("__Item", This.PropertyValueGetter) ;Https://, Msdn.Microsoft.Com/En-Us/Library/Windows/Desktop/Dd373600(V=Vs.85).Aspx Static NavDir := { Min:0x0, Up:0x1, Down:0x2, Left:0x3, Right:0x4, Next:0x5, Previous:0x6, FirstChild:0x7, LastChild:0x8, Max:0x9 }.Defineprop("__Item", This.PropertyValueGetter) ;Https://Msdn.Microsoft.Com/En-Us/Library/Windows/Desktop/Dd373634(V=Vs.85).Aspx Static SelectionFlag := { None:0x0, TakeFocus:0x1, TakeSelection:0x2, ExtendSelection:0x4, AddSelection:0x8, RemoveSelection:0x10, Valid:0x1f }.Defineprop("__Item", This.PropertyValueGetter) ;Msaa Events List: ; Https://Msdn.Microsoft.Com/En-Us/Library/Windows/Desktop/Dd318066(V=Vs.85).Aspx ;What Are Win Events: ; Https://Msdn.Microsoft.Com/En-Us/Library/Windows/Desktop/Dd373868(V=Vs.85).Aspx ;System-Level And Object-Level Events: ; Https://Msdn.Microsoft.Com/En-Us/Library/Windows/Desktop/Dd373657(V=Vs.85).Aspx ;Console Accessibility: ; Https://Msdn.Microsoft.Com/En-Us/Library/Ms971319.Aspx Static Event := { Min:0x00000001, Max:0x7fffffff, System_Sound:0x0001, System_Alert:0x0002, System_Foreground:0x0003, System_MenuStart:0x0004, System_MenuEnd:0x0005, System_MenuPopupStart:0x0006, System_MenuPopupEnd:0x0007, System_CaptureStart:0x0008, System_CaptureEnd:0x0009, System_MoveSizeStart:0x000a, System_MoveSizeEnd:0x000b, System_ContextHelpStart:0x000c, System_ContextHelpEnd:0x000d, System_DragDropStart:0x000e, System_DragDropEnd:0x000f, System_DialogStart:0x0010, System_DialogEnd:0x0011, System_ScrollingStart:0x0012, System_ScrollingEnd:0x0013, System_SwitchStart:0x0014, System_SwitchEnd:0x0015, System_MinimizeStart:0x0016, System_MinimizeEnd:0x0017, Console_Caret:0x4001, Console_Update_Region:0x4002, Console_Update_Simple:0x4003, Console_Update_Scroll:0x4004, Console_Layout:0x4005, Console_Start_Application:0x4006, Console_End_Application:0x4007, Object_Create:0x8000, Object_Destroy:0x8001, Object_Show:0x8002, Object_Hide:0x8003, Object_Reorder:0x8004, Object_Focus:0x8005, Object_Selection:0x8006, Object_SelectionAdd:0x8007, Object_SelectionRemove:0x8008, Object_SelectionWithin:0x8009, Object_StateChange:0x800a, Object_LocationChange:0x800b, Object_NameChange:0x800c, Object_DescriptionChange:0x800d, Object_ValueChange:0x800e, Object_ParentChange:0x800f, Object_HelpChange:0x8010, Object_DefactionChange:0x8011, Object_AcceleratorChange:0x8012, Object_Invoked:0x8013, Object_TextSelectionChanged:0x8014, Object_ContentScrolled:0x8015, System_ArrangmentPreview:0x8016, Object_Cloaked:0x8017, Object_Uncloaked:0x8018, Object_LiveRegionChanged:0x8019, Object_HostedObjectsInvalidated:0x8020, Object_DragStart:0x8021, Object_DragCancel:0x8022, Object_DragComplete:0x8023, Object_DragEnter:0x8024, Object_DragLeave:0x8025, Object_DragDropped:0x8026, Object_IME_Show:0x8027, Object_IME_Hide:0x8028, Object_IME_Change:0x8029, Object_TextEdit_ConversionTargetChanged:0x8030, Object_End:0x80FF }.Defineprop("__Item", This.PropertyValueGetter) Static WinEvent := { OutOfContext:0, SkipOwnThread:1, SkipOwnProcess:2, InContext:3 }.Defineprop("__Item", This.PropertyValueGetter) static __HighlightGuis := Map() class IAccessible { /** * Internal method. Creates an Acc element from a raw IAccessible COM object and/or childId, * and additionally stores the hWnd of the window the object belongs to. * @param accessible IAccessible COM object * @param childId IAccessible childId * @param wId hWnd of the parent window */ __New(accessible, childId:=0, wId:=0) { if ComObjType(accessible, "Name") != "IAccessible" throw Error("Could not access an IAccessible Object") this.DefineProp("ptr", {value:ComObjValue(accessible)}) this.DefineProp("accessible", {value:accessible}) this.DefineProp("childId", {value:childId}) if wId=0 try wId := this.WinID this.DefineProp("wId", {value:wId}) this.DefineProp("ObjPtr", {value:ObjPtr(this)}) } __Delete() { if Acc.__HighlightGuis.Has(this.ObjPtr) { for _, r in Acc.__HighlightGuis[this.ObjPtr] r.Destroy() Acc.__HighlightGuis[this.ObjPtr] := [] } } /** * Internal method. Is a wrapper to access acc properties or methods that take only the * childId as an input value. This usually shouldn't be called, unless the user is * trying to access a property/method that is undefined in this library. */ __Get(Name, Params) { if !(SubStr(Name,3)="acc") { try return this.accessible.acc%Name%[this.childId] try return this.accessible.acc%Name%(this.childId) ; try method with self try return this.accessible.acc%Name% ; try property } try return this.accessible.%Name%[this.childId] try return this.accessible.%Name%(this.childId) return this.accessible.%Name% } /** * Enables array-like use of Acc elements to access child elements. * If value is an integer then the nth corresponding child will be returned. * If value is a string, then it will be parsed as a comma-separated path which * allows both indexes (nth child) and RoleText values. * If value is an object, then it will be used in a FindElement call with scope set to Children. * @returns {Acc.IAccessible} */ __Item[params*] { get { oAcc := this for _, param in params { if IsInteger(param) oAcc := oAcc.GetNthChild(param) else if IsObject(param) oAcc := oAcc.FindElement(param, 2) else if Type(param) = "String" oAcc := Acc.ElementFromPath(param, oAcc, False) else TypeError("Invalid item type!", -1) } return oAcc } } /** * Enables enumeration of Acc elements, usually in a for loop. * Usage: * for [index, ] oChild in oElement */ __Enum(varCount) { maxLen := this.Length, i := 0, children := this.Children EnumElements(&element) { if ++i > maxLen return false element := children[i] return true } EnumIndexAndElements(&index, &element) { if ++i > maxLen return false index := i element := children[i] return true } return (varCount = 1) ? EnumElements : EnumIndexAndElements } /** * Internal method. Enables setting IAccessible acc properties. */ __Set(Name, Params, Value) { if !(SubStr(Name,3)="acc") try return this.accessible.acc%Name%[Params*] := Value return this.accessible.%Name%[Params*] := Value } /** * Internal method. Enables setting IAccessible acc properties. */ __Call(Name, Params) { if !(SubStr(Name,3)="acc") try return this.accessible.acc%Name%(Params.Length?Params[1]:0) return this.accessible.%Name%(Params*) } ; Wrappers for native IAccessible methods and properties. /** * Modifies the selection or moves the keyboard focus of the specified object. * Objects that support selection or receive the keyboard focus should support this method. * @param flags One of the SelectionFlag constants */ Select(flags) => (this.accessible.accSelect(IsInteger(flags) ? flags : Acc.SelectionFlag.%flags%,this.childId)) /** * Performs the specified object's default action. Not all objects have a default action. */ DoDefaultAction() => (this.accessible.accDoDefaultAction(this.childId)) /** * Retrieves the child element or child object that is displayed at a specific point on the screen. * This method usually shouldn't be called. To get the accessible object that is displayed at a point, * use the ElementFromPoint method, which calls this method internally on native IAccessible side. */ HitTest(x, y) => (this.IAccessibleFromVariant(this.accessible.accHitTest(x, y))) /** * Traverses to another UI element within a container and retrieves the object. * This method is deprecated and should not be used. * @param navDir One of the NavDir constants. * @returns {Acc.IAccessible} */ Navigate(navDir) { navDir := IsInteger(navDir) ? navDir : Acc.NavDir.%navDir% varEndUpAt := this.accessible.accNavigate(navDir,this.childId) if Type(varEndUpAt) = "ComObject" return Acc.IAccessible(Acc.Query(varEndUpAt)) else if IsInteger(varEndUpAt) return Acc.IAccessible(this.accessible, varEndUpAt, this.wId) else return } Name { get => (this.accessible.accName[this.childId]) set => (this.accessible.accName[this.childId] := Value) } Value { get => (this.accessible.accValue[this.childId]) set => (this.accessible.accValue[this.childId] := Value) } Role => (this.accessible.accRole[this.childId]) ; Returns an integer RoleText => (Acc.GetRoleText(this.Role)) ; Returns a string Help => (this.accessible.accHelp[this.childId]) KeyboardShortcut => (this.accessible.accKeyboardShortcut[this.childId]) State => (this.accessible.accState[this.childId]) ; Returns an integer StateText => (Acc.GetStateText(this.accessible.accState[this.childId])) ; Returns a string Description => (this.accessible.accDescription[this.childId]) ; Returns a string DefaultAction => (this.accessible.accDefaultAction[this.childId]) ; Returns a string ; Retrieves the Acc element child that has the keyboard focus. Focus => (this.IAccessibleFromVariant(this.accessible.accFocus())) ; Returns an array of Acc elements that are the selected children of this object. Selection => (this.IAccessibleFromVariant(this.accessible.accSelection())) ; Returns the parent of this object as an Acc element Parent => (this.IsChild ? Acc.IAccessible(this.accessible,,this.wId) : Acc.IAccessible(Acc.Query(this.accessible.accParent))) ; Returns the Hwnd for the control corresponding to this object ControlID { get { if DllCall("oleacc\WindowFromAccessibleObject", "Ptr", this, "uint*", &hWnd:=0) = 0 return hWnd throw Error("WindowFromAccessibleObject failed", -1) } } ; Returns the Hwnd for the window corresponding to this object WinID { get { if DllCall("oleacc\WindowFromAccessibleObject", "Ptr", this, "uint*", &hWnd:=0) = 0 return DllCall("GetAncestor", "UInt", hWnd, "UInt", GA_ROOT := 2) throw Error("WindowFromAccessibleObject failed", -1) } } ; Checks whether internally this corresponds to a native IAccessible object or a childId IsChild => (this.childId == 0 ? False : True) ; Checks whether this object is selected. This is very slow, a better alternative is Element.Selection IsSelected { get { try oSel := this.Parent.Selection return IsSet(oSel) && this.IsEqual(oSel) } } ; Returns the child count of this object Length => (this.childId == 0 ? this.accessible.accChildCount : 0) ; Checks whether this object still exists and is visible/accessible Exists { get { try { if ((state := this.State) == 32768) || (state == 1) || (((pos := this.Location).x==0) && (pos.y==0) && (pos.w==0) && (pos.h==0)) return 0 } catch return 0 return 1 } } /** * Returns an object containing the location of this element * @returns {Object} {x: screen x-coordinate, y: screen y-coordinate, w: width, h: height} */ Location { get { x:=Buffer(4, 0), y:=Buffer(4, 0), w:=Buffer(4, 0), h:=Buffer(4, 0) this.accessible.accLocation(ComValue(0x4003, x.ptr, 1), ComValue(0x4003, y.ptr, 1), ComValue(0x4003, w.ptr, 1), ComValue(0x4003, h.ptr, 1), this.childId) Return {x:NumGet(x,0,"int"), y:NumGet(y,0,"int"), w:NumGet(w,0,"int"), h:NumGet(h,0,"int")} } } ; Returns all children of this object as an array of Acc elements Children { get { if this.IsChild || !(cChildren := this.accessible.accChildCount) return [] Children := Array(), varChildren := Buffer(cChildren * (8+2*A_PtrSize)) try { if DllCall("oleacc\AccessibleChildren", "ptr", this, "int",0, "int", cChildren, "ptr", varChildren, "int*", cChildren) > -1 { Loop cChildren { i := (A_Index-1) * (A_PtrSize * 2 + 8) + 8 child := NumGet(varChildren, i, "ptr") Children.Push(NumGet(varChildren, i-8, "ptr") = 9 ? Acc.IAccessible(Acc.Query(child),,this.wId) : Acc.IAccessible(this.accessible, child, this.wId)) NumGet(varChildren, i-8, "ptr") = 9 ? ObjRelease(child) : "" } Return Children } } throw Error("AccessibleChildren DllCall Failed", -1) } } /** * Internal method. Used to convert a variant returned by native IAccessible to * an Acc element or an array of Acc elements. */ IAccessibleFromVariant(var) { if Type(var) = "ComObject" return Acc.IAccessible(Acc.Query(var),,this.wId) else if Type(var) = "Enumerator" { oArr := [] Loop { if var.Call(&childId) oArr.Push(this.IAccessibleFromVariant(childId)) else return oArr } } else if IsInteger(var) return Acc.IAccessible(this.accessible,var,this.wId) else return var } ; Returns the nth child of this element. Equivalent to Element[n] GetNthChild(n) { if !IsNumber(n) throw TypeError("Child must be an integer", -1) n := Integer(n) cChildren := this.accessible.accChildCount if n > cChildren throw IndexError("Child index " n " is out of bounds", -1) varChildren := Buffer(cChildren * (8+2*A_PtrSize)) try { if DllCall("oleacc\AccessibleChildren", "ptr", this, "int",0, "int",cChildren, "ptr",varChildren, "int*",cChildren) > -1 { if n < 1 n := cChildren + n + 1 if n < 1 || n > cChildren throw IndexError("Child index " n " is out of bounds", -1) i := (n-1) * (A_PtrSize * 2 + 8) + 8 child := NumGet(varChildren, i, "ptr") oChild := NumGet(varChildren, i-8, "ptr") = 9 ? Acc.IAccessible(Acc.Query(child),,this.wId) : Acc.IAccessible(this.accessible, child, this.wId) NumGet(varChildren, i-8, "ptr") = 9 ? ObjRelease(child) : "" Return oChild } } throw Error("AccessibleChildren DllCall Failed", -1) } /** * Returns the path from the current element to oTarget element. * The returned path is a comma-separated list of integers corresponding to the order the * IAccessible tree needs to be traversed to access oTarget element from this element. * If no path is found then an empty string is returned. * @param oTarget An Acc element. */ GetPath(oTarget) { if Type(oTarget) != "Acc.IAccessible" throw TypeError("oTarget must be a valid Acc element!", -1) oNext := oTarget, oPrev := oTarget, path := "" try { while !this.IsEqual(oNext) for i, oChild in oNext := oNext.Parent { if oChild.IsEqual(oPrev) { path := i "," path, oPrev := oNext break } } path := SubStr(path, 1, -1) if Acc.ElementFromPath(path, this, False).IsEqual(oTarget) return path } oFind := this.FindElement({IsEqual:oTarget}) return oFind ? oFind.Path : "" } /** * Returns an object containing the x, y coordinates and width and height: {x:x coordinate, y:y coordinate, w:width, h:height}. * @param relativeTo Coordinate mode, which can be client, window or screen. Default is A_CoordModeMouse. * @returns {Object} {x: relative x-coordinate, y: relative y-coordinate, w: width, h: height} */ GetLocation(relativeTo:="") { relativeTo := (relativeTo == "") ? A_CoordModeMouse : relativeTo, loc := this.Location if (relativeTo = "screen") return loc else if (relativeTo = "window") { RECT := Buffer(16) DllCall("user32\GetWindowRect", "Int", this.wId, "Ptr", RECT) return {x:(loc.x-NumGet(RECT, 0, "Int")), y:(loc.y-NumGet(RECT, 4, "Int")), w:loc.w, h:loc.h} } else if (relativeTo = "client") { pt := Buffer(8), NumPut("int",loc.x,pt), NumPut("int",loc.y,pt,4) DllCall("ScreenToClient", "Int", this.wId, "Ptr", pt) return {x:NumGet(pt,0,"int"), y:NumGet(pt,4,"int"), w:loc.w, h:loc.h} } else throw Error(relativeTo "is not a valid CoordMode",-1) } /** * Checks whether this element is equal to another element * @param oCompare The Acc element to be compared against. */ IsEqual(oCompare) { loc1 := {x:0,y:0,w:0,h:0}, loc2 := {x:0,y:0,w:0,h:0} try loc1 := this.Location catch { ; loc1 unset loc1 := {x:0,y:0,w:0,h:0} try return oCompare.Location && 0 ; if loc2 is set then code will return } try loc2 := oCompare.Location if (loc1.x != loc2.x) || (loc1.y != loc2.y) || (loc1.w != loc2.w) || (loc1.h != loc2.h) return 0 for _, v in ((loc1.x = 0) && (loc1.y = 0) && (loc1.w = 0) && (loc1.h = 0)) ? ["Role", "Value", "Name", "State", "DefaultAction", "Description", "KeyboardShortcut", "Help"] : ["Role", "Name"] { try v1 := this.%v% catch { ; v1 unset try v2 := oCompare.%v% catch ; both unset, continue continue return 0 ; v1 unset, v2 set } try v2 := oCompare.%v% catch ; v1 set, v2 unset return 0 if v1 != v2 ; both set return 0 } return 1 } /** * Finds the first element matching a set of conditions. * The returned element also has a "Path" property with the found elements path * @param condition Condition object (see ValidateCondition) * @param scope The search scope (Acc.TreeScope value): Element, Children, Family (Element+Children), Descendants, SubTree (Element+Descendants). Default is Descendants. * @param index Looks for the n-th element matching the condition * @param order The order of tree traversal (Acc.TreeTraversalOptions value): Default, LastToFirst, PostOrder. Default is FirstToLast and PreOrder. * @param depth Maximum level of depth for the search. Default is no limit. * @returns {Acc.IAccessible} */ FindElement(condition, scope:=4, index:=1, order:=0, depth:=-1) { if IsObject(condition) { for key in ["index", "scope", "depth", "order"] if condition.HasOwnProp(key) %key% := condition.%key% if condition.HasOwnProp("i") index := condition.i if index < 0 order |= 2, index := -index else if index = 0 throw Error("Condition index cannot be 0", -1) } scope := IsInteger(scope) ? scope : Acc.TreeScope.%scope%, order := IsInteger(order) ? order : Acc.TreeTraversalOptions.%order% if order&1 return order&2 ? PostOrderLastToFirstRecursiveFind(this, condition, scope,, ++depth) : PostOrderFirstToLastRecursiveFind(this, condition, scope,, ++depth) if scope&1 if this.ValidateCondition(condition) && (--index = 0) return this.DefineProp("Path", {value:""}) if scope>1 return order&2 ? PreOrderLastToFirstRecursiveFind(this, condition, scope,, depth) : PreOrderFirstToLastRecursiveFind(this, condition, scope,,depth) PreOrderFirstToLastRecursiveFind(element, condition, scope:=4, path:="", depth:=-1) { --depth for i, child in element.Children { if child.ValidateCondition(condition) && (--index = 0) return child.DefineProp("Path", {value:path (path?",":"") i}) else if (scope&4) && (depth != 0) && (rf := PreOrderFirstToLastRecursiveFind(child, condition,, path (path?",":"") i, depth)) return rf } } PreOrderLastToFirstRecursiveFind(element, condition, scope:=4, path:="", depth:=-1) { children := element.Children, length := children.Length + 1, --depth Loop (length - 1) { child := children[length-A_index] if child.ValidateCondition(condition) && (--index = 0) return child.DefineProp("Path", {value:path (path?",":"") (length-A_index)}) else if scope&4 && (depth != 0) && (rf := PreOrderLastToFirstRecursiveFind(child, condition,, path (path?",":"") (length-A_index), depth)) return rf } } PostOrderFirstToLastRecursiveFind(element, condition, scope:=4, path:="", depth:=-1) { if (--depth != 0) && scope>1 { for i, child in element.Children { if (rf := PostOrderFirstToLastRecursiveFind(child, condition, (scope & ~2)|1, path (path?",":"") i, depth)) return rf } } if scope&1 && element.ValidateCondition(condition) && (--index = 0) return element.DefineProp("Path", {value:path}) } PostOrderLastToFirstRecursiveFind(element, condition, scope:=4, path:="", depth:=-1) { if (--depth != 0) && scope>1 { children := element.Children, length := children.Length + 1 Loop (length - 1) { if (rf := PostOrderLastToFirstRecursiveFind(children[length-A_index], condition, (scope & ~2)|1, path (path?",":"") (length-A_index), depth)) return rf } } if scope&1 && element.ValidateCondition(condition) && (--index = 0) return element.DefineProp("Path", {value:path}) } } FindFirst(args*) => this.FindElement(args*) /** * Returns an array of elements matching the condition (see description under ValidateCondition) * The returned elements also have the "Path" property with the found elements path * @param condition Condition object (see ValidateCondition). Default is to match any condition. * @param scope The search scope (Acc.TreeScope value): Element, Children, Family (Element+Children), Descendants, SubTree (Element+Descendants). Default is Descendants. * @param depth Maximum level of depth for the search. Default is no limit. * @returns {[Acc.IAccessible]} */ FindElements(condition:=True, scope:=4, depth:=-1) { if Type(condition) = "Object" { if condition.HasOwnProp("scope") scope := condition.scope if condition.HasOwnProp("depth") depth := condition.depth } matches := [], ++depth, scope := IsInteger(scope) ? scope : Acc.TreeScope.%scope% if scope&1 if this.ValidateCondition(condition) matches.Push(this.DefineProp("Path", {value:""})) if scope>1 RecursiveFind(this, condition, (scope|1)^1, &matches,, depth) return matches RecursiveFind(element, condition, scope, &matches, path:="", depth:=-1) { if scope>1 { --depth for i, child in element { if child.ValidateCondition(condition) matches.Push(child.DefineProp("Path", {value:path (path?",":"") i})) if scope&4 && (depth != 0) RecursiveFind(child, condition, scope, &matches, path (path?",":"") i, depth) } } } } FindAll(args*) => this.FindElements(args*) /** * Waits for an element matching a condition or path to exist in the Acc tree. * Element being in the Acc tree doesn't mean it's necessarily visible or interactable, * use WaitElementExist for that. * @param conditionOrPath Condition object (see ValidateCondition), or Acc path as a string (comma-separated numbers) * @param timeOut Timeout in milliseconds. Default in indefinite waiting. * @param scope The search scope (Acc.TreeScope value): Element, Children, Family (Element+Children), Descendants, SubTree (Element+Descendants). Default is Descendants. * @param index Looks for the n-th element matching the condition * @param order The order of tree traversal (Acc.TreeTraversalOptions value): Default, PostOrder, LastToFirst. Default is FirstToLast and PreOrder. * @param depth Maximum level of depth for the search. Default is no limit. * @returns {Acc.IAccessible} */ WaitElement(conditionOrPath, timeOut:=-1, scope:=4, index:=1, order:=0, depth:=-1) { if Type(conditionOrPath) = "Object" && conditionOrPath.HasOwnProp("timeOut") timeOut := conditionOrPath.timeOut waitTime := A_TickCount + timeOut while ((timeOut < 1) ? 1 : (A_tickCount < waitTime)) { try return IsObject(conditionOrPath) ? this.FindElement(conditionOrPath, scope, index, depth) : this[conditionOrPath] Sleep 40 } } /** * Waits for an element matching a condition or path to appear. * @param conditionOrPath Condition object (see ValidateCondition), or Acc path as a string (comma-separated numbers) * @param timeOut Timeout in milliseconds. Default in indefinite waiting. * @param scope The search scope (Acc.TreeScope value): Element, Children, Family (Element+Children), Descendants, SubTree (Element+Descendants). Default is Descendants. * @param index Looks for the n-th element matching the condition * @param order The order of tree traversal (Acc.TreeTraversalOptions value): Default, LastToFirst, PostOrder. Default is FirstToLast and PreOrder. * @param depth Maximum level of depth for the search. Default is no limit. * @returns {Acc.IAccessible} */ WaitElementExist(conditionOrPath, timeOut:=-1, scope:=4, index:=1, order:=0, depth:=-1) { if Type(conditionOrPath) = "Object" && conditionOrPath.HasOwnProp("timeOut") timeOut := conditionOrPath.timeOut waitTime := A_TickCount + timeOut while ((timeOut < 1) ? 1 : (A_tickCount < waitTime)) { try { oFind := IsObject(conditionOrPath) ? this.FindElement(conditionOrPath, scope, index, depth) : this[conditionOrPath] if oFind.Exists return oFind } Sleep 40 } } /** * Waits for this element to not exist. Returns True if the element disappears before the timeout. * @param timeOut Timeout in milliseconds. Default in indefinite waiting. */ WaitNotExist(timeOut:=-1) { waitTime := A_TickCount + timeOut while ((timeOut < 1) ? 1 : (A_tickCount < waitTime)) { if !this.Exists return 1 Sleep 40 } } /** * Checks whether the current element or any of its ancestors match the condition, * and returns that Acc element. If no element is found, an error is thrown. * @param condition Condition object (see ValidateCondition) * @returns {Acc.IAccessible} */ Normalize(condition) { if this.ValidateCondition(condition) return this oEl := this Loop { try { oEl := oEl.Parent if oEl.ValidateCondition(condition) return oEl } catch break } return 0 } /* Checks whether the element matches a provided condition. Everything inside {} is an "and" condition Everything inside [] is an "or" condition Object key "not" creates a not condition matchmode key defines the MatchMode: StartsWith, Substring, Exact, RegEx (Acc.MATCHMODE values) casesensitive key defines case sensitivity: True=case sensitive; False=case insensitive {Name:"Something"} => Name must match "Something" (case sensitive) {Name:"Something", RoleText:"something else"} => Name must match "Something" and RoleText must match "something else" [{Name:"Something", Role:42}, {Name:"Something2", RoleText:"something else"}] => Name=="Something" and Role==42 OR Name=="Something2" and RoleText=="something else" {Name:"Something", not:[RoleText:"something", RoleText:"something else"]} => Name must match "something" and RoleText cannot match "something" nor "something else" */ ValidateCondition(oCond) { if !IsObject(oCond) return !!oCond ; if oCond is not an object, then it is treated as True or False condition if Type(oCond) = "Array" { ; or condition for _, c in oCond if this.ValidateCondition(c) return 1 return 0 } matchmode := 3, casesensitive := 1, notCond := False for p in ["matchmode", "mm"] if oCond.HasOwnProp(p) matchmode := oCond.%p% try matchmode := IsInteger(matchmode) ? matchmode : Acc.MATCHMODE.%matchmode% for p in ["casesensitive", "cs"] if oCond.HasOwnProp(p) casesensitive := oCond.%p% for prop, cond in oCond.OwnProps() { switch Type(cond) { ; and condition case "String", "Integer": if prop ~= "i)^(index|i|matchmode|mm|casesensitive|cs|scope|timeout)$" continue propValue := "" try propValue := this.%prop% switch matchmode, 0 { case 2: if !InStr(propValue, cond, casesensitive) return 0 case 1: if !((casesensitive && (SubStr(propValue, 1, StrLen(cond)) == cond)) || (!casesensitive && (SubStr(propValue, 1, StrLen(cond)) = cond))) return 0 case "Regex": if !(propValue ~= cond) return 0 default: if !((casesensitive && (propValue == cond)) || (!casesensitive && (propValue = cond))) return 0 } case "Acc.IAccessible": if (prop="IsEqual") ? !this.IsEqual(cond) : !this.ValidateCondition(cond) return 0 default: if (HasProp(cond, "Length") ? cond.Length = 0 : ObjOwnPropCount(cond) = 0) { try return this.%prop% && 0 catch return 1 } else if (prop = "Location") { try loc := cond.HasOwnProp("relative") ? this.GetLocation(cond.relative) : cond.HasOwnProp("r") ? this.GetLocation(cond.r) : this.Location catch return 0 for lprop, lval in cond.OwnProps() { if (!((lprop = "relative") || (lprop = "r")) && (loc.%lprop% != lval)) return 0 } } else if ((prop = "not") ? this.ValidateCondition(cond) : !this.ValidateCondition(cond)) return 0 } } return 1 } /** * Outputs relevant information about the element * @param scope The search scope: Element, Children, Family (Element+Children), Descendants, SubTree (Element+Descendants). Default is Element. * @param delimiter The delimiter separating the outputted properties * @param depth Maximum number of levels to dump. Default is no limit. * @returns {String} */ Dump(scope:=1, delimiter:=" ", depth:=-1) { out := "", scope := IsInteger(scope) ? scope : Acc.TreeScope.%scope% if scope&1 { RoleText := "N/A", Role := "N/A", Value := "N/A", Name := "N/A", StateText := "N/A", State := "N/A", DefaultAction := "N/A", Description := "N/A", KeyboardShortcut := "N/A", Help := "N/A", Location := {x:"N/A",y:"N/A",w:"N/A",h:"N/A"} for _, v in ["RoleText", "Role", "Value", "Name", "StateText", "State", "DefaultAction", "Description", "KeyboardShortcut", "Help", "Location"] try %v% := this.%v% out := "RoleText: " RoleText delimiter "Role: " Role delimiter "[Location: {x:" Location.x ",y:" Location.y ",w:" Location.w ",h:" Location.h "}]" delimiter "[Name: " Name "]" delimiter "[Value: " Value "]" (StateText ? delimiter "[StateText: " StateText "]" : "") (State ? delimiter "[State: " State "]" : "") (DefaultAction ? delimiter "[DefaultAction: " DefaultAction "]" : "") (Description ? delimiter "[Description: " Description "]" : "") (KeyboardShortcut ? delimiter "[KeyboardShortcut: " KeyboardShortcut "]" : "") (Help ? delimiter "[Help: " Help "]" : "") (this.childId ? delimiter "ChildId: " this.childId : "") "`n" } if scope&4 return Trim(RecurseTree(this, out,, depth), "`n") if scope&2 { for n, oChild in this.Children out .= n ":" delimiter oChild.Dump() "`n" } return Trim(out, "`n") RecurseTree(oAcc, tree, path:="", depth:=-1) { if depth > 0 { StrReplace(path, "," , , , &count) if count >= (depth-1) return tree } try { if !oAcc.Length return tree } catch return tree For i, oChild in oAcc.Children { tree .= path (path?",":"") i ":" delimiter oChild.Dump() "`n" tree := RecurseTree(oChild, tree, path (path?",":"") i, depth) } return tree } } /** * Outputs relevant information about the element and all its descendants. * @param delimiter The delimiter separating the outputted properties * @param depth Maximum number of levels to dump. Default is no limit. * @returns {String} */ DumpAll(delimiter:=" ", depth:=-1) => this.Dump(5, delimiter, depth) ; Same as Dump() ToString() => this.Dump() /** * Highlights the element for a chosen period of time. * @param showTime Can be one of the following: * Unset - highlights for 2 seconds, or removes the highlighting * 0 - Indefinite highlighting. If the element object gets destroyed, so does the highlighting. * Positive integer (eg 2000) - will highlight and pause for the specified amount of time in ms * Negative integer - will highlight for the specified amount of time in ms, but script execution will continue * "clear" - removes the highlight * @param color The color of the highlighting. Default is red. * @param d The border thickness of the highlighting in pixels. Default is 2. * @returns {Acc.IAccessible} */ Highlight(showTime:=unset, color:="Red", d:=2) { if !Acc.__HighlightGuis.Has(this.ObjPtr) Acc.__HighlightGuis[this.ObjPtr] := [] if (!IsSet(showTime) && Acc.__HighlightGuis[this.ObjPtr].Length) || (IsSet(showTime) && showTime = "clear") { for _, r in Acc.__HighlightGuis[this.ObjPtr] r.Destroy() Acc.__HighlightGuis[this.ObjPtr] := [] return this } else if !IsSet(showTime) showTime := 2000 try loc := this.Location if !IsSet(loc) || !IsObject(loc) return this Loop 4 { Acc.__HighlightGuis[this.ObjPtr].Push(Gui("+AlwaysOnTop -Caption +ToolWindow -DPIScale +E0x08000000")) } Loop 4 { i:=A_Index , x1:=(i=2 ? loc.x+loc.w : loc.x-d) , y1:=(i=3 ? loc.y+loc.h : loc.y-d) , w1:=(i=1 or i=3 ? loc.w+2*d : d) , h1:=(i=2 or i=4 ? loc.h+2*d : d) Acc.__HighlightGuis[this.ObjPtr][i].BackColor := color Acc.__HighlightGuis[this.ObjPtr][i].Show("NA x" . x1 . " y" . y1 . " w" . w1 . " h" . h1) } if showTime > 0 { Sleep(showTime) this.Highlight() } else if showTime < 0 SetTimer(ObjBindMethod(this, "Highlight", "clear"), -Abs(showTime)) return this } ClearHighlight() => this.Highlight("clear") /** * Clicks the center of the element. * @param WhichButton Left (default), Right, Middle (or just the first letter of each of these); or the fourth or fifth mouse button (X1 or X2). * If WhichButton is an Integer, then Sleep will be called with that number. * Example: Click(200) will sleep 200ms after clicking * @param ClickCount The number of times to click the mouse. * If ClickCount is a number >=10, then Sleep will be called with that number. * To click 10+ times and sleep after, specify "ClickCount SleepTime". * Example: Click("left", 200) will sleep 200ms after clicking. * Example: Click("left", "20 200") will left-click 20 times and then sleep 200ms. * @param DownOrUp This component is normally omitted, in which case each click consists of a down-event followed by an up-event. * Otherwise, specify the word Down (or the letter D) to press the mouse button down without releasing it. * Later, use the word Up (or the letter U) to release the mouse button. * @param Relative Optional offset values for both X and Y (eg "-5 10" would offset X by -5 and Y by +10). * @param NoActivate Setting NoActivate to True will prevent the window from being brought to front if the clickable point is not visible on screen. * @returns {Acc.IAccessible} */ Click(WhichButton:="left", ClickCount:=1, DownOrUp:="", Relative:="", NoActivate:=False) { rel := [0,0], pos := this.Location, saveCoordMode := A_CoordModeMouse, cCount := 1, SleepTime := -1 if (Relative && !InStr(Relative, "rel")) rel := StrSplit(Relative, " "), Relative := "" if IsInteger(WhichButton) SleepTime := WhichButton, WhichButton := "left" if !IsInteger(ClickCount) && InStr(ClickCount, " ") { sCount := StrSplit(ClickCount, " ") cCount := sCount[1], SleepTime := sCount[2] } else if ClickCount > 9 { SleepTime := cCount, cCount := 1 } if (!NoActivate && (Acc.WindowFromPoint(pos.x+pos.w//2+rel[1], pos.y+pos.h//2+rel[2]) != this.wId)) { WinActivate(this.wId) WinWaitActive(this.wId) } CoordMode("Mouse", "Screen") Click(pos.x+pos.w//2+rel[1] " " pos.y+pos.h//2+rel[2] " " WhichButton (ClickCount ? " " ClickCount : "") (DownOrUp ? " " DownOrUp : "") (Relative ? " " Relative : "")) CoordMode("Mouse", saveCoordMode) Sleep(SleepTime) return this } /** * ControlClicks the center of the element after getting relative coordinates with GetLocation("client"). * @param WhichButton The button to click: LEFT, RIGHT, MIDDLE (or just the first letter of each of these). * If omitted or blank, the LEFT button will be used. * If an Integer is provided then a Sleep will be called afterwards. * Ex: ControlClick(200) will sleep 200ms after clicking. * @returns {Acc.IAccessible} */ ControlClick(WhichButton:="left", ClickCount:=1, Options:="") { pos := this.GetLocation("client") ControlClick("X" pos.x+pos.w//2 " Y" pos.y+pos.h//2, this.wId,, IsInteger(WhichButton) ? "left" : WhichButton, ClickCount, Options) if IsInteger(WhichButton) Sleep(WhichButton) return this } } /** * Returns an Acc element from a screen coordinate. If both coordinates are omitted then * the element under the mouse will be returned. * @param x The x-coordinate * @param y The y-coordinate * @param activateChromium Whether to turn on accessibility for Chromium-based windows. Default is True. * @returns {Acc.IAccessible} */ static ElementFromPoint(x:=unset, y:=unset, activateChromium:=True) { if !(IsSet(x) && IsSet(y)) DllCall("GetCursorPos", "int64P", &pt64:=0), x := 0xFFFFFFFF & pt64, y := pt64 >> 32 else pt64 := y << 32 | (x & 0xFFFFFFFF) wId := DllCall("GetAncestor", "UInt", DllCall("user32.dll\WindowFromPoint", "int64", pt64), "UInt", 2) ; hwnd from point by SKAN. 2 = GA_ROOT if activateChromium Acc.ActivateChromiumAccessibility(wId) pvarChild := Buffer(8 + 2 * A_PtrSize) if DllCall("oleacc\AccessibleObjectFromPoint", "int64",pt64, "ptr*",&ppAcc := 0, "ptr",pvarChild) = 0 { ; returns a pointer from which we get a Com Object return Acc.IAccessible(ComValue(9, ppAcc), NumGet(pvarChild,8,"UInt"), wId) } } ; Wrapper for native function name static ObjectFromPoint(args*) => Acc.ElementFromPoint(args*) /** * Returns an Acc element corresponding to the provided window Hwnd. * @param hWnd The window Hwnd. Default is Last Found Window. * @param idObject An Acc.ObjId constant. Default is Acc.ObjId.Window (value 0). * Note that to get objects by control Hwnds, use ObjId.Client (value -4). * @param activateChromium Whether to turn on accessibility for Chromium-based windows. Default is True. * @returns {Acc.IAccessible} */ static ElementFromHandle(hWnd:="", idObject := "Window", activateChromium:=True) { if !IsInteger(idObject) try idObject := Acc.ObjId.%idObject% if !IsInteger(hWnd) hWnd := WinExist(hWnd) if !hWnd throw Error("Invalid window handle or window not found", -1) if activateChromium Acc.ActivateChromiumAccessibility(hWnd) IID := Buffer(16) if DllCall("oleacc\AccessibleObjectFromWindow", "ptr",hWnd, "uint",idObject &= 0xFFFFFFFF , "ptr",-16 + NumPut("int64", idObject == 0xFFFFFFF0 ? 0x46000000000000C0 : 0x719B3800AA000C81, NumPut("int64", idObject == 0xFFFFFFF0 ? 0x0000000000020400 : 0x11CF3C3D618736E0, IID)) , "ptr*", ComObj := ComValue(9,0)) = 0 Return Acc.IAccessible(ComObj,,hWnd) } ; Wrapper for native function name static ObjectFromWindow(args*) => Acc.ElementFromHandle(args*) /** * Returns an Acc element corresponding to the provided windows Chrome_RenderWidgetHostHWND control. * @param hWnd The window Hwnd. Default is Last Found Window. * @param activateChromium Whether to turn on accessibility. Default is True. * @returns {Acc.IAccessible} */ static ElementFromChromium(hWnd:="", activateChromium:=True) { if !IsInteger(hWnd) hWnd := WinExist(hWnd) if !hWnd throw Error("Invalid window handle or window not found", -1) if activateChromium Acc.ActivateChromiumAccessibility(hWnd) if !(cHwnd := ControlGetHwnd("Chrome_RenderWidgetHostHWND1", hWnd)) throw Error("Chromium render element was not found", -1) return Acc.ElementFromHandle(cHwnd, -4,False) } static ObjectFromChromium(args*) => Acc.ElementFromChromium(args*) /** * Returns an Acc element from a path string (comma-separated integers or RoleText values) * @param ChildPath Comma-separated indexes for the tree traversal. * Instead of an index, RoleText is also permitted. * @param hWnd Window handle or IAccessible object. Default is Last Found Window. * @param activateChromium Whether to turn on accessibility for Chromium-based windows. Default is True. * @returns {Acc.IAccessible} */ static ElementFromPath(ChildPath, hWnd:="", activateChromium:=True) { if Type(hWnd) = "Acc.IAccessible" oAcc := hWnd else { if activateChromium Acc.ActivateChromiumAccessibility(hWnd) oAcc := Acc.ElementFromHandle(hWnd) } ChildPath := StrReplace(StrReplace(ChildPath, ".", ","), " ") Loop Parse ChildPath, "," { if IsInteger(A_LoopField) oAcc := oAcc.GetNthChild(A_LoopField) else { RegExMatch(A_LoopField, "(\D+)(\d*)", &m), i := m[2] || 1, c := 0 if m[1] = "p" { Loop i oAcc := oAcc.Parent continue } for oChild in oAcc { try { if (StrReplace(oChild.RoleText, " ") = m[1]) && (++c = i) { oAcc := oChild break } } } } } Return oAcc } static ObjectFromPath(args*) => Acc.ElementFromPath(args*) /** * Internal method. Used to get an Acc element returned from an event. * @param hWnd Window/control handle * @param idObject Object ID of the object that generated the event * @param idChild Specifies whether the event was triggered by an object or one of its child elements. * @returns {Acc.IAccessible} */ static ObjectFromEvent(hWnd, idObject, idChild) { if (DllCall("oleacc\AccessibleObjectFromEvent" , "Ptr", hWnd , "UInt", idObject , "UInt", idChild , "Ptr*", pacc := ComValue(9,0) , "Ptr", varChild := Buffer(16)) = 0) { return Acc.IAccessible(pacc, NumGet(varChild, 8, "UInt"), DllCall("GetAncestor", "UInt", hWnd, "UInt", 2)) } throw Error("ObjectFromEvent failed", -1) } /** * Returns the root element (Acc element for the desktop) * @returns {Acc.IAccessible} */ static GetRootElement() { return Acc.ElementFromHandle(0x10010) } /** * Activates accessibility in a Chromium-based window. * The WM_GETOBJECT message is sent to the Chrome_RenderWidgetHostHWND1 control * and the render elements' Name property is accessed. Once the message is sent, the method * will wait up to 500ms until accessibility is enabled. * For a specific Hwnd, accessibility will only be tried to turn on once, and regardless of * whether it was successful or not, later calls of this method with that Hwnd will simply return. * @returns True if accessibility was successfully turned on. */ static ActivateChromiumAccessibility(hWnd:="") { static activatedHwnds := Map() if !IsInteger(hWnd) hWnd := WinExist(hWnd) if activatedHwnds.Has(hWnd) return 1 activatedHwnds[hWnd] := 1, cHwnd := 0 try cHwnd := ControlGetHwnd("Chrome_RenderWidgetHostHWND1", hWnd) if !cHwnd return SendMessage(WM_GETOBJECT := 0x003D, 0, 1,, cHwnd) try { rendererEl := Acc.ElementFromHandle(cHwnd,,False).FindElement({Role:15}, 5) _ := rendererEl.Name ; it doesn't work without calling CurrentName (at least in Skype) } waitTime := A_TickCount + 500 while IsSet(rendererEl) && (A_TickCount < waitTime) { try { if rendererEl.Value return 1 } Sleep 20 } } ; Internal method to query an IAccessible pointer static Query(pAcc) { oCom := ComObjQuery(pAcc, "{618736e0-3c3d-11cf-810c-00aa00389b71}") ObjAddRef(oCom.ptr) Try Return ComValue(9, oCom.ptr) } ; Internal method to get the RoleText from Role integer static GetRoleText(nRole) { if !IsInteger(nRole) { if (Type(nRole) = "String") && (nRole != "") return nRole throw TypeError("The specified role is not an integer!",-1) } nRole := Integer(nRole) nSize := DllCall("oleacc\GetRoleText", "Uint", nRole, "Ptr", 0, "Uint", 0) VarSetStrCapacity(&sRole, nSize+2) DllCall("oleacc\GetRoleText", "Uint", nRole, "str", sRole, "Uint", nSize+2) Return sRole } ; Internal method to get the StateText from State integer static GetStateText(nState) { nSize := DllCall("oleacc\GetStateText" , "Uint" , nState , "Ptr" , 0 , "Uint" , 0) VarSetStrCapacity(&sState, nSize+2) DllCall("oleacc\GetStateText" , "Uint" , nState , "str" , sState , "Uint" , nSize+2) return sState } /** * Registers an event to the provided callback function. * Returns an event handler object, that once destroyed will unhook the event. * @param callback The callback function with two mandatory arguments: CallbackFunction(oAcc, EventInfo) * @param eventMin One of the Acc.Event constants * @param eventMax Optional: one of the Acc.Event constants, which if provided will register * a range of events from eventMin to eventMax * @param PID Optional: Process ID from which to register events. Default is all processes. * @returns {Object} */ static RegisterWinEvent(callback, eventMin, eventMax?, PID:=0) { if HasMethod(eventMin) ; Legacy support: if eventMin is a method, then the arguments are: event, callback, PID PID := eventMax ?? PID, eventMax := callback, callback := eventMin, eventMin := eventMax if IsSet(eventMax) && HasMethod(eventMax) ; Legacy support: if eventMax is a method, then the arguments are: eventMin, eventMax, callback, PID callbackBuf := eventMax, eventMax := eventMin, eventMin := callback, callback := callbackBuf if !IsSet(eventMax) eventMax := eventMin if Type(eventMin) = "String" try eventMin := Acc.Event.%eventMin% if Type(eventMax) = "String" try eventMax := Acc.Event.%eventMax% pCallback := CallbackCreate(this.GetMethod("HandleWinEvent").Bind(this, callback), "F", 7) hook := Acc.SetWinEventHook(eventMin, eventMax, pCallback, PID) return {__Hook:hook, __Callback:pCallback, __Delete:{ call: (*) => (this.UnhookWinEvent(hook), CallbackFree(pCallback)) }} } ; Internal method. Calls the callback function after wrapping the IAccessible native object static HandleWinEvent(fCallback, hWinEventHook, Event, hWnd, idObject, idChild, dwEventThread, dwmsEventTime) { Critical try return fCallback(oAcc := Acc.ObjectFromEvent(hWnd, idObject, idChild), {Event:Event, EventThread:dwEventThread, EventTime:dwmsEventTime&0x7FFFFFFF, ControlID:hWnd, WinID:oAcc.wId, ObjId:idObject}) } ; Internal method. Hooks a range of events to a callback function. static SetWinEventHook(eventMin, eventMax, pCallback, PID:=0) { DllCall("ole32\CoInitialize", "Uint", 0) Return DllCall("SetWinEventHook", "Uint", eventMin, "Uint", eventMax, "Uint", 0, "UInt", pCallback, "Uint", PID, "Uint", 0, "Uint", 0) } ; Internal method. Unhooks a WinEventHook. static UnhookWinEvent(hHook) { Return DllCall("UnhookWinEvent", "Ptr", hHook) } /** * Returns the Hwnd to a window from a set of screen coordinates * @param X Screen X-coordinate * @param Y Screen Y-coordinate */ static WindowFromPoint(X, Y) { ; by SKAN and Linear Spoon return DllCall("GetAncestor", "UInt", DllCall("user32.dll\WindowFromPoint", "Int64", Y << 32 | (X & 0xFFFFFFFF)), "UInt", 2) } /** * Removes all highlights created by Element.Highlight() */ static ClearHighlights() { for _, p in Acc.__HighlightGuis { for __, r in p r.Destroy() } Acc.__HighlightGuis := Map() } ; Internal class: AccViewer code class Viewer { __New() { this.Stored := {mwId:0, FilteredTreeView:Map(), TreeView:Map()} this.Capturing := False this.gViewer := Gui("AlwaysOnTop Resize","AccViewer") this.gViewer.OnEvent("Close", (*) => ExitApp()) this.gViewer.OnEvent("Size", this.GetMethod("gViewer_Size").Bind(this)) this.gViewer.Add("Text", "w100", "Window Info").SetFont("bold") this.LVWin := this.gViewer.Add("ListView", "h140 w250", ["Property", "Value"]) this.LVWin.OnEvent("ContextMenu", LV_CopyTextMethod := this.GetMethod("LV_CopyText").Bind(this)) this.LVWin.ModifyCol(1,60) this.LVWin.ModifyCol(2,180) for _, v in ["Title", "Text", "Id", "Location", "Class(NN)", "Process", "PID"] this.LVWin.Add(,v,"") this.gViewer.Add("Text", "w100", "Acc Info").SetFont("bold") this.LVProps := this.gViewer.Add("ListView", "h220 w250", ["Property", "Value"]) this.LVProps.OnEvent("ContextMenu", LV_CopyTextMethod) this.LVProps.ModifyCol(1,100) this.LVProps.ModifyCol(2,140) for _, v in ["RoleText", "Role", "Value", "Name", "Location", "StateText", "State", "DefaultAction", "Description", "KeyboardShortcut", "Help", "ChildId"] this.LVProps.Add(,v,"") this.ButCapture := this.gViewer.Add("Button", "xp+60 y+10 w130", "Start capturing (F1)") this.ButCapture.OnEvent("Click", this.CaptureHotkeyFunc := this.GetMethod("ButCapture_Click").Bind(this)) HotKey("~F1", this.CaptureHotkeyFunc) this.SBMain := this.gViewer.Add("StatusBar",, " Start capturing, then hold cursor still to construct tree") this.SBMain.OnEvent("Click", this.GetMethod("SBMain_Click").Bind(this)) this.SBMain.OnEvent("ContextMenu", this.GetMethod("SBMain_Click").Bind(this)) this.gViewer.Add("Text", "x278 y10 w100", "Acc Tree").SetFont("bold") this.TVAcc := this.gViewer.Add("TreeView", "x275 y25 w250 h390 -0x800") this.TVAcc.OnEvent("Click", this.GetMethod("TVAcc_Click").Bind(this)) this.TVAcc.OnEvent("ContextMenu", this.GetMethod("TVAcc_ContextMenu").Bind(this)) this.TVAcc.Add("Start capturing to show tree") this.TextFilterTVAcc := this.gViewer.Add("Text", "x275 y428", "Filter:") this.EditFilterTVAcc := this.gViewer.Add("Edit", "x305 y425 w100") this.EditFilterTVAcc.OnEvent("Change", this.GetMethod("EditFilterTVAcc_Change").Bind(this)) this.gViewer.Show() } ; Resizes window controls when window is resized gViewer_Size(GuiObj, MinMax, Width, Height) { this.TVAcc.GetPos(&TVAccX, &TVAccY, &TVAccWidth, &TVAccHeight) this.TVAcc.Move(,,Width-TVAccX-10,Height-TVAccY-50) this.TextFilterTVAcc.Move(TVAccX, Height-42) this.EditFilterTVAcc.Move(TVAccX+30, Height-45) this.TVAcc.GetPos(&LVPropsX, &LVPropsY, &LVPropsWidth, &LVPropsHeight) this.LVProps.Move(,,,Height-LVPropsY-225) this.ButCapture.Move(,Height -55) } ; Starts showing the element under the cursor with 200ms intervals with CaptureCallback ButCapture_Click(GuiCtrlObj?, Info?) { if this.Capturing { this.StopCapture() return } this.Capturing := True HotKey("~F1", this.CaptureHotkeyFunc, "Off") HotKey("~Esc", this.CaptureHotkeyFunc, "On") this.TVAcc.Delete() this.TVAcc.Add("Hold cursor still to construct tree") this.ButCapture.Text := "Stop capturing (Esc)" this.CaptureCallback := this.GetMethod("CaptureCycle").Bind(this) SetTimer(this.CaptureCallback, 200) } ; Handles right-clicking a listview (copies to clipboard) LV_CopyText(GuiCtrlObj, Info, *) { LVData := Info > GuiCtrlObj.GetCount() ? ListViewGetContent("", GuiCtrlObj) : ListViewGetContent("Selected", GuiCtrlObj) ToolTip("Copied: " (A_Clipboard := RegExReplace(LVData, "([ \w]+)\t", "$1: "))) SetTimer((*) => ToolTip(), -3000) } ; Copies the Acc path to clipboard when statusbar is clicked SBMain_Click(GuiCtrlObj, Info, *) { if InStr(this.SBMain.Text, "Path:") { ToolTip("Copied: " (A_Clipboard := SubStr(this.SBMain.Text, 9))) SetTimer((*) => ToolTip(), -3000) } } ; Stops capturing elements under mouse, unhooks CaptureCallback StopCapture(GuiCtrlObj:=0, Info:=0) { if this.Capturing { this.Capturing := False this.ButCapture.Text := "Start capturing (F1)" HotKey("~Esc", this.CaptureHotkeyFunc, "Off") HotKey("~F1", this.CaptureHotkeyFunc, "On") SetTimer(this.CaptureCallback, 0) this.Stored.oAcc.Highlight() return } } ; Gets Acc element under mouse, updates the GUI. ; If the mouse is not moved for 1 second then constructs the Acc tree. CaptureCycle() { MouseGetPos(&mX, &mY, &mwId) oAcc := Acc.ElementFromPoint() if this.Stored.HasOwnProp("oAcc") && IsObject(oAcc) && oAcc.IsEqual(this.Stored.oAcc) { if this.FoundTime != 0 && ((A_TickCount - this.FoundTime) > 1000) { if (mX == this.Stored.mX) && (mY == this.Stored.mY) this.ConstructTreeView(), this.FoundTime := 0 else this.FoundTime := A_TickCount } this.Stored.mX := mX, this.Stored.mY := mY return } this.LVWin.Delete() WinGetPos(&mwX, &mwY, &mwW, &mwH, mwId) propsOrder := ["Title", "Text", "Id", "Location", "Class(NN)", "Process", "PID"] props := Map("Title", WinGetTitle(mwId), "Text", WinGetText(mwId), "Id", mwId, "Location", "x: " mwX " y: " mwY " w: " mwW " h: " mwH, "Class(NN)", WinGetClass(mwId), "Process", WinGetProcessName(mwId), "PID", WinGetPID(mwId)) for propName in propsOrder this.LVWin.Add(,propName,props[propName]) this.LVProps_Populate(oAcc) this.Stored.mwId := mwId, this.Stored.oAcc := oAcc, this.Stored.mX := mX, this.Stored.mY := mY, this.FoundTime := A_TickCount } ; Populates the listview with Acc element properties LVProps_Populate(oAcc) { Acc.ClearHighlights() ; Clear oAcc.Highlight(0) ; Indefinite show this.LVProps.Delete() Location := {x:"N/A",y:"N/A",w:"N/A",h:"N/A"}, RoleText := "N/A", Role := "N/A", Value := "N/A", Name := "N/A", StateText := "N/A", State := "N/A", DefaultAction := "N/A", Description := "N/A", KeyboardShortcut := "N/A", Help := "N/A", ChildId := "" for _, v in ["RoleText", "Role", "Value", "Name", "Location", "StateText", "State", "DefaultAction", "Description", "KeyboardShortcut", "Help", "ChildId"] { try %v% := oAcc.%v% this.LVProps.Add(,v, v = "Location" ? ("x: " %v%.x " y: " %v%.y " w: " %v%.w " h: " %v%.h) : %v%) } } ; Handles selecting elements in the Acc tree, highlights the selected element TVAcc_Click(GuiCtrlObj, Info) { if this.Capturing return try oAcc := this.EditFilterTVAcc.Value ? this.Stored.FilteredTreeView[Info] : this.Stored.TreeView[Info] if IsSet(oAcc) && oAcc { try this.SBMain.SetText(" Path: " oAcc.Path) this.LVProps_Populate(oAcc) } } ; Permits copying the Dump of Acc element(s) to clipboard TVAcc_ContextMenu(GuiCtrlObj, Item, IsRightClick, X, Y) { TVAcc_Menu := Menu() try oAcc := this.EditFilterTVAcc.Value ? this.Stored.FilteredTreeView[Item] : this.Stored.TreeView[Item] if IsSet(oAcc) TVAcc_Menu.Add("Copy to Clipboard", (*) => A_Clipboard := oAcc.Dump()) TVAcc_Menu.Add("Copy Tree to Clipboard", (*) => A_Clipboard := Acc.ElementFromHandle(this.Stored.mwId).DumpAll()) TVAcc_Menu.Show() } ; Handles filtering the Acc elements inside the TreeView when the text hasn't been changed in 500ms. ; Sorts the results by Acc properties. EditFilterTVAcc_Change(GuiCtrlObj, Info, *) { static TimeoutFunc := "", ChangeActive := False if !this.Stored.TreeView.Count return if (Info != "DoAction") || ChangeActive { if !TimeoutFunc TimeoutFunc := this.GetMethod("EditFilterTVAcc_Change").Bind(this, GuiCtrlObj, "DoAction") SetTimer(TimeoutFunc, -500) return } ChangeActive := True this.Stored.FilteredTreeView := Map(), parents := Map() if !(searchPhrase := this.EditFilterTVAcc.Value) { this.ConstructTreeView() ChangeActive := False return } this.TVAcc.Delete() temp := this.TVAcc.Add("Searching...") Sleep -1 this.TVAcc.Opt("-Redraw") this.TVAcc.Delete() for index, oAcc in this.Stored.TreeView { for _, prop in ["RoleText", "Role", "Value", "Name", "StateText", "State", "DefaultAction", "Description", "KeyboardShortcut", "Help", "ChildId"] { try { if InStr(oAcc.%Prop%, searchPhrase) { if !parents.Has(prop) parents[prop] := this.TVAcc.Add(prop,, "Expand") this.Stored.FilteredTreeView[this.TVAcc.Add(this.GetShortDescription(oAcc), parents[prop], "Expand")] := oAcc } } } } if !this.Stored.FilteredTreeView.Count this.TVAcc.Add("No results found matching `"" searchPhrase "`"") this.TVAcc.Opt("+Redraw") TimeoutFunc := "", ChangeActive := False } ; Populates the TreeView with the Acc tree when capturing and the mouse is held still ConstructTreeView() { this.TVAcc.Delete() this.TVAcc.Add("Constructing Tree, please wait...") Sleep -1 this.TVAcc.Opt("-Redraw") this.TVAcc.Delete() this.Stored.TreeView := Map() this.RecurseTreeView(Acc.ElementFromHandle(this.Stored.mwId)) this.TVAcc.Opt("+Redraw") for k, v in this.Stored.TreeView if this.Stored.oAcc.IsEqual(v) this.TVAcc.Modify(k, "Vis Select"), this.SBMain.SetText(" Path: " v.Path) } ; Stores the Acc tree with corresponding path values for each element RecurseTreeView(oAcc, parent:=0, path:="") { this.Stored.TreeView[TWEl := this.TVAcc.Add(this.GetShortDescription(oAcc), parent, "Expand")] := oAcc.DefineProp("Path", {value:path}) for k, v in oAcc this.RecurseTreeView(v, TWEl, path (path?",":"") k) } ; Creates a short description string for the Acc tree elements GetShortDescription(oAcc) { elDesc := " `"`"" try elDesc := " `"" oAcc.Name "`"" try elDesc := oAcc.RoleText elDesc catch elDesc := "`"`"" elDesc return elDesc } } }
声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。
评论(0)