GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
ElementHelper.cs
1// =============================================================================
2// GPAL - Generally Positive Automation Library
3// Copyright © 2026 Software Decisions, Inc. All rights reserved.
4//
5// This file is part of GPAL.
6// Licensed under the Business Source License 1.1
7//
8// Primary development, architecture, and vision by Michael B. Vederman,
9// CEO of Software Decisions, Inc., Texas.
10//
11// Internal development maintained privately.
12// Public releases appear on GitHub: https://github.com/SoftwareDecisionsInc/GPAL.
13//
14// See LICENSE for full terms, including Additional Use Grant.
15// =============================================================================
16
17using System;
18using System.Collections.Generic;
19using System.Collections.ObjectModel;
20using System.Diagnostics;
21using System.Drawing;
22using System.Linq;
23using System.Runtime.InteropServices;
24using System.Text.RegularExpressions;
25using System.Threading;
26using System.Windows;
27using System.Windows.Automation;
28using System.Windows.Forms;
29using OpenQA.Selenium;
30using OpenQA.Selenium.Interactions;
31using OpenQA.Selenium.Support.UI;
32using static GenerallyPositive.Enums;
34using Condition = System.Windows.Automation.Condition;
35
37{
38
39 internal class ElementHelper
40 {
41 delegate ReadOnlyCollection<GPALAutomationElement> FindElementsByDelegate(Application application, SelectorPathEntry selectorPath, bool searchForSelector = true, AutomationElement element = null);
42 // virtual keycodes: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes?redirectedfrom=MSDN
43 // scan codes: https://en.wikipedia.org/wiki/Scancode
44 static byte ScanCode {get; } = 0;
45
46 // to avoid repeat messages for the same selector when waiting for a selector
47 static List<string> listOfMessages = new List<string>();
48 static bool supressedMessage = false;
49 static Selector lastSelector = null;
50
63 public static ReadOnlyCollection<GPALAutomationElement> FindElements(Application application, UnitOfWork currentUOW, Selector selector, out bool matchedAll, out List<GPALAutomationElement> matchedElements, AutomationElement automationElement = null, bool callIfHandler = true)
64 {
65 int foundCount = 0;
66
67 string elementStringToMatch = null;
68 ReadOnlyCollection<GPALAutomationElement> retElems = null;
69 List<GPALAutomationElement> matchedElems = new List<GPALAutomationElement>();
70 MatchCollection mc = null;
71 ReadOnlyCollection<GPALAutomationElement> allFoundElems = null;
72 List<GPALAutomationElement> totalAllFoundElems = new List<GPALAutomationElement>();
73 List<GPALAutomationElement> iterateOverTheseElements = null;
74 bool tryNextSelector = false;
75 int criteriaCnt = 0;
76 string message = string.Empty;
77 int retries = 1;
78 bool retry = false;
79
80 matchedAll = false;
81 matchedElements = new List<GPALAutomationElement>();
82
83 currentUOW.CurrentSelector = selector;
84
85 if (false == lastSelector?.Name.Equals(selector.Name))
86 {
87 listOfMessages.Clear();
88 supressedMessage = false;
89 lastSelector = selector;
90 }
91 else if (null == lastSelector)
92 lastSelector = selector;
93
94 // !!!!!!!!!!!!!!! MUST MATCH THE ENUM ORDER !!!!!!!!!!!!!!!!!!
95 // public enum SelectorPathType { NotSet = 0, Css = 1, Image = 2, Text = 4, Value = 8, Xpath = 16, Name = 32, AutomationID = 64, ClassName = 128 };
96 List<FindElementsByDelegate> functionList = new List<FindElementsByDelegate> { null /* NotSet=0 */, FindElementsByCSS, FindElementsByImage, FindElementsByText, FindElementsByValue, FindElementsByXPath, FindElementsByName, FindElementsByAutomationID, FindElementsByClassName };
97 // iterate selectors, finding elements
98 foreach (SelectorSettings.SelectorPathEntry selectorPathEntry in selector.SelectorPaths)
99 {
100 tryAgain: // NOTE: CAVEAT: goto label - don't like it, but it has to happen sometimes. we cannot have any inner loop as that will break retrying the next selector
101 int textLength = selectorPathEntry.SelectorPath.Length - 1;
102 string abbreviatedText = selectorPathEntry.SelectorPath.Substring(0, Math.Min(7, textLength)) + "..." + selectorPathEntry.SelectorPath.Substring(Math.Max(0, textLength - 7));
103 if (18 > selectorPathEntry.SelectorPath.Length)
104 abbreviatedText = selectorPathEntry.SelectorPath;
105
106 int idx = GetIndexOfEnum<SelectorPathType>((int)selectorPathEntry.SelectorPathType);
107 tryNextSelector = false;
108 string errorMessage = $"Error finding elements for selector [{selector.Name}][{abbreviatedText}]";
109
110 try
111 {
112 message = $"Searching for elements for selector [{selector.Name}] using [{selectorPathEntry.SelectorPathType}] [{abbreviatedText}]";
113
114 if (false == listOfMessages.Contains(message))
115 {
116 listOfMessages.Add(message);
117 GPAL.PublishSimpleEvent(GPALEventType.INFO, message, application, GPALObjectType.Application);
118 }
119
120 allFoundElems = functionList[idx](application, selectorPathEntry, selector.SearchForSelector, automationElement);
121
122 // if we get null back, we will retry once just to make sure
123 if (null == allFoundElems || 0 == (allFoundElems?.Count ?? 0)) // did not find any elements
124 {
125 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"{errorMessage}. Null set returned. retrying.", application, GPALObjectType.Application);
126 retry = true;
127 }
128 else
129 totalAllFoundElems.AddRange(allFoundElems.ToList());
130
131 #region <Element Matching>
132 if (0 < selector.MatchCriteria.Count)
133 {
134 foreach (SelectorSettings.MatchCriteriaEntry matchCriteriaEntry in selector.MatchCriteria)
135 {
136 if (MatchType.Custom == matchCriteriaEntry.MatchType)
137 {
138 // match function returns true or false for matchedAll
139 CallIfStatus handled = matchCriteriaEntry.AppMatchingFunction(allFoundElems.ToList(), out matchedElems, out matchedAll, selector, matchCriteriaEntry.ExactMatch);
140
141 if (CallIfStatus.Terminate == handled)
142 {
143 string msg = $"Custom match handler [{matchCriteriaEntry.AppMatchingFunction.GetInvocationList()[0].Method.Name}] requested program termination for selector {selector.Name} path [{selectorPathEntry.SelectorPath}].";
144 GPAL.PublishSimpleEvent(GPALEventType.WARNING, msg, application, GPALObjectType.Application);
145 throw new GPALException(msg);
146 }
147
148 // always return matched elements, it's either all or just those matched
149 matchedElements.AddRange(matchedElems);
150 matchedElements = matchedElements.Distinct().ToList(); // dedupe // what order these will be in over iterations is anyone's guess, but they will be on one page
151
152 if (CallIfStatus.Handled == handled) // stop matching
153 break;
154 else if (CallIfStatus.TryNext == handled) // custom algorithm didn't like the result set and wants us to try the next selectorpath
155 {
156 tryNextSelector = true;
157 break;
158 }
159 }
160 }
161
162 if (true == tryNextSelector)
163 continue;
164
165 // if our custom match matched items, then apply further matching to these items
166 // else apply matching to all found elements
167 if (0 == matchedElements.Count)
168 iterateOverTheseElements = new List<GPALAutomationElement>(allFoundElems);
169 else
170 iterateOverTheseElements = new List<GPALAutomationElement>(matchedElements);
171
172 // we are done with idx, we can reuse, used as index into findelement method array
173 for (idx = 0; idx < iterateOverTheseElements.Count; idx++)
174 {
175 GPALAutomationElement automationElement2 = iterateOverTheseElements[idx];
176 criteriaCnt = 0;
177
178 for (int matchIdx = 0; matchIdx < selector.MatchCriteria.Count; matchIdx++)
179 {
180 SelectorSettings.MatchCriteriaEntry matchCriteriaEntry = selector.MatchCriteria[matchIdx];
181
182 if (MatchType.None != matchCriteriaEntry.MatchType)
183 {
184 criteriaCnt++; // keep count of how many match criteria we have, ALL must match on the element, we should have as many matched elements as criteria for ALL to match
185
186 // Mask out the REGEX 1 bit to get to the actual type
187 switch ((MatchType)((int)matchCriteriaEntry.MatchType & 0XFE))
188 {
189 case MatchType.Text:
190 elementStringToMatch = automationElement2.Ae.GetText();
191 break;
192 case MatchType.Href:
193 elementStringToMatch = automationElement2.Ae.GetText(); // TODO: is there an attribute for this?
194 break;
195 case MatchType.Src:
196 elementStringToMatch = automationElement2.Ae.GetText(); // TODO: is there an attribute for this?
197 break;
198 case MatchType.Value:
199 elementStringToMatch = automationElement2.Ae.GetText(); // TODO: is there an attribute for this?
200 break;
201 }
202
203 if (1 == ((int)MatchType.Regex & (int)matchCriteriaEntry.MatchType))
204 {
205
206 mc = Regex.Matches(elementStringToMatch, matchCriteriaEntry.StringToMatch);
207 if (0 < mc.Count)
208 matchedElems.Add(automationElement2);
209 }
210 else
211 {
212 if (false == matchCriteriaEntry.ExactMatch && elementStringToMatch.Contains(matchCriteriaEntry.StringToMatch))
213 matchedElems.Add(automationElement2);
214 else if (true == matchCriteriaEntry.ExactMatch && elementStringToMatch.Equals(matchCriteriaEntry.StringToMatch))
215 matchedElems.Add(automationElement2);
216 }
217 }
218 }
219 if (criteriaCnt == matchedElems.Count) // we have one element that must be added once for every match, if not, it doesn't match all criteria, so try the next element
220 matchedElements.AddRange(matchedElems.Distinct()); // add this match to the out return list (dedupe it first)
221 }
222 matchedElements = matchedElements.Distinct().ToList(); // dedupe // what order these will be in over iterations is anyone's guess, but they will be on one page
223 matchedAll = (allFoundElems.Count == foundCount);
224 }
225 else // no match criteria - all found
226 {
227 matchedAll = true;
228 matchedElems = allFoundElems?.ToList() ?? new List<GPALAutomationElement>();
229 matchedElements = matchedElems; // out return list
230 }
231 #endregion <Element Matching>
232
233 }
234 catch (Exception ex) // element not found for some reason
235 {
236 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Exception thrown trying to find elements for selector [{selector.Name}]. Continuing.", currentUOW, GPALObjectType.UnitOfWork, ex);
237 continue;
238 }
239
240 // return only WithAllThatMatch row count of those that matched (or from all)
241 // matchedElems contains either All elememts or just the matched
242 if (int.MaxValue != currentUOW.WithAllThatMatch && 0 < matchedElements?.Count) // just WithAllThatMatch row count of elements
243 {
244 int elemIdx = 0;
245 List<GPALAutomationElement> tmpList = new List<GPALAutomationElement>();
246
247 // ensure we copy over only as many results as requested, or how many results we have
248 while (elemIdx < Math.Min((int)currentUOW.WithAllThatMatch, (int)matchedElements?.Count))
249 tmpList.Add(matchedElements[elemIdx++]);
250
251 matchedElements = tmpList;
252 }
253
254 selectorPathEntry.AppSelectorFoundResults = allFoundElems?.ToList() ?? new List<GPALAutomationElement>();
255 selectorPathEntry.AppSelectorMatchedResults = matchedElems;
256
257 retElems = new ReadOnlyCollection<GPALAutomationElement>(totalAllFoundElems.Distinct().ToList()); // always return everything found
258
259 foreach (GPALAutomationElement element in retElems)
260 {
261 element.Application = application;
262 element.Selector = selector;
263 }
264
265 foreach (GPALAutomationElement element in matchedElements)
266 {
267 element.Application = application;
268 element.Selector = selector;
269 }
270
271 if (true == retry && 0 < retries--)
272 {
273 retry = false;
274 goto tryAgain; // NOTE: CAVEAT: goto label
275 }
276
277 selector.ElementsFoundAndMatchedCount = matchedElements.Count();
278
279 message = $"[{retElems?.Count ?? 0}] elements found for selector [{selector.Name}]";
280
281 if (false == listOfMessages.Contains(message))
282 {
283 listOfMessages.Add(message);
284 GPAL.PublishSimpleEvent(GPALEventType.INFO, message, application, GPALObjectType.Application);
285
286 if (0 < retElems?.Count && (true == selector.AnyMatchCriteria() || Int32.MaxValue != currentUOW.WithAllThatMatch))
287 {
288 var matchCount = selector.MatchCriteria.Count > 0 ? selector.MatchCriteria.Count.ToString() : $"WithAllThatMatch({currentUOW.WithAllThatMatch})";
289
290 if ((true == matchCount.Contains("WithAllThatMatch") && 1 < currentUOW.WithAllThatMatch) || 0 < selector.MatchCriteria.Count)
291 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"[{matchedElements.Count}] elements matched for selector [{selector.Name}] with [{matchCount}] match criteria", application, GPALObjectType.Application);
292 }
293 }
294 else if (false == supressedMessage)
295 {
296 GPAL.PublishSimpleEvent(GPALEventType.INFO, "Supressing repeat messages", application, GPALObjectType.Application);
297 supressedMessage = true;
298 }
299
300 // CAVEAT:if one path matches all elements found, are we done? maybe the next path finds different elements we want to add?
301 // TODO: do we have a fluent switch 'TryAllPaths' or 'StopOnSuccess'
302 // For complex evaluations, roll your own matching function,
303 if (true == matchedAll || retElems.Count > 0) // for now, if a selector path matches one match criteria, we consider ourselves done with matching
304 break;
305
306 } // foreach selectorpath
307
308 // false == callIfHandler will be the waitfor call
309 if (true == callIfHandler)
310 CallIfHandlers(application, currentUOW, allFoundElems, matchedElements, selector, matchedAll);
311
312 selector.ElementsFoundAndMatchedCount = true == selector.AnyMatchCriteria() ? matchedElements.Count() : retElems.Count();
313
314 return retElems;
315 }
316
317 // TODO: what do these values mean outside of the bubble handlers?
318 // currentUOW.callFoundHandled
319 // currentUOW.callNotFoundHandled
330 private static void CallIfHandlers(Application application, UnitOfWork currentUOW, ReadOnlyCollection<GPALAutomationElement> foundElements, List<GPALAutomationElement> matchedElements, Selector selector, bool matchedAll)
331 {
332 string str = null;
333 string currentMethodName = null;
334 UnitOfWork safeUOW;
335
336 // TODO: do we care about matchedall?
337
338 // bubble thru handlers based upon handler return value
339 // 0 = not handled, bubble to next handler
340 // 1 = handled and continue with the program
341 // -1 = unexpected error and terminate program
342 if (0 < (foundElements?.Count ?? 0))
343 {
344 if (0 < selector.AppCallIfFound?.Count)
345 {
346 foreach (Application.CallIfDelegate func in selector.AppCallIfFound)
347 {
348 bool safeActionCalled = application.CurrentUOW.ActionCalled;
349 safeUOW = application.CurrentUOW;
350 application.CurrentUOW.ActionCalled = true; // this will allow the callif method to immediately use application.WaitFor
351
352 currentMethodName = func.Method.Name;
353 str = $"Application Selector CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
354
355 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Invoking Selector CallIfFound [{func.Method.Name}]", application, GPALObjectType.Application);
356
357 selector.AppCallIfFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
358
359 application.CurrentUOW = safeUOW;
360 application.CurrentUOW.ActionCalled = safeActionCalled;
361
362 if (CallIfStatus.NotHandled == selector.AppCallIfFoundHandled)
363 continue;
364 else
365 break;
366 }
367
368 if (CallIfStatus.Terminate == selector.AppCallIfFoundHandled)
369 {
370 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Application);
371 throw new GPALException(str);
372 }
373 }
374
375 // selector.AppCallIfFoundHandled - if handled by selector handler, don't call UOW handler
376 if (0 == selector.AppCallIfFoundHandled && 0 < currentUOW.AppCallIfFound.Count)
377 {
378 foreach (Application.CallIfDelegate func in currentUOW.AppCallIfFound)
379 {
380 safeUOW = application.CurrentUOW;
381
382 currentMethodName = func.Method.Name;
383 str = $"Application UOW Selector CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
384
385 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Invoking UOW CallIfFound [{func.Method.Name}]", application, GPALObjectType.Application);
386
387 currentUOW.CallIfNotFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
388
389 application.CurrentUOW = safeUOW;
390
391 if (CallIfStatus.NotHandled == currentUOW.CallIfNotFoundHandled)
392 continue;
393 else
394 break;
395 }
396
397 if (CallIfStatus.Terminate == currentUOW.AppCallIfFoundHandled)
398 {
399 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Application);
400 throw new GPALException(str);
401 }
402 }
403
404 // currentUOW.AppCallIfFoundHandled - if handled by UOW handler, don't call global handler
405 if (0 == currentUOW.AppCallIfFoundHandled && 0 < GPAL.GPALSettings.AppCallIfFoundList.Count)
406 {
407 foreach (Application.CallIfDelegate func in GPAL.GPALSettings.AppCallIfFoundList)
408 {
409 safeUOW = application.CurrentUOW;
410
411 currentMethodName = func.Method.Name;
412 str = $"Gloabl CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
413
414 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Invoking Global CallIfFound [{func.Method.Name}]", application, GPALObjectType.Application);
415
416 GPAL.GPALSettings.AppCallIfFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
417
418 application.CurrentUOW = safeUOW;
419
420 if (CallIfStatus.NotHandled == GPAL.GPALSettings.AppCallIfFoundHandled)
421 continue;
422 else
423 break;
424 }
425
426 if (CallIfStatus.Terminate == GPAL.GPALSettings.AppCallIfFoundHandled)
427 {
428 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Browser);
429 throw new GPALException(str);
430 }
431 }
432 }
433 else
434 {
435 if (0 < selector.AppCallIfNotFound.Count)
436 {
437 foreach (Application.CallIfDelegate func in currentUOW.AppCallIfNotFound)
438 {
439 safeUOW = application.CurrentUOW;
440
441 currentMethodName = func.Method.Name;
442 str = $"Application Selector CallIfNotFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
443
444 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Invoking Selector CallIfNotFound [{func.Method.Name}]", application, GPALObjectType.Application);
445
446 selector.AppCallIfNotFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
447
448 application.CurrentUOW = safeUOW;
449
450 if (CallIfStatus.NotHandled == selector.AppCallIfNotFoundHandled)
451 continue;
452 else
453 break;
454 }
455
456 if (CallIfStatus.Terminate == currentUOW.AppCallIfNotFoundHandled)
457 {
458 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Application);
459 throw new GPALException(str);
460 }
461 }
462
463 if (0 == selector.AppCallIfNotFoundHandled && 0 < currentUOW.AppCallIfNotFound.Count)
464 {
465 foreach (Application.CallIfDelegate func in currentUOW.AppCallIfNotFound)
466 {
467 safeUOW = application.CurrentUOW;
468
469 currentMethodName = func.Method.Name;
470 str = $"Application UOW Selector CallIfNotFound handler [{currentMethodName}] requested program termination on selector ]{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
471
472 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Invoking UOW CallIfNotFound [{func.Method.Name}]", application, GPALObjectType.Application);
473
474 currentUOW.AppCallIfNotFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
475
476 application.CurrentUOW = safeUOW;
477
478 if (CallIfStatus.NotHandled == currentUOW.AppCallIfNotFoundHandled)
479 continue;
480 else
481 break;
482 }
483
484 if (CallIfStatus.Terminate == currentUOW.AppCallIfNotFoundHandled)
485 {
486 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Application);
487 throw new GPALException(str);
488 }
489 }
490
491 // currentUOW.AppCallIfNotFoundHandled - if handled by UOW handler, don't call global handler
492 if (0 == currentUOW.AppCallIfNotFoundHandled && 0 < GPAL.GPALSettings.AppCallIfNotFoundList.Count)
493 {
494 foreach (Application.CallIfDelegate func in GPAL.GPALSettings.AppCallIfNotFoundList)
495 {
496 safeUOW = application.CurrentUOW;
497
498 currentMethodName = func.Method.Name;
499 str = $"Gloabl CallIfNotFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
500
501 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Invoking Global CallIfNotFound [{func.Method.Name}]", application, GPALObjectType.Application);
502
503 GPAL.GPALSettings.AppCallIfNotFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
504
505 application.CurrentUOW = safeUOW;
506
507 if (CallIfStatus.NotHandled == GPAL.GPALSettings.AppCallIfNotFoundHandled)
508 continue;
509 else
510 break;
511 }
512
513 if (CallIfStatus.Terminate == GPAL.GPALSettings.AppCallIfNotFoundHandled)
514 {
515 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Browser);
516 throw new GPALException(str);
517 }
518 }
519
520 if (true == selector.StopOnNotFound || true == GPAL.StopOnNotFound)
521 {
522 str = $@"{(true == selector.StopOnNotFound ? "selector." : "GPAL.")}StopOnNotFound set, terminating on Not Found element for [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
523 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Application);
524 throw new GPALException(str);
525 }
526 }
527 }
537 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByImage(Application application, SelectorPathEntry selectorPath, bool searchForSelector = true, AutomationElement element = null)
538 {
539 ReadOnlyCollection<GPALAutomationElement> elems = null;
540
541 Rectangle foundImageBounds = new Rectangle();
542 List<GPALAutomationElement> retElement = new List<GPALAutomationElement>(); ;
543
544 if (null == selectorPath.Image)
545 {
546 try
547 {
548 selectorPath.Image = Image.FromFile(selectorPath.SelectorPath);
549 }
550 catch (Exception ex)
551 {
552 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unable to load image for [{selectorPath.SelectorPath}]", application, GPALObjectType.Application, ex);
553 return null;
554 }
555 }
556
557 Image template = selectorPath.Image;
558 foundImageBounds = ((ImageHelper)GPAL.ImageHelper).FindImage(selectorPath.SelectorPath, application); // first check, is image we seek already on-screen?
559
560 // page down the application page looking for the image (just send page down key)
561 // TODO: does this make any sense for a desktop application?
562 //if (true == searchForSelector)
563 //{
564 // // Not found, start from top of page
565 // if (true == foundImageBounds.IsEmpty)
566 // SendKey(GPAL.VK_HOME); // page top
567
568 // while (true == foundImageBounds.IsEmpty && false == IsEndOfPage(element))
569 // {
570 // SendKey(GPAL.VK_NEXT); // page down
571 // Thread.Sleep(250);
572 // foundImageBounds = ImageHelper.FindImage(new Bitmap(template));
573 // }
574 //}
575
576 if (false == foundImageBounds.IsEmpty)
577 {
578 // create a psuedo automationelement to use in the subsequent methods
579 retElement.Add(new GPALAutomationElement(null, new GPALElement(foundImageBounds.Location, foundImageBounds.Size, selectorPath.SelectorPathType.ToString(), null, "GPALElement")));
580 elems = new ReadOnlyCollection<GPALAutomationElement>(retElement);
581 }
582 else
583 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $"Unable to find the image you are attempting to match.", application, GPALObjectType.Application);
584
585 return elems;
586 }
595 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByCSS(Application application, SelectorPathEntry selectorPath, bool searchForElement = true, AutomationElement element = null)
596 {
597 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Find elements by CSS is not supported for applications.", application, GPALObjectType.Application);
598 return null;
599 }
600
610 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByXPath(
611 Application application,
612 SelectorPathEntry selectorPath,
613 bool searchForElement = true,
614 AutomationElement element = null)
615 {
616 if (string.IsNullOrWhiteSpace(selectorPath?.SelectorPath))
617 return new ReadOnlyCollection<GPALAutomationElement>(Array.Empty<GPALAutomationElement>());
618
619 AutomationElement current = element ?? application.RootAutomationElement;
620 if (current == null)
621 return new ReadOnlyCollection<GPALAutomationElement>(Array.Empty<GPALAutomationElement>());
622
623 // Split on '/' and remove empty entries
624 string[] tokens = selectorPath.SelectorPath.Split('/');
625
626 // RootAutomationElement is AutomationElement.FromHandle(mainWindowHandle) - i.e. the top-level
627 // window itself, which is exactly what the recorder roots its path at. So the first token
628 // (typically the Window) must be matched against the current element itself, not its children.
629 bool firstToken = true;
630
631 foreach (string token in tokens)
632 {
633 if (string.IsNullOrWhiteSpace(token)) continue;
634
635 string controlTypeStr = token;
636 var conditions = new List<Condition>();
637
638 int attrStart = token.IndexOf('[');
639 if (attrStart > 0)
640 {
641 controlTypeStr = token.Substring(0, attrStart);
642
643 // Parse attributes [@Name='xxx'][@ClassName='yyy'] etc.
644 string remaining = token.Substring(attrStart);
645 while (remaining.Contains("[@"))
646 {
647 int start = remaining.IndexOf("[@");
648 int end = remaining.IndexOf(']', start);
649 if (end == -1) break;
650
651 string attrSegment = remaining.Substring(start + 2, end - start - 2);
652
653 var parts = attrSegment.Split(new char[] { '=' }, 2);
654
655 if (parts.Length == 2)
656 {
657 string attrName = parts[0].Trim();
658 string value = parts[1].Trim().Trim('\'', '"');
659
660 Condition cond = attrName.ToLowerInvariant() switch
661 {
662 "name" => new PropertyCondition(AutomationElement.NameProperty, value),
663 "automationid" => new PropertyCondition(AutomationElement.AutomationIdProperty, value),
664 "classname" => new PropertyCondition(AutomationElement.ClassNameProperty, value),
665 _ => null
666 };
667
668 if (cond != null)
669 conditions.Add(cond);
670 }
671
672 remaining = remaining.Substring(end + 1);
673 }
674 }
675
676 // Add ControlType condition
677 ControlType controlType = GetControlType(controlTypeStr);
678 if (controlType != null)
679 {
680 conditions.Add(new PropertyCondition(AutomationElement.ControlTypeProperty, controlType));
681 }
682
683 if (conditions.Count == 0)
684 continue;
685
686 Condition finalCondition = conditions.Count == 1
687 ? conditions[0]
688 : new AndCondition(conditions.ToArray());
689
690 AutomationElement match = null;
691
692 // For the first token, the current root window may itself be the match (TreeScope.Element
693 // evaluates the condition against the element itself), rather than one of its children.
694 if (firstToken)
695 match = current.FindFirst(TreeScope.Element, finalCondition);
696
697 // Otherwise (or if the root itself did not match) search direct children.
698 if (match == null)
699 {
700 var found = current.FindAll(TreeScope.Children, finalCondition);
701 if (found.Count > 0)
702 match = found[0];
703 }
704
705 if (match == null)
706 {
707 GPAL.PublishSimpleEvent(GPALEventType.ERROR,
708 $"Unable to locate element for Selector {selectorPath.SelectorPath}, token '{token}'.",
709 application, GPALObjectType.Application);
710 return new ReadOnlyCollection<GPALAutomationElement>(Array.Empty<GPALAutomationElement>());
711 }
712
713 // Move to the match
714 current = match;
715 firstToken = false;
716
717 // Expand menu items if needed
718 if (current.Current.ControlType == ControlType.MenuItem)
719 {
720 ExpandMenuItem(application, current);
721 }
722 }
723
724 return current != null
725 ? new ReadOnlyCollection<GPALAutomationElement>(new List<GPALAutomationElement> { new GPALAutomationElement(current) })
726 : new ReadOnlyCollection<GPALAutomationElement>(Array.Empty<GPALAutomationElement>());
727 }
728
735 private static ControlType GetControlType(string programmaticName)
736 {
737 if (string.IsNullOrWhiteSpace(programmaticName))
738 return null;
739
740 // Force initialization of the static constructor (important quirk!)
741 _ = ControlType.Button.ToString();
742
743 // Try direct lookup by name via the known AutomationControlType enum + offset
744 if (Enum.TryParse<System.Windows.Automation.Peers.AutomationControlType>(programmaticName, true, out var enumValue))
745 {
746 int id = (int)enumValue + 50000; // Official offset for ControlType IDs
747 return ControlType.LookupById(id);
748 }
749
750 // Fallback: try common aliases
751 return programmaticName.ToLowerInvariant() switch
752 {
753 "window" => ControlType.Window,
754 "pane" => ControlType.Pane,
755 "button" => ControlType.Button,
756 "edit" => ControlType.Edit, // TextBox
757 "textbox" => ControlType.Edit,
758 "combobox" => ControlType.ComboBox,
759 "list" => ControlType.List,
760 "listitem" => ControlType.ListItem,
761 "menuitem" => ControlType.MenuItem,
762 "tab" => ControlType.Tab,
763 "tabitem" => ControlType.TabItem,
764 "tree" => ControlType.Tree,
765 "treeitem" => ControlType.TreeItem,
766 "document" => ControlType.Document,
767 "group" => ControlType.Group,
768 "checkbox" => ControlType.CheckBox,
769 "radiobutton" => ControlType.RadioButton,
770 "hyperlink" => ControlType.Hyperlink,
771 "image" => ControlType.Image,
772 "spinner" => ControlType.Spinner,
773 "slider" => ControlType.Slider,
774 "statusbar" => ControlType.StatusBar,
775 "toolbar" => ControlType.ToolBar,
776 "datagrid" => ControlType.DataGrid,
777 "dataitem" => ControlType.DataItem,
778 _ => null
779 };
780 }
781 /*
782 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByXPath(Application application, SelectorPathEntry selectorPath, bool searchForElement = true, AutomationElement element = null)
783 {
784 ReadOnlyCollection<GPALAutomationElement> elem = null;
785 string[] tokens = selectorPath.SelectorPath.Split('/');
786 AutomationElement tmpAE = element ;
787
788 int maxCnt = tokens.Length;
789 int cnt = 0;
790 bool getOne = false;
791
792 foreach (string token in tokens)
793 {
794 cnt++;
795 if (0 < token.Length)
796 {
797 int start = token.IndexOf('[') + 1;
798 int nth = 0;
799
800 if (0 < start)
801 {
802 int length = token.IndexOf(']') - start;
803 string nthStr = token.Substring(start, length);
804 nth = Int32.Parse(nthStr) - 1; // 1 based first entry convert to zero-based c#
805 }
806
807 getOne = (0 == start);
808
809 string controlType = token;
810 if (0 < start)
811 controlType = token.Substring(0, start - 1);
812
813 if (null != tmpAE)
814 elem = tmpAE.FindElements(controlType, ConditionPropertyType.ControlType, nth);
815 else
816 elem = application.RootAutomationElement.FindElements(controlType, ConditionPropertyType.ControlType, nth, true);
817
818 if (0 < elem.Count)
819 {
820 List<GPALAutomationElement> AElist = new List<GPALAutomationElement>();
821 if (-1 != nth)
822 {
823 if (nth < elem.Count)
824 tmpAE = elem[nth].Ae;
825 //else
826 // tmpAE = null;
827 }
828 else
829 tmpAE = elem[0].Ae;
830
831 if (null != tmpAE)
832 {
833 AElist.Add(new GPALAutomationElement(tmpAE));
834 elem = new ReadOnlyCollection<GPALAutomationElement>(AElist);
835 }
836 else
837 elem = null; // do not return any found elements, in case nth element not found, could return what was found
838 // TODO: would that be useful here? browser side will not return anything
839 }
840
841 // If we are not at the last 'menu item' then expand it to expose it to UIA
842 if (0 < elem?.Count && ControlType.MenuItem == tmpAE?.Current.ControlType && cnt < maxCnt)
843 ExpandMenuItem(application, tmpAE);
844 else if (null == elem || 0 == elem?.Count)
845 {
846 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $"Unable to locate element for Selector [{selectorPath.SelectorPath}], token [{token}].", application, GPALObjectType.Application);
847 break;
848 }
849 }
850 }
851 return elem;
852 }
853 */
862 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByAutomationID(Application application, SelectorPathEntry selectorPath, bool searchForElement = true, AutomationElement element = null)
863 {
864 return FindElementsBy(application, selectorPath, ConditionPropertyType.AutomationID, searchForElement, element);
865 }
874 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByText(Application application, SelectorPathEntry selectorPath, bool searchForElement = true, AutomationElement element = null)
875 {
876 return FindElementsBy(application, selectorPath, ConditionPropertyType.Value, searchForElement, element); // TODO: CAVEAT: KLUDGE: is this correct? Value?
877 }
886 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByValue(Application application, SelectorPathEntry selectorPath, bool searchForElement = true, AutomationElement element = null)
887 {
888 return FindElementsBy(application, selectorPath, ConditionPropertyType.Value, searchForElement, element);
889 }
898 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByName(Application application, SelectorPathEntry selectorPath, bool searchForElement = true, AutomationElement element = null)
899 {
900 return FindElementsBy(application, selectorPath, ConditionPropertyType.Name, searchForElement, element);
901 }
910 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByClassName(Application application, SelectorPathEntry selectorPath, bool searchForElement = true, AutomationElement element = null)
911 {
912 return FindElementsBy(application, selectorPath, ConditionPropertyType.ClassName, searchForElement, element);
913 }
924 private static ReadOnlyCollection<GPALAutomationElement> FindElementsBy(Application application, SelectorPathEntry selectorPath, ConditionPropertyType conditionPropertyType, bool searchForElement = true, AutomationElement element = null)
925 {
926 ReadOnlyCollection<GPALAutomationElement> elem;
927
928 if (null != element)
929 elem = element.FindElements(selectorPath.SelectorPath, conditionPropertyType);
930 else
931 elem = application.RootAutomationElement.FindElements(selectorPath.SelectorPath, conditionPropertyType);
932
933 return elem;
934 }
944 internal static void PublishToEventHandler(Application application, Selector selector, List<GPALAutomationElement> elements)
945 {
946 string msg = "";
947 if (true == selector.DeleteMe)
948 msg = $"Ignoring selector {selector.Name} marked for deletion. Continuing.";
949 else if (null == elements || 0 == elements.Count)
950 msg = $"No elements found for selector {selector.Name}. Continuing.";
951
952 GPAL.PublishSimpleEvent(GPALEventType.INFO, msg, application, GPALObjectType.Application);
953 }
962 public static List<GPALAutomationElement> WaitFor(Application application, int timeoutInTicks, out bool matchedAll)
963 {
964 bool matchedAll2 = true;
965 List<GPALAutomationElement> foundElements = null;
966 List<GPALAutomationElement> tmpFoundElements = null;
967 List<GPALAutomationElement> matchedElements;
968
969 foundElements = new List<GPALAutomationElement>();
970
971 foreach (Selector selector in application.CurrentUOW.WithSelectorList)
972 {
973 if (SelectorType.Selector != selector.SelectorType)
974 continue; // we don't look up data literals
975
976 tmpFoundElements = WaitFor(application, selector, timeoutInTicks, out matchedAll2, out matchedElements);
977
978 tmpFoundElements = matchedElements;
979
980 // FindElements calls CallIF handlers which can handle an action and delete a node, check here before moving on
981 if (true == selector.DeleteMe || null == tmpFoundElements)
982 {
983 PublishToEventHandler(application, selector, tmpFoundElements);
984 continue;
985 }
986
987 int rowCount = 0;
988
989 foreach (GPALAutomationElement element in tmpFoundElements)
990 {
991 foundElements.Add(element);
992 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
993 break;
994 }
995 }
996
997 matchedAll = matchedAll2;
998 return foundElements;
999 }
1000
1011 private static List<GPALAutomationElement> WaitFor(Application application, Selector selector, int timeoutInTicks, out bool matchedAll, out List<GPALAutomationElement> matchedElements, AutomationElement element = null)
1012 {
1013 bool matchedAll2 = true, matchedAll3 = true;
1014 List<GPALAutomationElement> matchedElements2;
1015 ReadOnlyCollection<GPALAutomationElement> elements = FindElements(application, application.CurrentUOW, selector, out matchedAll2, out matchedElements2, element);
1016
1017 if (WaitTime.Forever == timeoutInTicks)
1018 timeoutInTicks = Int32.MaxValue; // forever
1019
1020 // keep looking while we have time left and nothing has turned up. FindElements returns an empty
1021 // collection (never null) when it finds nothing, so this has to test the count, not the reference.
1022 for (; 0 < timeoutInTicks && 0 == (elements?.Count ?? 0); timeoutInTicks -= 1000)
1023 {
1024 elements = FindElements(application, application.CurrentUOW, selector, out matchedAll3, out matchedElements2, element);
1025 if (0 == elements.Count)
1026 Thread.Sleep(Math.Min(1000, timeoutInTicks));
1027 else
1028 break;
1029 }
1030
1031 matchedAll = (matchedAll2 | matchedAll3); // one of those found it or not :)
1032 matchedElements = matchedElements2; // always use matched, it's either all or those matched
1033
1034 return elements?.ToList<GPALAutomationElement>();
1035 }
1036
1044 public static void FillInFrom(Application application, string useText, WriteMode writeMode)
1045 {
1046 List<GPALAutomationElement> matchedElements;
1047
1048 foreach (Selector selector in application.CurrentUOW.WithSelectorList)
1049 {
1050 if (SelectorType.Selector != selector.SelectorType)
1051 continue; // we don't look up data literals
1052
1053 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out bool matchedAll, out matchedElements);
1054
1055 // FindElements calls CallIF handlers which can handle an action and delete a node, check here before moving on
1056 if (true == selector.DeleteMe || null == elems)
1057 {
1058 PublishToEventHandler(application, selector, elems?.ToList());
1059 continue;
1060 }
1061
1062 if (null != matchedElements)
1063 elems = new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
1064
1065 int rowCount = 0;
1066
1067 foreach (GPALAutomationElement elem in elems)
1068 {
1069 if (null != elem.Ae)
1070 {
1071 bool isPassword = elem.Ae.Current.IsPassword;
1072
1073 string abbreviatedText = useText[0] + "..." + useText[useText.Length - 1];
1074
1075 if (false == isPassword)
1076 {
1077 if (GPALEventType.DEBUG == (GPALEventType.DEBUG & GPAL.GPALSettings.DebugEvents) || GPALEventType.DEBUG == (GPALEventType.DEBUG & GPAL.GPALSettings.ConsoleEvents))
1078 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $"[{writeMode}] text [{useText}] in element [{elem.Ae.Current.LocalizedControlType}][{selector.Name}]", application, GPALObjectType.Application);
1079 else
1080 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"[{writeMode}] text [{abbreviatedText}] in element [{elem.Ae.Current.LocalizedControlType}][{selector.Name}]", application, GPALObjectType.Application);
1081 }
1082 else
1083 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"[{writeMode}] XXXpasswordXXX in element [{elem.Ae.Current.LocalizedControlType}][{selector.Name}]", application, GPALObjectType.Application);
1084
1085 FillInFrom(application, elem.Ae, new UnitOfWork.ElementNode() { OffsetX = selector.OffsetX, OffsetY = selector.OffsetY }, useText, writeMode);
1086 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
1087 break;
1088 }
1089 }
1090
1091 UnitOfWork safeUOW = application.CurrentUOW;
1092 CallIfStatus handled = 0;
1093
1094 // CAVEAT: there is no concept of 'handled (1)' vs 'not handled (0)' but definitely can request to exit
1095 IGPALGrid<string> tokens = GPAL.Grid.ToGPALObject();
1096 tokens.AddRow(new List<string> { useText });
1097 if (null != application.CurrentUOW.AppCallAfterFillIn)
1098 handled = application.CurrentUOW.AppCallAfterFillIn(application, tokens, 1);
1099
1100 // CAVEAT: kludge - the call after handler can and probably will set a new current unit of work, but we need our old current unit
1101 // the call after handler UOW is no longer in scope, so restore our UOW
1102 application.CurrentUOW = safeUOW;
1103
1104 if (CallIfStatus.Terminate == handled)
1105 {
1106 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"CallAfterFillIn handler [{application.CurrentUOW.AppCallAfterFillIn.GetInvocationList()[0].Method.Name}] requested program termination.", application, GPALObjectType.Application);
1107 throw new GPALException($"CallAfterFillIn handler [{application.CurrentUOW.AppCallAfterFillIn.GetInvocationList()[0].Method.Name}] requested program termination.");
1108 }
1109 }
1110 }
1118 internal static void Scroll(Application application, object amount, ScrollTypes incrementsPercent, ScrollTypes horzVert)
1119 {
1120 List<GPALAutomationElement> matchedElements;
1121
1122 foreach (Selector selector in application.CurrentUOW.WithSelectorList)
1123 {
1124 if (SelectorType.Selector != selector.SelectorType)
1125 continue; // we don't look up data literals
1126
1127 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out bool matchedAll, out matchedElements);
1128
1129 // FindElements calls CallIF handlers which can handle an action and delete a node, check here before moving on
1130 if (true == selector.DeleteMe || null == elems)
1131 {
1132 PublishToEventHandler(application, selector, elems?.ToList());
1133 continue;
1134 }
1135
1136 if (null != matchedElements)
1137 elems = new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
1138
1139 int rowCount = 0;
1140
1141 foreach (GPALAutomationElement elem in elems)
1142 {
1143 if (null != elem.Ae)
1144 {
1145 Scroll(application, elem.Ae, selector.InteractionType, amount, incrementsPercent, horzVert);
1146 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
1147 break;
1148 }
1149 }
1150 }
1151 }
1162 internal static void Scroll(Application application, AutomationElement ae, InteractionType interactionType, object amount, ScrollTypes incrementsPercent, ScrollTypes horzVert)
1163 {
1164 //UIAutomation only
1165 if (Enums.InteractionType.Hardware == interactionType || true == application.applicationSettings.UseHardware)
1166 GPAL.PublishSimpleEvent(GPALEventType.INFO, "Hardware scrolling is not supported at this time. Find the scroll controls and click them instead.", application, GPALObjectType.Application);
1167 else
1168 {
1169 // the element is a scrollbar, so it only goes one direction horizontal or vertical
1170 if (true == ae.TryGetCurrentPattern(RangeValuePattern.Pattern, out object rangeValuePattern))
1171 {
1172 ((RangeValuePattern)rangeValuePattern).SetValue((double)amount);
1173 }
1174 else if (true == ae.TryGetCurrentPattern(ScrollPattern.Pattern, out object scrollPattern))
1175 {
1176 if (ScrollTypes.Percent == incrementsPercent)
1177 {
1178 double hPercent = ScrollTypes.Horizontal == horzVert ? (double)amount : ScrollPatternIdentifiers.NoScroll;
1179 double vPercent = ScrollTypes.Vertical == horzVert ? (double)amount : ScrollPatternIdentifiers.NoScroll;
1180 try
1181 {
1182 ((ScrollPattern)scrollPattern).SetScrollPercent(hPercent, vPercent);
1183 }
1184 catch (Exception ex)
1185 {
1186 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unable to set SetScrollPercent on control [{ae.Current.ControlType.LocalizedControlType}].", application, GPALObjectType.Application, ex);
1187 }
1188 }
1189 else
1190 {
1191 ScrollAmount hAmount = ScrollTypes.Horizontal == horzVert ? (ScrollAmount)amount : ScrollAmount.NoAmount;
1192 ScrollAmount vAmount = ScrollTypes.Vertical == horzVert ? (ScrollAmount)amount : ScrollAmount.NoAmount;
1193
1194 try
1195 {
1196 ((ScrollPattern)scrollPattern).Scroll(hAmount, vAmount);
1197 }
1198 catch (Exception ex)
1199 {
1200 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unable to Scroll on control [{ae.Current.ControlType.LocalizedControlType}].", application, GPALObjectType.Application, ex); ;
1201 }
1202 }
1203 }
1204
1205 }
1206 }
1216 internal static void FillInFrom(Application application, AutomationElement ae, UnitOfWork.ElementNode elementNode, string useText, WriteMode writeMode)
1217 {
1218 if (Enums.InteractionType.Hardware == elementNode.InteractionType || true == application.applicationSettings.UseHardware)
1219 HardwareFillInFrom(application, new GPALAutomationElement(ae), elementNode.OffsetX, elementNode.OffsetY, useText, writeMode);
1220 else
1221 {
1222 // LegacyIAccessiblePattern is only accessible via the UIAComWrapperX nuget package
1223 /*
1224 if (true == ae.TryGetCurrentPattern(System.Windows.Automation.LegacyIAccessiblePattern.Pattern, out object legacyPattern))
1225 {
1226 // Set focus for input functionality and begin.
1227 try
1228 {
1229 ae.SetFocus();
1230 }
1231 catch (Exception ex)
1232 {
1233 GPAL.GPALEventArgs args = new GPAL.GPALEventArgs()
1234 {
1235 GPALObject = application,
1236 GPALObjectType = GPALObjectType.Application,
1237 CurrentUOW = application.CurrentUOW,
1238 AutomationElement = ae,
1239 Message = $"Unable to set focus on Control {ae.Current.ControlType.LocalizedControlType}.",
1240 ExceptionRaised = ex,
1241 ScreenShot = ImageHelper.CaptureScreen(),
1242 DateTimeStamp = DateTime.Now
1243 };
1244
1245 GPAL.ExceptionHandler?.Invoke(application, args);
1246 }
1247 if (WriteMode.Overwrite == writeMode)
1248 ((LegacyIAccessiblePattern)legacyPattern).SetValue(useText);
1249 else if (WriteMode.Append == writeMode)
1250 {
1251 string content = ((LegacyIAccessiblePattern)legacyPattern).Current.Value;
1252 content += useText;
1253 ((LegacyIAccessiblePattern)legacyPattern).SetValue(content);
1254 }
1255 else if (WriteMode.Insert == writeMode)
1256 {
1257 string content = ((LegacyIAccessiblePattern)legacyPattern).Current.Value;
1258 useText += content;
1259 ((LegacyIAccessiblePattern)legacyPattern).SetValue(content);
1260 }
1261 }
1262 else
1263 */
1264 if (true == ae.TryGetCurrentPattern(ValuePattern.Pattern, out object valuePattern))
1265 {
1266 // Set focus for input functionality and begin.
1267 try
1268 {
1269 ae.SetFocus();
1270 }
1271 catch (Exception ex)
1272 {
1273 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unable to set focus on control [{ae.Current.ControlType.LocalizedControlType}].", application, GPALObjectType.Application, ex);
1274 }
1275
1276 if (WriteMode.Overwrite == writeMode)
1277 ((ValuePattern)valuePattern).SetValue(useText);
1278 else if (WriteMode.Append == writeMode)
1279 {
1280 string content = ((ValuePattern)valuePattern).Current.Value;
1281 content += useText;
1282 ((ValuePattern)valuePattern).SetValue(content);
1283 }
1284 else if (WriteMode.Insert == writeMode)
1285 {
1286 string content = ((ValuePattern)valuePattern).Current.Value;
1287 useText += content;
1288 ((ValuePattern)valuePattern).SetValue(content);
1289 }
1290 }
1291 else
1292 {
1293 if (false == GPAL.NoFallbackRecoveryActions)
1294 {
1295 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Control [{ae.Current.ControlType.LocalizedControlType}] for ElementNode does not support ValuePattern. Using hardware input fallback.", application, GPALObjectType.Application);
1296 HardwareFillInFrom(application, new GPALAutomationElement(ae), elementNode.OffsetX, elementNode.OffsetY, useText, writeMode);
1297 }
1298 else
1299 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Control [{ae.Current.ControlType.LocalizedControlType}] for ElementNode does not support ValuePattern. Continuing.", application, GPALObjectType.Application);
1300
1301 }
1302 }
1303 }
1314 public static void HardwareFillInFrom(Application application, GPALAutomationElement element, int offsetX, int offsetY, string text, WriteMode writeMode)
1315 {
1316 ApplicationHelper.TopProcess(application.Process);
1317
1318 HardwareFocus(application, element, offsetX, offsetY);
1319
1320 if (WriteMode.Overwrite == writeMode)
1321 {
1322 // clear the input, send CTRL-a (select all), then delete *altho we can prolly just typeover*
1323 HardwareHelper.SendChar('a', ModifierKeys.Control, true);
1324 Thread.Sleep(150);
1325 // HardwareHelper.SendKey(GPAL.VK_DELETE);
1326 }
1327 else if (WriteMode.Append == writeMode) // CTRL-End goto end of document
1328 {
1329 HardwareHelper.SendKey(GPAL.VK_END, ModifierKeys.Control);
1330 Thread.Sleep(150);
1331 }
1332 else if (WriteMode.Insert == writeMode) // CTRL-End goto start of document
1333 {
1334 HardwareHelper.SendKey(GPAL.VK_HOME, ModifierKeys.Control);
1335 Thread.Sleep(150);
1336 }
1337
1338 HardwareHelper.SendString(text, GPAL.TypingDelay);
1339 }
1340
1346 public static void Focus(Application application)
1347 {
1348 List<GPALAutomationElement> matchedElements;
1349
1350 foreach (Selector selector in application.CurrentUOW.WithSelectorList)
1351 {
1352 if (SelectorType.Selector != selector.SelectorType)
1353 continue; // we don't look up data literals
1354
1355 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out bool matchedAll, out matchedElements);
1356
1357 // FindElements calls CallIF handlers which can handle an action and delete a node, check here before moving on
1358 if (true == selector.DeleteMe || null == elems)
1359 {
1360 PublishToEventHandler(application, selector, elems?.ToList());
1361 continue;
1362 }
1363
1364 if (null != matchedElements)
1365 elems = new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
1366
1367 int rowCount = 0;
1368
1369 foreach (GPALAutomationElement elem in elems)
1370 {
1371 if (null != elem.Ae)
1372 {
1373 if (Enums.InteractionType.Hardware == selector.InteractionType || true == application.applicationSettings.UseHardware)
1374 {
1375 int offsetX, offsetY;
1376 offsetX = selector.SelectorSettings.OffsetX;
1377 offsetY = selector.SelectorSettings.OffsetY;
1378
1379 HardwareFocus(application, elem, offsetX, offsetY); // hardwre focus will try to click in text inputs
1380 }
1381 else
1382 {
1383
1384 try
1385 {
1386 if (null != elem.Ae)
1387 elem.Ae.SetFocus();
1388 else
1389 HardwareFocus(application, elem, 0, 0);
1390 }
1391 catch (Exception ex)
1392 {
1393 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Focus threw an excaption on selector [{selector.Name}].", application, GPALObjectType.Application, ex);
1394 }
1395 }
1396
1397 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
1398 break;
1399 }
1400 }
1401 }
1402 }
1410 public static void Hover(Application application, UnitOfWork currentUOW)
1411 {
1412 List<GPALAutomationElement> matchedElements;
1413
1414 foreach (Selector selector in currentUOW.WithSelectorList)
1415 {
1416 if (SelectorType.Selector != selector.SelectorType)
1417 continue; // we don't look up data literals
1418
1419 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, currentUOW, selector, out bool matchedAll, out matchedElements);
1420
1421 // FindElements calls CallIF handlers which can handle an action and delete a node, check here before moving on
1422 if (true == selector.DeleteMe || null == elems)
1423 {
1424 PublishToEventHandler(application, selector, elems?.ToList());
1425 continue;
1426 }
1427
1428 if (null != matchedElements)
1429 elems = new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
1430
1431 int rowCount = 0;
1432
1433 foreach (GPALAutomationElement elem in elems)
1434 {
1435 if (null != elem.Ae)
1436 {
1437 if (0 == elem.Ae.Current.BoundingRectangle.Width && 0 == elem.Ae.Current.BoundingRectangle.Height)
1438 {
1439 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Element has Zero Width and Height, skipping", application, GPALObjectType.Application);
1440 continue;
1441 }
1442
1443 if (Enums.InteractionType.Hardware == selector.InteractionType || true == application.applicationSettings.UseHardware)
1444 {
1445 // same as hardware moveto
1446 HardwareMoveTo(application, elem, selector.OffsetX, selector.OffsetY);
1447 }
1448 else if (0 != elem.Ae.Current.BoundingRectangle.Width && 0 != elem.Ae.Current.BoundingRectangle.Height)
1449 {
1450 try
1451 {
1452 elem.Ae.SetFocus();
1453 HardwareMoveTo(application, elem, selector.OffsetX, selector.OffsetY);
1454 }
1455 catch (Exception ex)
1456 {
1457 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"MoveToElement threw an excaption on selector [{selector.Name}], continuing.", application, GPALObjectType.Application, ex);
1458 HardwareMoveTo(application, elem, selector.OffsetX, selector.OffsetY);
1459 }
1460 }
1461
1462 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
1463 break;
1464 }
1465 }
1466 }
1467 }
1473 public static void MoveTo(Application application)
1474 {
1475 List<GPALAutomationElement> matchedElements;
1476
1477 foreach (Selector selector in application.CurrentUOW.WithSelectorList)
1478 {
1479 if (SelectorType.Selector != selector.SelectorType)
1480 continue; // we don't look up data literals
1481
1482 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out bool matchedAll, out matchedElements);
1483
1484 // FindElements calls CallIF handlers which can handle an action and delete a node, check here before moving on
1485 if (true == selector.DeleteMe || null == elems)
1486 {
1487 PublishToEventHandler(application, selector, elems?.ToList());
1488 continue;
1489 }
1490
1491 if (null != matchedElements)
1492 elems = new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
1493
1494 int rowCount = 0;
1495
1496 foreach (GPALAutomationElement elem in elems)
1497 {
1498 if (InteractionType.Hardware == selector.InteractionType || null != elem.Gae)
1499 HardwareMoveTo(application, elem, selector.OffsetX, selector.OffsetY);
1500 else if (null != elem.Ae)
1501 MoveTo(elem.Ae, selector.OffsetX, selector.OffsetY, application.Name);
1502
1503 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
1504 break;
1505 }
1506 }
1507 }
1508
1517 private static void MoveTo(AutomationElement element, int offsetX, int offsetY, string appName)
1518 {
1519 try
1520 {
1521 element.SetFocus();
1522 }
1523 catch (Exception ex)
1524 {
1525 if (false == GPAL.NoFallbackRecoveryActions)
1526 {
1527 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"(ElementHelper): SetFocus threw an excaption in Application [{appName}], falling back to hardware mouse move.", element, GPALObjectType.Other, ex);
1528 HardwareMoveTo(null, new GPALAutomationElement(element), offsetX, offsetY);
1529 }
1530 else
1531 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"(ElementHelper): SetFocus threw an excaption in Application [{appName}], continuing.", element, GPALObjectType.Other, ex);
1532 }
1533
1534 }
1542 private static void HardwareFocus(Application application, GPALAutomationElement element, int OffsetX, int OffsetY)
1543 {
1544 if (null != element.Ae && ControlType.Text == element.Ae.Current.ControlType)
1545 HardwareClick(application, element, ClickType.LeftClick, ModifierKeys.NONE, OffsetX, OffsetY);
1546 else
1547 HardwareMoveTo(application, element, OffsetX, OffsetY);
1548 }
1556 private static void HardwareMoveTo(Application application, GPALAutomationElement element, int OffsetX, int OffsetY)
1557 {
1558 Rectangle rect2 = new Rectangle(), iframeRect = new Rectangle(0, 0, 0, 0);
1559
1560 if (null != element.Gae)
1561 rect2.Location = element.Gae.Location;
1562 else
1563 {
1564 rect2.Location = new System.Drawing.Point((int) element.Ae.Current.BoundingRectangle.Location.X, (int) element.Ae.Current.BoundingRectangle.Location.Y);
1565
1566 // TODO: does this mean anything in an application?
1567 //if (true == application.CurrentUOW.CurrentSelector?.SearchForSelector)
1568 //{
1569 // // TODO: CAVEAT: is this good? we have to try to scroll the element on screen, but what happens if it can't find it?
1570 // if (false == IsVisibleInViewport(element))
1571 // {
1572 // SendKey(GPAL.VK_HOME); // home - go to document top, then scroll down
1573
1574 // while (false == IsVisibleInViewport(element) && false == IsEndOfPage(element))
1575 // {
1576 // SendKey(GPAL.VK_NEXT); // pagedown
1577 // Thread.Sleep(125);
1578 // }
1579 // rect2 = GetAbsCoordinates(application.BrowserSettings.BrowserType, element);
1580 // }
1581 //}
1582 }
1583
1584 if (true == GPAL.SimulateMouse || true == application.CurrentUOW.CurrentSelector?.SimulateMouse)
1585 HardwareHelper.MoveMouse(rect2.X + OffsetX, rect2.Y + OffsetY, 10, 10); // source didn't define values for rx, ry
1586 else
1587 HardwareHelper.MoveMouse(rect2.X + OffsetX, rect2.Y + OffsetY);
1588 }
1594 public static void LeftClick(Application application, ModifierKeys modifierKeys = ModifierKeys.NONE)
1595 {
1596 List<GPALAutomationElement> matchedElements;
1597
1598 foreach (Selector selector in application.CurrentUOW.WithSelectorList)
1599 {
1600 if (SelectorType.Selector != selector.SelectorType)
1601 continue; // we don't look up data literals
1602
1603 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out bool matchedAll, out matchedElements);
1604
1605 // FindElements calls CallIF handlers which can handle an action and delete a node, check here before moving on
1606 if (true == selector.DeleteMe || null == elems)
1607 {
1608 PublishToEventHandler(application, selector, elems?.ToList());
1609 continue;
1610 }
1611
1612 if (null != matchedElements)
1613 elems = new ReadOnlyCollection<GPALAutomationElement>(matchedElements); // either All or just those that matched
1614
1615 int rowCount = 0;
1616
1617 foreach (GPALAutomationElement elem in elems)
1618 {
1619 Click(application, selector, elem, ClickType.LeftClick, modifierKeys);
1620
1621 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
1622 break;
1623 }
1624 }
1625 }
1632 public static bool GenericClick(Application application, ClickType clickType)
1633 {
1634 List<GPALAutomationElement> matchedElements;
1635 bool matchedAll = false;
1636 bool clickPerformed = false;
1637
1638 foreach (Selector sel in application.CurrentUOW.WithSelectorList)
1639 {
1640 if (SelectorType.Selector != sel.SelectorType)
1641 continue; // we don't look up data literals
1642
1643 ElementHelper.FindElements(application, application.CurrentUOW, sel, out matchedAll, out matchedElements);
1644
1645 if (null != matchedElements)
1646 {
1647 int rowCount = 0;
1648
1649 foreach (GPALAutomationElement elem in matchedElements)
1650 {
1651 Click(application, sel, elem, clickType, ModifierKeys.NONE);
1652 clickPerformed = true;
1653
1654 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
1655 break;
1656 }
1657 }
1658 }
1659 return clickPerformed;
1660 }
1670 public static void Click(Application application, Selector selector, GPALAutomationElement element, ClickType clickType, ModifierKeys modifierKeys)
1671 {
1672 int offsetX, offsetY;
1673 offsetX = selector.SelectorSettings.OffsetX;
1674 offsetY = selector.SelectorSettings.OffsetY;
1675
1676 if (Enums.InteractionType.Hardware == selector.InteractionType || null != element.Gae || true == application.applicationSettings.UseHardware)
1677 HardwareClick(application, element, clickType, modifierKeys, offsetX, offsetY);
1678 else if (null != element.Ae)
1679 {
1680 UIAutomationClick(application, selector, element, clickType, modifierKeys);
1681 }
1682 }
1683
1693 public static void UIAutomationClick(Application application, Selector selector, GPALAutomationElement element, ClickType clickType, ModifierKeys modifierKeys)
1694 {
1695 object objPattern;
1696 try
1697 {
1698 if (true == element.Ae.TryGetCurrentPattern(InvokePattern.Pattern, out objPattern))
1699 {
1700 ((InvokePattern)objPattern).Invoke();
1701 }
1702 }
1703 catch (Exception ex)
1704 {
1705 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Invoke Pattern failed, trying hardware click.", application, GPALObjectType.Application, ex);
1706 HardwareClick(application, element, clickType, modifierKeys, selector?.OffsetX ?? 5, selector?.OffsetY ?? 5); // CAVEAT: if .ForSelector is not provided, default to 5
1707 }
1708 }
1709
1720 public static void HardwareClick(Application application, GPALAutomationElement element, ClickType clickType, ModifierKeys modifierKeys, int OffsetX = 0, int OffsetY = 0)
1721 {
1722 int offsetX = 0, offsetY = 0;
1723 Rectangle rect, iframeRect = new Rectangle(0,0,0,0);
1724 Random random = new Random(10007);
1725
1726 try
1727 {
1728 // move to the element before we click on it
1729 if (ModifierKeys.NONE != modifierKeys)
1730 HardwareHelper.PressModifierKey(modifierKeys);
1731
1732 HardwareMoveTo(application, element, OffsetX, OffsetY);
1733
1734 // now get it's location on screen
1735 if (null != element.Gae)
1736 rect = new Rectangle(element.Gae.Location, element.Gae.Size);
1737 else
1738 rect = new Rectangle(new System.Drawing.Point((int)element.Ae.Current.BoundingRectangle.Location.X, (int)element.Ae.Current.BoundingRectangle.Location.Y), new System.Drawing.Size((int)element.Ae.Current.BoundingRectangle.Size.Width, (int)element.Ae.Current.BoundingRectangle.Size.Height));
1739
1740 // offset not specified, so generate a random number somewhere inside the element
1741 if (0 < rect.Width && 0 < rect.Height)
1742 if (0 == OffsetX && 0 == OffsetY)
1743 {
1744 offsetX = random.Next(1, 0 != rect.Width ? rect.Width - 2 : 10);
1745 offsetY = random.Next(1, 0 != rect.Height ? rect.Height - 2 : 10);
1746 }
1747 else
1748 {
1749 offsetX = OffsetX;
1750 offsetY = OffsetY;
1751 }
1752
1753 // try to click on it using the defined 'clickable point'
1754 if (null != element.Ae && true == element.Ae.TryGetClickablePoint(out System.Windows.Point clickablePoint))
1755 HardwareHelper.LeftClick((int)clickablePoint.X, (int)clickablePoint.Y);
1756 else // try to click in bounding rectangle
1757 HardwareHelper.HardwareClick(rect.X + offsetX + iframeRect.X, rect.Y + offsetY + iframeRect.Y, clickType);
1758 }
1759 catch
1760 {
1761
1762 }
1763 finally
1764 {
1765 if (ModifierKeys.NONE != modifierKeys)
1766 HardwareHelper.ReleaseModifierKeys(modifierKeys);
1767 }
1768 }
1775 public static void DragAndDrop(Application application, ModifierKeys modifierKeys = ModifierKeys.NONE)
1776 {
1777 List<GPALAutomationElement> matchedElements;
1778
1779 foreach (Selector selector in application.CurrentUOW.WithSelectorList)
1780 {
1781 if (SelectorType.Selector != selector.SelectorType)
1782 continue;
1783
1784 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out bool matchedAll, out matchedElements);
1785
1786 if (true == selector.DeleteMe || null == elems)
1787 {
1788 PublishToEventHandler(application, selector, elems?.ToList());
1789 continue;
1790 }
1791
1792 if (null != matchedElements)
1793 elems = new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
1794
1795 int rowCount = 0;
1796
1797 foreach (GPALAutomationElement elem in elems)
1798 {
1799 Rectangle rect;
1800
1801 if (null != elem.Gae)
1802 rect = new Rectangle(elem.Gae.Location, elem.Gae.Size);
1803 else
1804 rect = new Rectangle(
1805 new System.Drawing.Point((int)elem.Ae.Current.BoundingRectangle.X, (int)elem.Ae.Current.BoundingRectangle.Y),
1806 new System.Drawing.Size((int)elem.Ae.Current.BoundingRectangle.Width, (int)elem.Ae.Current.BoundingRectangle.Height));
1807
1808 if (ModifierKeys.NONE != modifierKeys)
1809 HardwareHelper.PressModifierKey(modifierKeys);
1810
1811 // Mouse down at element origin + OffsetX/Y, mouse up at that point + DeltaX/Y.
1812 HardwareHelper.MouseDown(rect.X + selector.OffsetX, rect.Y + selector.OffsetY, ClickType.LeftClick);
1813 HardwareHelper.MouseUp(rect.X + selector.OffsetX + selector.DeltaX, rect.Y + selector.OffsetY + selector.DeltaY, ClickType.LeftClick);
1814
1815 if (ModifierKeys.NONE != modifierKeys)
1816 HardwareHelper.ReleaseModifierKeys(modifierKeys);
1817
1818 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
1819 break;
1820 }
1821 }
1822 }
1823
1831 public static void ExpandMenuItem(Application application, AutomationElement automationElement)
1832 {
1833 if (true == GPAL.SimulateMouse || true == application.CurrentUOW.CurrentSelector?.SimulateMouse)
1834 {
1835 HardwareHelper.MoveMouse((int)automationElement.Current.BoundingRectangle.X, (int)automationElement.Current.BoundingRectangle.Y, 10, 10); // source didn't define values for rx, ry }
1836 HardwareHelper.LeftClick((int)automationElement.Current.BoundingRectangle.X, (int)automationElement.Current.BoundingRectangle.Y);
1837 Thread.Sleep(250); // wait for display before trying to find elements
1838 }
1839 else
1840 {
1841 try
1842 {
1843 ExpandCollapsePattern pattern = automationElement.GetCurrentPattern(ExpandCollapsePattern.Pattern) as ExpandCollapsePattern;
1844 pattern.Expand();
1845 }
1846 catch (Exception ex)
1847 {
1848 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unable to expand menuitem for Control [{automationElement.Current.LocalizedControlType}], trying InvokePattern.", application, GPALObjectType.Application, ex);
1849 try
1850 {
1851 InvokePattern pattern = automationElement.GetCurrentPattern(ExpandCollapsePattern.Pattern) as InvokePattern;
1852 pattern.Invoke();
1853 }
1854 catch (Exception ex2)
1855 {
1856 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unable to expand menuitem for Control [{automationElement.Current.LocalizedControlType}], using hardware fallback.", application, GPALObjectType.Application, ex2);
1857
1858 Random random = new Random(10007);
1859
1860 int offsetX = random.Next(1, (int)automationElement.Current.BoundingRectangle.Width - 2);
1861 int offsetY = random.Next(1, (int)automationElement.Current.BoundingRectangle.Height - 2);
1862 ElementHelper.HardwareClick(null, new GPALAutomationElement(automationElement), Enums.ClickType.LeftClick, Enums.ModifierKeys.NONE, offsetX, offsetY);
1863 }
1864 }
1865 }
1866 }
1871 public static void SwitchToTab(Application application)
1872 {
1873 List<GPALAutomationElement> matchedElements;
1874 bool matchedAll = false;
1875
1876 foreach (Selector sel in application.CurrentUOW.WithSelectorList)
1877 {
1878 if (SelectorType.Selector != sel.SelectorType)
1879 continue; // we don't look up data literals
1880
1881 ElementHelper.FindElements(application, application.CurrentUOW, sel, out matchedAll, out matchedElements);
1882
1883 if (null != matchedElements)
1884 {
1885 int rowCount = 0;
1886
1887 foreach (GPALAutomationElement elem in matchedElements)
1888 {
1889 if (null != elem.Ae && "TabControl" == elem.Ae.Current.LocalizedControlType)
1890 {
1891 if (true == elem.Ae.TryGetCurrentPattern(SelectionItemPattern.Pattern, out object selectionPattern))
1892 {
1893 try
1894 {
1895 ((SelectionItemPattern)selectionPattern).Select();
1896 }
1897 catch (Exception ex)
1898 {
1899 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Unable to use SelectionPattern to select the tab [{elem.Ae.Current.Name}], falling back on hardware click.", application, GPALObjectType.Application, ex);
1900 HardwareClick(application, elem, ClickType.LeftClick, ModifierKeys.NONE, sel.OffsetX, sel.OffsetY);
1901 }
1902 }
1903 else
1904 HardwareClick(application, elem, ClickType.LeftClick, ModifierKeys.NONE, sel.OffsetX, sel.OffsetY);
1905
1906 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
1907 break;
1908 }
1909 }
1910 }
1911 }
1912
1913 }
1919 public static void SetRange(Application application, double rangeValue)
1920 {
1921 List<GPALAutomationElement> matchedElements;
1922 bool matchedAll = false;
1923
1924 foreach (Selector sel in application.CurrentUOW.WithSelectorList)
1925 {
1926 if (SelectorType.Selector != sel.SelectorType)
1927 continue;
1928
1929 ElementHelper.FindElements(application, application.CurrentUOW, sel, out matchedAll, out matchedElements);
1930
1931 if (null != matchedElements)
1932 {
1933 foreach (GPALAutomationElement elem in matchedElements)
1934 {
1935 if (null == elem.Ae) continue;
1936
1937 if (true == elem.Ae.TryGetCurrentPattern(RangeValuePattern.Pattern, out object rangeValuePattern))
1938 {
1939 try
1940 {
1941 ((RangeValuePattern)rangeValuePattern).SetValue(rangeValue);
1942 }
1943 catch (Exception ex)
1944 {
1945 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Unable to use RangeValuePattern to set value [{rangeValue}] on [{elem.Ae.Current.LocalizedControlType}].", application, GPALObjectType.Application, ex);
1946 }
1947 }
1948 else if (true == elem.Ae.TryGetCurrentPattern(ValuePattern.Pattern, out object valuePattern))
1949 {
1950 try
1951 {
1952 ((ValuePattern)valuePattern).SetValue(rangeValue.ToString());
1953 }
1954 catch (Exception ex)
1955 {
1956 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Unable to use ValuePattern to set value [{rangeValue}] on [{elem.Ae.Current.LocalizedControlType}].", application, GPALObjectType.Application, ex);
1957 }
1958 }
1959 else
1960 {
1961 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Control [{elem.Ae.Current.LocalizedControlType}] does not support RangeValuePattern or ValuePattern - unable to set range value.", application, GPALObjectType.Application);
1962 }
1963 }
1964 }
1965 }
1966 }
1967 }
1968}
1969
1970
1971
Settings for the current selector. Use this only for debugging.