18using System.Collections.Generic;
19using System.Collections.ObjectModel;
20using System.Diagnostics;
23using System.Runtime.InteropServices;
24using System.Text.RegularExpressions;
25using System.Threading;
27using System.Windows.Automation;
28using System.Windows.Forms;
30using OpenQA.Selenium.Interactions;
31using OpenQA.Selenium.Support.UI;
34using Condition = System.Windows.Automation.Condition;
40 internal class ElementHelper
42 delegate ReadOnlyCollection<GPALAutomationElement> FindElementsByDelegate(Application application, SelectorPathEntry selectorPath,
bool searchForSelector =
true, AutomationElement element =
null);
45 static byte ScanCode {
get; } = 0;
48 static List<string> listOfMessages =
new List<string>();
49 static bool supressedMessage =
false;
50 static Selector lastSelector =
null;
64 public static ReadOnlyCollection<GPALAutomationElement> FindElements(Application application, UnitOfWork currentUOW, Selector selector, out
bool matchedAll, out List<GPALAutomationElement> matchedElements, AutomationElement automationElement =
null,
bool callIfHandler =
true)
68 string elementStringToMatch =
null;
69 ReadOnlyCollection<GPALAutomationElement> retElems =
null;
70 List<GPALAutomationElement> matchedElems =
new List<GPALAutomationElement>();
71 MatchCollection mc =
null;
72 ReadOnlyCollection<GPALAutomationElement> allFoundElems =
null;
73 List<GPALAutomationElement> totalAllFoundElems =
new List<GPALAutomationElement>();
74 List<GPALAutomationElement> iterateOverTheseElements =
null;
75 bool tryNextSelector =
false;
77 string message =
string.Empty;
82 matchedElements =
new List<GPALAutomationElement>();
84 currentUOW.CurrentSelector = selector;
86 if (
false == lastSelector?.Name.Equals(selector.Name))
88 listOfMessages.Clear();
89 supressedMessage =
false;
90 lastSelector = selector;
92 else if (
null == lastSelector)
93 lastSelector = selector;
97 List<FindElementsByDelegate> functionList =
new List<FindElementsByDelegate> {
null , FindElementsByCSS, FindElementsByImage, FindElementsByText, FindElementsByValue, FindElementsByXPath, FindElementsByName, FindElementsByAutomationID, FindElementsByClassName, FindElementsByPlaceholder };
99 foreach (SelectorSettings.SelectorPathEntry selectorPathEntry in selector.SelectorPaths)
102 int textLength = selectorPathEntry.SelectorPath.Length - 1;
103 string abbreviatedText = selectorPathEntry.SelectorPath.Substring(0, Math.Min(7, textLength)) +
"..." + selectorPathEntry.SelectorPath.Substring(Math.Max(0, textLength - 7));
104 if (18 > selectorPathEntry.SelectorPath.Length)
105 abbreviatedText = selectorPathEntry.SelectorPath;
107 int idx = GetIndexOfEnum<SelectorPathType>((
int)selectorPathEntry.SelectorPathType);
108 tryNextSelector =
false;
109 string errorMessage = $
"Error finding elements for selector [{selector.Name}][{abbreviatedText}]";
113 message = $
"Searching for elements for selector [{selector.Name}] using [{selectorPathEntry.SelectorPathType}] [{abbreviatedText}]";
115 if (
false == listOfMessages.Contains(message))
117 listOfMessages.Add(message);
118 GPAL.PublishSimpleEvent(GPALEventType.INFO, message, application, GPALObjectType.Application);
121 allFoundElems = functionList[idx](application, selectorPathEntry, selector.SearchForSelector, automationElement);
124 if (0 < (allFoundElems?.Count ?? 0))
125 totalAllFoundElems.AddRange(allFoundElems.ToList());
127 #region <Element Matching>
128 if (0 < selector.MatchCriteria.Count)
130 foreach (SelectorSettings.MatchCriteriaEntry matchCriteriaEntry in selector.MatchCriteria)
132 if (MatchType.Custom == matchCriteriaEntry.MatchType)
135 CallIfStatus handled = matchCriteriaEntry.AppMatchingFunction(allFoundElems.ToList(), out matchedElems, out matchedAll, selector, matchCriteriaEntry.ExactMatch);
137 if (CallIfStatus.Terminate == handled)
139 string msg = $
"Custom match handler [{matchCriteriaEntry.AppMatchingFunction.GetInvocationList()[0].Method.Name}] requested program termination for selector {selector.Name} path [{selectorPathEntry.SelectorPath}].";
140 GPAL.PublishSimpleEvent(GPALEventType.WARNING, msg, application, GPALObjectType.Application);
141 throw new GPALException(msg);
145 matchedElements.AddRange(matchedElems);
146 matchedElements = matchedElements.Distinct().ToList();
148 if (CallIfStatus.Handled == handled)
150 else if (CallIfStatus.TryNext == handled)
152 tryNextSelector =
true;
158 if (
true == tryNextSelector)
163 if (0 == matchedElements.Count)
164 iterateOverTheseElements =
new List<GPALAutomationElement>(allFoundElems);
166 iterateOverTheseElements =
new List<GPALAutomationElement>(matchedElements);
169 for (idx = 0; idx < iterateOverTheseElements.Count; idx++)
171 GPALAutomationElement automationElement2 = iterateOverTheseElements[idx];
174 for (
int matchIdx = 0; matchIdx < selector.MatchCriteria.Count; matchIdx++)
176 SelectorSettings.MatchCriteriaEntry matchCriteriaEntry = selector.MatchCriteria[matchIdx];
178 if (MatchType.None != matchCriteriaEntry.MatchType)
183 switch ((MatchType)((
int)matchCriteriaEntry.MatchType & 0XFE))
186 elementStringToMatch = automationElement2.Ae.GetText();
194 case MatchType.Placeholder:
195 elementStringToMatch = automationElement2.Ae.GetText();
202 case MatchType.Value:
203 elementStringToMatch = automationElement2.Ae.GetValue() ?? automationElement2.Ae.GetText();
207 if (1 == ((
int)MatchType.Regex & (
int)matchCriteriaEntry.MatchType))
210 mc = Regex.Matches(elementStringToMatch, matchCriteriaEntry.StringToMatch);
212 matchedElems.Add(automationElement2);
216 if (
false == matchCriteriaEntry.ExactMatch && elementStringToMatch.Contains(matchCriteriaEntry.StringToMatch))
217 matchedElems.Add(automationElement2);
218 else if (
true == matchCriteriaEntry.ExactMatch && elementStringToMatch.Equals(matchCriteriaEntry.StringToMatch))
219 matchedElems.Add(automationElement2);
223 if (criteriaCnt == matchedElems.Count)
224 matchedElements.AddRange(matchedElems.Distinct());
226 matchedElements = matchedElements.Distinct().ToList();
227 matchedAll = (allFoundElems.Count == foundCount);
232 matchedElems = allFoundElems?.ToList() ??
new List<GPALAutomationElement>();
233 matchedElements = matchedElems;
235 #endregion <Element Matching>
240 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Exception thrown trying to find elements for selector [{selector.Name}]. Continuing.", currentUOW, GPALObjectType.UnitOfWork, ex);
246 if (
int.MaxValue != currentUOW.WithAllThatMatch && 0 < matchedElements?.Count)
249 List<GPALAutomationElement> tmpList =
new List<GPALAutomationElement>();
252 while (elemIdx < Math.Min((
int)currentUOW.WithAllThatMatch, (
int)matchedElements?.Count))
253 tmpList.Add(matchedElements[elemIdx++]);
255 matchedElements = tmpList;
258 selectorPathEntry.AppSelectorFoundResults = allFoundElems?.ToList() ??
new List<GPALAutomationElement>();
259 selectorPathEntry.AppSelectorMatchedResults = matchedElems;
261 retElems =
new ReadOnlyCollection<GPALAutomationElement>(totalAllFoundElems.Distinct().ToList());
263 foreach (GPALAutomationElement element
in retElems)
265 element.Application = application;
266 element.Selector = selector;
269 foreach (GPALAutomationElement element
in matchedElements)
271 element.Application = application;
272 element.Selector = selector;
275 if (
true == retry && 0 < retries--)
281 selector.ElementsFoundAndMatchedCount = matchedElements.Count();
283 message = $
"[{retElems?.Count ?? 0}] elements found for selector [{selector.Name}]";
285 if (
false == listOfMessages.Contains(message))
287 listOfMessages.Add(message);
288 GPAL.PublishSimpleEvent(GPALEventType.INFO, message, application, GPALObjectType.Application);
290 if (0 < retElems?.Count && (
true == selector.AnyMatchCriteria() || Int32.MaxValue != currentUOW.WithAllThatMatch))
292 var matchCount = selector.MatchCriteria.Count > 0 ? selector.MatchCriteria.Count.ToString() : $
"WithAllThatMatch({currentUOW.WithAllThatMatch})";
294 if ((
true == matchCount.Contains(
"WithAllThatMatch") && 1 < currentUOW.WithAllThatMatch) || 0 < selector.MatchCriteria.Count)
295 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{matchedElements.Count}] elements matched for selector [{selector.Name}] with [{matchCount}] match criteria", application, GPALObjectType.Application);
298 else if (
false == supressedMessage)
300 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Supressing repeat messages", application, GPALObjectType.Application);
301 supressedMessage =
true;
307 if (
true == matchedAll || retElems?.Count > 0)
313 if (
true == callIfHandler)
314 CallIfHandlers(application, currentUOW, allFoundElems, matchedElements, selector, matchedAll);
319 selector.ElementsFoundAndMatchedCount =
true == selector.AnyMatchCriteria()
320 ? (matchedElements?.Count() ?? 0)
321 : (retElems?.Count ?? 0);
339 private static void CallIfHandlers(Application application, UnitOfWork currentUOW, ReadOnlyCollection<GPALAutomationElement> foundElements, List<GPALAutomationElement> matchedElements, Selector selector,
bool matchedAll)
342 string currentMethodName =
null;
351 if (0 < (foundElements?.Count ?? 0))
353 if (0 < selector.AppCallIfFound?.Count)
355 foreach (Application.CallIfDelegate func in selector.AppCallIfFound)
357 bool safeActionCalled = application.CurrentUOW.ActionCalled;
358 safeUOW = application.CurrentUOW;
359 application.CurrentUOW.ActionCalled =
true;
361 currentMethodName = func.Method.Name;
362 str = $
"Application Selector CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
364 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Selector CallIfFound [{func.Method.Name}]", application, GPALObjectType.Application);
366 selector.AppCallIfFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
368 application.CurrentUOW = safeUOW;
369 application.CurrentUOW.ActionCalled = safeActionCalled;
371 if (CallIfStatus.NotHandled == selector.AppCallIfFoundHandled)
377 if (CallIfStatus.Terminate == selector.AppCallIfFoundHandled)
379 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Application);
380 throw new GPALException(str);
385 if (0 == selector.AppCallIfFoundHandled && 0 < currentUOW.AppCallIfFound.Count)
387 foreach (Application.CallIfDelegate func in currentUOW.AppCallIfFound)
389 safeUOW = application.CurrentUOW;
391 currentMethodName = func.Method.Name;
392 str = $
"Application UOW Selector CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
394 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking UOW CallIfFound [{func.Method.Name}]", application, GPALObjectType.Application);
396 currentUOW.CallIfNotFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
398 application.CurrentUOW = safeUOW;
400 if (CallIfStatus.NotHandled == currentUOW.CallIfNotFoundHandled)
406 if (CallIfStatus.Terminate == currentUOW.AppCallIfFoundHandled)
408 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Application);
409 throw new GPALException(str);
414 if (0 == currentUOW.AppCallIfFoundHandled && 0 < GPAL.GPALSettings.AppCallIfFoundList.Count)
416 foreach (Application.CallIfDelegate func in GPAL.GPALSettings.AppCallIfFoundList)
418 safeUOW = application.CurrentUOW;
420 currentMethodName = func.Method.Name;
421 str = $
"Gloabl CallIfFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
423 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Global CallIfFound [{func.Method.Name}]", application, GPALObjectType.Application);
425 GPAL.GPALSettings.AppCallIfFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
427 application.CurrentUOW = safeUOW;
429 if (CallIfStatus.NotHandled == GPAL.GPALSettings.AppCallIfFoundHandled)
435 if (CallIfStatus.Terminate == GPAL.GPALSettings.AppCallIfFoundHandled)
437 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Browser);
438 throw new GPALException(str);
444 if (0 < selector.AppCallIfNotFound.Count)
446 foreach (Application.CallIfDelegate func in currentUOW.AppCallIfNotFound)
448 safeUOW = application.CurrentUOW;
450 currentMethodName = func.Method.Name;
451 str = $
"Application Selector CallIfNotFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
453 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Selector CallIfNotFound [{func.Method.Name}]", application, GPALObjectType.Application);
455 selector.AppCallIfNotFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
457 application.CurrentUOW = safeUOW;
459 if (CallIfStatus.NotHandled == selector.AppCallIfNotFoundHandled)
465 if (CallIfStatus.Terminate == currentUOW.AppCallIfNotFoundHandled)
467 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Application);
468 throw new GPALException(str);
472 if (0 == selector.AppCallIfNotFoundHandled && 0 < currentUOW.AppCallIfNotFound.Count)
474 foreach (Application.CallIfDelegate func in currentUOW.AppCallIfNotFound)
476 safeUOW = application.CurrentUOW;
478 currentMethodName = func.Method.Name;
479 str = $
"Application UOW Selector CallIfNotFound handler [{currentMethodName}] requested program termination on selector ]{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
481 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking UOW CallIfNotFound [{func.Method.Name}]", application, GPALObjectType.Application);
483 currentUOW.AppCallIfNotFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
485 application.CurrentUOW = safeUOW;
487 if (CallIfStatus.NotHandled == currentUOW.AppCallIfNotFoundHandled)
493 if (CallIfStatus.Terminate == currentUOW.AppCallIfNotFoundHandled)
495 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Application);
496 throw new GPALException(str);
501 if (0 == currentUOW.AppCallIfNotFoundHandled && 0 < GPAL.GPALSettings.AppCallIfNotFoundList.Count)
503 foreach (Application.CallIfDelegate func in GPAL.GPALSettings.AppCallIfNotFoundList)
505 safeUOW = application.CurrentUOW;
507 currentMethodName = func.Method.Name;
508 str = $
"Gloabl CallIfNotFound handler [{currentMethodName}] requested program termination on selector [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
510 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Invoking Global CallIfNotFound [{func.Method.Name}]", application, GPALObjectType.Application);
512 GPAL.GPALSettings.AppCallIfNotFoundHandled = func(application, foundElements?.Cast<IGPALAutomationElement>().ToList(), matchedElements?.Cast<IGPALAutomationElement>().ToList(), selector, matchedAll);
514 application.CurrentUOW = safeUOW;
516 if (CallIfStatus.NotHandled == GPAL.GPALSettings.AppCallIfNotFoundHandled)
522 if (CallIfStatus.Terminate == GPAL.GPALSettings.AppCallIfNotFoundHandled)
524 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Browser);
525 throw new GPALException(str);
529 if (
true == selector.StopOnNotFound ||
true == GPAL.StopOnNotFound)
531 str =
$@"{(true == selector.StopOnNotFound ? "selector.
" : "GPAL.
")}StopOnNotFound set, terminating on Not Found element for [{selector.Name}] [{selector.SelectorPaths[0].SelectorPath}]";
532 GPAL.PublishSimpleEvent(GPALEventType.INFO, str, application, GPALObjectType.Application);
533 throw new GPALException(str);
546 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByImage(Application application, SelectorPathEntry selectorPath,
bool searchForSelector =
true, AutomationElement element =
null)
548 ReadOnlyCollection<GPALAutomationElement> elems =
null;
550 Rectangle foundImageBounds =
new Rectangle();
551 List<GPALAutomationElement> retElement =
new List<GPALAutomationElement>(); ;
553 if (
null == selectorPath.Image)
557 selectorPath.Image = Image.FromFile(selectorPath.SelectorPath);
561 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to load image for [{selectorPath.SelectorPath}]", application, GPALObjectType.Application, ex);
566 Image
template = selectorPath.Image;
567 foundImageBounds = ((ImageHelper)GPAL.ImageHelper).FindImage(selectorPath.SelectorPath, application);
573 if (
true == searchForSelector &&
true == foundImageBounds.IsEmpty &&
null != GPAL.ImageMatcher)
575 ScrollPattern view = application.ScrollableView();
579 view.SetScrollPercent(ScrollPattern.NoScroll, 0);
581 foundImageBounds = ((ImageHelper)GPAL.ImageHelper).FindImage(selectorPath.SelectorPath, application);
587 while (
true == foundImageBounds.IsEmpty && 100 > Math.Round(view.Current.VerticalScrollPercent) && was != view.Current.VerticalScrollPercent)
589 was = view.Current.VerticalScrollPercent;
590 view.ScrollVertical(ScrollAmount.LargeIncrement);
592 foundImageBounds = ((ImageHelper)GPAL.ImageHelper).FindImage(selectorPath.SelectorPath, application);
597 if (
false == foundImageBounds.IsEmpty)
600 retElement.Add(
new GPALAutomationElement(
null,
new GPALElement(foundImageBounds.Location, foundImageBounds.Size, selectorPath.SelectorPathType.ToString(),
null,
"GPALElement")));
601 elems =
new ReadOnlyCollection<GPALAutomationElement>(retElement);
606 Selector selector = application.CurrentUOW?.CurrentSelector;
608 if (
false == application.applicationSettings.HiddenDesktop
609 &&
false == application.applicationSettings.UseHardware
610 && Enums.InteractionType.Hardware != selector?.InteractionType)
612 GPALAutomationElement under = ControlUnderImage(application, foundImageBounds, selector);
616 under.ImageMatch = retElement[0];
617 elems =
new ReadOnlyCollection<GPALAutomationElement>(
new List<GPALAutomationElement> { under });
641 private static GPALAutomationElement ControlUnderImage(Application application, Rectangle match, Selector selector)
643 GPALAutomationElement retVal =
null;
646 int aimX = match.X + ((0 == (selector?.OffsetX ?? 0) && 0 == (selector?.OffsetY ?? 0)) ? match.Width / 2 : selector.OffsetX);
647 int aimY = match.Y + ((0 == (selector?.OffsetX ?? 0) && 0 == (selector?.OffsetY ?? 0)) ? match.Height / 2 : selector.OffsetY);
651 AutomationElement ae = AutomationElement.FromPoint(
new System.Windows.Point(aimX, aimY));
657 AutomationElement step = ae;
658 AutomationElement actionable =
null;
660 while (
null == actionable &&
null != step &&
false == Automation.Compare(step, AutomationElement.RootElement))
662 if (
true == IsActionable(step))
665 step = TreeWalker.RawViewWalker.GetParent(step);
668 retVal =
new GPALAutomationElement(actionable ?? ae);
674 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Could not read the control under the image match for [{selector?.Name}]", application, GPALObjectType.Application, ex);
677 GPAL.PublishSimpleEvent(GPALEventType.DEBUG,
null != retVal
678 ? $
"Image match for [{selector?.Name}] is control [{retVal.Ae.Current.ControlType.ProgrammaticName}][{retVal.Ae.Current.Name}], clicked at the matched point"
679 : $
"Image match for [{selector?.Name}] has no control of its own under it, so only the point is used", application, GPALObjectType.Application);
689 private static bool IsActionable(AutomationElement ae)
691 ControlType type = ae.Current.ControlType;
693 return true == (bool)ae.GetCurrentPropertyValue(AutomationElement.IsInvokePatternAvailableProperty)
694 ||
true == (bool)ae.GetCurrentPropertyValue(AutomationElement.IsTogglePatternAvailableProperty)
695 ||
true == (bool)ae.GetCurrentPropertyValue(AutomationElement.IsValuePatternAvailableProperty)
696 ||
true == (bool)ae.GetCurrentPropertyValue(AutomationElement.IsSelectionItemPatternAvailableProperty)
697 ||
true == (bool)ae.GetCurrentPropertyValue(AutomationElement.IsExpandCollapsePatternAvailableProperty)
698 || ControlType.Button == type || ControlType.MenuItem == type || ControlType.CheckBox == type
699 || ControlType.RadioButton == type || ControlType.Edit == type || ControlType.Hyperlink == type
700 || ControlType.ComboBox == type || ControlType.ListItem == type || ControlType.TabItem == type
701 || ControlType.TreeItem == type || ControlType.SplitButton == type || ControlType.DataItem == type;
711 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByCSS(Application application, SelectorPathEntry selectorPath,
bool searchForElement =
true, AutomationElement element =
null)
713 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Find elements by CSS is not supported for applications.", application, GPALObjectType.Application);
724 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByPlaceholder(Application application, SelectorPathEntry selectorPath,
bool searchForElement =
true, AutomationElement element =
null)
726 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Find elements by placeholder is not supported for applications.", application, GPALObjectType.Application);
745 private static bool RootedAtDesktop(
string selectorPath)
747 string[] tokens = (selectorPath ??
string.Empty).Split(
new char[] {
'/' }, StringSplitOptions.RemoveEmptyEntries);
749 return 1 < tokens.Length
750 &&
"desktop" == tokens[0].ToLowerInvariant()
751 &&
false == tokens[1].ToLowerInvariant().StartsWith(
"window");
754 [DllImport(
"user32.dll", SetLastError =
true)]
755 private static extern IntPtr GetMenu(IntPtr window);
757 [DllImport(
"user32.dll", SetLastError =
true)]
758 private static extern IntPtr GetSubMenu(IntPtr menu,
int position);
760 [DllImport(
"user32.dll", SetLastError =
true)]
761 private static extern int GetMenuItemCount(IntPtr menu);
763 [DllImport(
"user32.dll", SetLastError =
true)]
764 private static extern int GetMenuItemID(IntPtr menu,
int position);
766 [DllImport(
"user32.dll", CharSet = CharSet.Unicode, SetLastError =
true)]
767 private static extern int GetMenuString(IntPtr menu,
int item, System.Text.StringBuilder text,
int count, uint flags);
769 [DllImport(
"user32.dll", SetLastError =
true)]
770 private static extern bool PostMessage(IntPtr window,
int message, IntPtr wParam, IntPtr lParam);
772 private const uint MF_BYPOSITION = 0x0400;
773 private const int WM_COMMAND = 0x0111;
781 private static List<string> MenuNamesIn(
string selectorPath)
783 List<string> retVal =
new List<string>();
785 foreach (Match step
in Regex.Matches(selectorPath ??
string.Empty,
@"MenuItem[^/]*?\[@Name='([^']*)'\]"))
786 retVal.Add(step.Groups[1].Value);
801 private static string MenuItemName(IntPtr menu,
int position)
803 System.Text.StringBuilder text =
new System.Text.StringBuilder(256);
805 GetMenuString(menu, position, text, text.Capacity, MF_BYPOSITION);
807 string name = text.ToString().Replace(
"&",
string.Empty);
808 int shortcut = name.IndexOf(
'\t');
810 return (0 <= shortcut ? name.Substring(0, shortcut) : name).Trim();
826 private static int CommandForMenuPath(IntPtr menu, List<string> names,
int index)
830 if (IntPtr.Zero != menu && index < names.Count)
831 for (
int position = 0; position < GetMenuItemCount(menu) && 0 == retVal; position++)
833 if (
false ==
string.Equals(MenuItemName(menu, position), names[index], StringComparison.OrdinalIgnoreCase))
836 IntPtr inside = GetSubMenu(menu, position);
838 retVal = index == names.Count - 1 && IntPtr.Zero == inside
839 ? GetMenuItemID(menu, position)
840 : CommandForMenuPath(inside, names, index + 1);
852 private static GPALAutomationElement MenuItemFor(Application application,
string selectorPath)
854 GPALAutomationElement retVal =
null;
855 List<string> names = MenuNamesIn(selectorPath);
856 AutomationElement root = application.RootAutomationElement;
858 if (0 < names.Count &&
null != root)
860 IntPtr window =
new IntPtr(root.Current.NativeWindowHandle);
861 int command = CommandForMenuPath(GetMenu(window), names, 0);
865 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{string.Join(" then
", names)}] was not there to be walked, so it was read from the window's own menu, where it sends command [{command}].", application, GPALObjectType.Application);
867 retVal =
new GPALAutomationElement { MenuCommand = command, MenuWindow = window };
890 private static List<AutomationElement> StartingPoints(Application application,
string selectorPath)
892 List<AutomationElement> retVal =
new List<AutomationElement>();
894 if (
false == RootedAtDesktop(selectorPath))
895 retVal.Add(application.RootAutomationElement);
896 else if (
false == application.applicationSettings.HiddenDesktop)
897 retVal.Add(AutomationElement.RootElement);
902 retVal.Add(application.RootAutomationElement);
904 foreach (IntPtr window
in Desktops.WindowsOn(application.applicationSettings.HiddenDesktopName))
906 AutomationElement onDesktop = AutomationElement.FromHandle(window);
908 if (
null != onDesktop &&
false == onDesktop.Equals(application.RootAutomationElement))
909 retVal.Add(onDesktop);
913 retVal.RemoveAll(each =>
null == each);
918 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByXPath(
919 Application application,
920 SelectorPathEntry selectorPath,
921 bool searchForElement =
true,
922 AutomationElement element =
null)
924 if (
string.IsNullOrWhiteSpace(selectorPath?.SelectorPath))
925 return new ReadOnlyCollection<GPALAutomationElement>(Array.Empty<GPALAutomationElement>());
927 List<AutomationElement> roots =
null != element
928 ?
new List<AutomationElement> { element }
929 : StartingPoints(application, selectorPath.SelectorPath);
931 AutomationElement current = 0 < roots.Count ? roots[0] :
null;
934 return new ReadOnlyCollection<GPALAutomationElement>(Array.Empty<GPALAutomationElement>());
936 string[] tokens = selectorPath.SelectorPath.Split(
'/');
939 int lastToken = Array.FindLastIndex(tokens, t =>
false ==
string.IsNullOrWhiteSpace(t));
941 WalkFailure failure =
new WalkFailure();
942 List<AutomationElement> found =
new List<AutomationElement>();
944 foreach (AutomationElement root
in roots)
946 found = WalkFrom(application, root, tokens, 0, lastToken,
true, failure);
955 GPALAutomationElement throughTheMenu = 0 == found.Count ? MenuItemFor(application, selectorPath.SelectorPath) : null;
957 if (0 == found.Count &&
null == throughTheMenu)
958 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"Unable to locate element for Selector [{selectorPath.SelectorPath}], stopped at token [{failure.Token}]. {WhatIsThere(failure.Under ?? current)}", application, GPALObjectType.Application);
960 List<GPALAutomationElement> retVal =
new List<GPALAutomationElement>();
962 foreach (AutomationElement each
in found)
963 retVal.Add(
new GPALAutomationElement(each));
965 if (
null != throughTheMenu)
966 retVal.Add(throughTheMenu);
968 return new ReadOnlyCollection<GPALAutomationElement>(retVal);
976 private class WalkFailure
978 internal int Depth = -1;
979 internal string Token;
980 internal AutomationElement Under;
1007 private static List<AutomationElement> WalkFrom(Application application, AutomationElement current,
string[] tokens,
int index,
int lastToken,
bool firstToken, WalkFailure failure)
1009 List<AutomationElement> result =
new List<AutomationElement>();
1014 bool descendant =
false;
1015 Condition condition =
null;
1017 while (next < tokens.Length &&
null == (condition = ConditionFor(tokens[next])))
1022 if (
true ==
string.IsNullOrWhiteSpace(tokens[next]) && 0 < next)
1028 if (next >= tokens.Length)
1029 result.Add(current);
1032 List<AutomationElement> candidates =
new List<AutomationElement>();
1036 if (
true == firstToken)
1038 AutomationElement
self = current.FindFirst(TreeScope.Element, condition);
1041 candidates.Add(
self);
1044 if (0 == candidates.Count)
1045 foreach (AutomationElement child
in current.FindAll(
true == descendant ? TreeScope.Descendants : TreeScope.Children, condition))
1046 candidates.Add(child);
1054 if (0 == candidates.Count)
1055 candidates = RawMatches(current, condition, descendant);
1059 int position = IndexFor(tokens[next]);
1062 candidates = position <= candidates.Count
1063 ?
new List<AutomationElement> { candidates[position - 1] }
1064 :
new List<AutomationElement>();
1066 if (0 == candidates.Count && next > failure.Depth)
1068 failure.Depth = next;
1069 failure.Token = tokens[next];
1070 failure.Under = current;
1073 foreach (AutomationElement candidate
in candidates)
1078 if (candidate.Current.ControlType == ControlType.MenuItem && next < lastToken)
1079 ExpandMenuItem(application, candidate);
1081 List<AutomationElement> reached = WalkFrom(application, candidate, tokens, next + 1, lastToken,
false, failure);
1083 if (0 == reached.Count)
1086 result.AddRange(reached);
1090 if (next < lastToken)
1111 internal static List<AutomationElement> RawMatches(AutomationElement current, Condition condition,
bool descendant)
1113 List<AutomationElement> retVal =
new List<AutomationElement>();
1114 TreeWalker walker = TreeWalker.RawViewWalker;
1116 for (AutomationElement child = walker.GetFirstChild(current);
null != child; child = walker.GetNextSibling(child))
1118 if (
true == Matches(child, condition))
1121 if (
true == descendant)
1122 retVal.AddRange(RawMatches(child, condition,
true));
1139 private static bool Matches(AutomationElement element, Condition condition)
1141 bool retVal =
false;
1143 if (ReferenceEquals(Condition.TrueCondition, condition))
1145 else if (condition is PropertyCondition property)
1147 object wanted = property.Value;
1148 object actual = element.GetCurrentPropertyValue(property.Property);
1152 retVal = actual is ControlType controlType && wanted is
int wantedType
1153 ? controlType.Id == wantedType
1154 : Equals(actual, wanted);
1156 else if (condition is AndCondition all)
1160 foreach (Condition each
in all.GetConditions())
1161 if (
false == Matches(element, each))
1167 else if (condition is OrCondition any)
1169 foreach (Condition each
in any.GetConditions())
1170 if (
true == Matches(element, each))
1176 else if (condition is NotCondition other)
1177 retVal =
false == Matches(element, other.Condition);
1187 private static int IndexFor(
string token)
1190 int start = (token ??
string.Empty).IndexOf(
'[');
1194 while (0 < start && 0 == result)
1196 int end = token.IndexOf(
']', start);
1201 if (
true ==
int.TryParse(token.Substring(start + 1, end - start - 1), out
int position) && 0 < position)
1204 start = token.IndexOf(
'[', end);
1215 private static Condition ConditionFor(
string token)
1217 Condition result =
null;
1219 if (
false ==
string.IsNullOrWhiteSpace(token))
1221 string controlTypeStr = token;
1222 var conditions =
new List<Condition>();
1224 int attrStart = token.IndexOf(
'[');
1227 controlTypeStr = token.Substring(0, attrStart);
1230 string remaining = token.Substring(attrStart);
1231 while (remaining.Contains(
"[@"))
1233 int start = remaining.IndexOf(
"[@");
1234 int end = remaining.IndexOf(
']', start);
1235 if (end == -1)
break;
1237 string attrSegment = remaining.Substring(start + 2, end - start - 2);
1239 var parts = attrSegment.Split(
new char[] {
'=' }, 2);
1241 if (parts.Length == 2)
1243 string attrName = parts[0].Trim();
1244 string value = parts[1].Trim().Trim(
'\'',
'"');
1246 Condition cond = attrName.ToLowerInvariant()
switch
1248 "name" =>
new PropertyCondition(AutomationElement.NameProperty, value),
1249 "automationid" =>
new PropertyCondition(AutomationElement.AutomationIdProperty, value),
1250 "classname" =>
new PropertyCondition(AutomationElement.ClassNameProperty, value),
1255 conditions.Add(cond);
1258 remaining = remaining.Substring(end + 1);
1263 ControlType controlType = GetControlType(controlTypeStr);
1264 if (controlType !=
null)
1265 conditions.Add(
new PropertyCondition(AutomationElement.ControlTypeProperty, controlType));
1267 if (1 == conditions.Count)
1268 result = conditions[0];
1269 else if (1 < conditions.Count)
1270 result =
new AndCondition(conditions.ToArray());
1282 private static ControlType GetControlType(
string programmaticName)
1284 if (
string.IsNullOrWhiteSpace(programmaticName))
1288 _ = ControlType.Button.ToString();
1291 if (Enum.TryParse<System.Windows.Automation.Peers.AutomationControlType>(programmaticName,
true, out var enumValue))
1293 int id = (int)enumValue + 50000;
1294 return ControlType.LookupById(
id);
1298 return programmaticName.ToLowerInvariant()
switch
1300 "window" => ControlType.Window,
1301 "pane" => ControlType.Pane,
1302 "button" => ControlType.Button,
1303 "edit" => ControlType.Edit,
1304 "textbox" => ControlType.Edit,
1305 "combobox" => ControlType.ComboBox,
1306 "list" => ControlType.List,
1307 "listitem" => ControlType.ListItem,
1308 "menuitem" => ControlType.MenuItem,
1309 "tab" => ControlType.Tab,
1310 "tabitem" => ControlType.TabItem,
1311 "tree" => ControlType.Tree,
1312 "treeitem" => ControlType.TreeItem,
1313 "document" => ControlType.Document,
1314 "group" => ControlType.Group,
1315 "checkbox" => ControlType.CheckBox,
1316 "radiobutton" => ControlType.RadioButton,
1317 "hyperlink" => ControlType.Hyperlink,
1318 "image" => ControlType.Image,
1319 "spinner" => ControlType.Spinner,
1320 "slider" => ControlType.Slider,
1321 "statusbar" => ControlType.StatusBar,
1322 "toolbar" => ControlType.ToolBar,
1323 "datagrid" => ControlType.DataGrid,
1324 "dataitem" => ControlType.DataItem,
1336 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByAutomationID(Application application, SelectorPathEntry selectorPath,
bool searchForElement =
true, AutomationElement element =
null)
1338 return FindElementsBy(application, selectorPath, ConditionPropertyType.AutomationID, searchForElement, element);
1348 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByText(Application application, SelectorPathEntry selectorPath,
bool searchForElement =
true, AutomationElement element =
null)
1350 return FindElementsBy(application, selectorPath, ConditionPropertyType.Value, searchForElement, element);
1360 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByValue(Application application, SelectorPathEntry selectorPath,
bool searchForElement =
true, AutomationElement element =
null)
1362 return FindElementsBy(application, selectorPath, ConditionPropertyType.Value, searchForElement, element);
1372 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByName(Application application, SelectorPathEntry selectorPath,
bool searchForElement =
true, AutomationElement element =
null)
1374 return FindElementsBy(application, selectorPath, ConditionPropertyType.Name, searchForElement, element);
1384 private static ReadOnlyCollection<GPALAutomationElement> FindElementsByClassName(Application application, SelectorPathEntry selectorPath,
bool searchForElement =
true, AutomationElement element =
null)
1386 return FindElementsBy(application, selectorPath, ConditionPropertyType.ClassName, searchForElement, element);
1398 private static ReadOnlyCollection<GPALAutomationElement> FindElementsBy(Application application, SelectorPathEntry selectorPath, ConditionPropertyType conditionPropertyType,
bool searchForElement =
true, AutomationElement element =
null)
1400 ReadOnlyCollection<GPALAutomationElement> elem;
1402 if (
null != element)
1403 elem = element.FindElements(selectorPath.SelectorPath, conditionPropertyType);
1405 elem = application.RootAutomationElement.FindElements(selectorPath.SelectorPath, conditionPropertyType);
1418 internal static void PublishToEventHandler(Application application, Selector selector, List<GPALAutomationElement> elements)
1421 if (
true == selector.DeleteMe)
1422 msg = $
"Ignoring selector {selector.Name} marked for deletion. Continuing.";
1423 else if (
null == elements || 0 == elements.Count)
1424 msg = $
"No elements found for selector {selector.Name}. Continuing.";
1426 GPAL.PublishSimpleEvent(GPALEventType.INFO, msg, application, GPALObjectType.Application);
1436 public static List<GPALAutomationElement> WaitFor(Application application,
int timeoutInTicks, out
bool matchedAll)
1438 bool matchedAll2 =
true;
1439 List<GPALAutomationElement> foundElements =
null;
1440 List<GPALAutomationElement> tmpFoundElements =
null;
1441 List<GPALAutomationElement> matchedElements;
1443 foundElements =
new List<GPALAutomationElement>();
1445 foreach (Selector selector
in application.CurrentUOW.WithSelectorList)
1447 if (SelectorType.Selector != selector.SelectorType)
1450 tmpFoundElements = WaitFor(application, selector, timeoutInTicks, out matchedAll2, out matchedElements);
1452 tmpFoundElements = matchedElements;
1455 if (
true == selector.DeleteMe ||
null == tmpFoundElements)
1457 PublishToEventHandler(application, selector, tmpFoundElements);
1463 foreach (GPALAutomationElement element
in tmpFoundElements)
1465 foundElements.Add(element);
1466 if (++rowCount >= application.CurrentUOW.WithAllThatMatch)
1471 matchedAll = matchedAll2;
1472 return foundElements;
1485 private static List<GPALAutomationElement> WaitFor(Application application, Selector selector,
int timeoutInTicks, out
bool matchedAll, out List<GPALAutomationElement> matchedElements, AutomationElement element =
null)
1487 bool matchedAll2 =
true, matchedAll3 =
true;
1488 List<GPALAutomationElement> matchedElements2;
1489 ReadOnlyCollection<GPALAutomationElement> elements = FindElements(application, application.CurrentUOW, selector, out matchedAll2, out matchedElements2, element);
1491 if (WaitTime.Forever == timeoutInTicks)
1492 timeoutInTicks = Int32.MaxValue;
1496 for (; 0 < timeoutInTicks && 0 == (elements?.Count ?? 0); timeoutInTicks -= 1000)
1498 elements = FindElements(application, application.CurrentUOW, selector, out matchedAll3, out matchedElements2, element);
1499 if (0 == elements.Count)
1500 Thread.Sleep(Math.Min(1000, timeoutInTicks));
1505 matchedAll = (matchedAll2 | matchedAll3);
1506 matchedElements = matchedElements2;
1508 return elements?.ToList<GPALAutomationElement>();
1518 public static void FillInFrom(Application application,
string useText, WriteMode writeMode)
1520 List<GPALAutomationElement> matchedElements;
1522 foreach (Selector selector
in application.CurrentUOW.WithSelectorList)
1524 if (SelectorType.Selector != selector.SelectorType)
1527 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
1530 if (
true == selector.DeleteMe ||
null == elems)
1532 PublishToEventHandler(application, selector, elems?.ToList());
1536 if (
null != matchedElements)
1537 elems =
new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
1541 foreach (GPALAutomationElement elem
in elems)
1543 if (
null != elem.Ae)
1545 bool isPassword = elem.Ae.Current.IsPassword;
1550 string abbreviatedText = 2 < (useText?.Length ?? 0)
1551 ? useText[0] +
"..." + useText[useText.Length - 1]
1554 if (
false == isPassword)
1556 if (GPALEventType.DEBUG == (GPALEventType.DEBUG & GPAL.GPALSettings.DebugEvents) || GPALEventType.DEBUG == (GPALEventType.DEBUG & GPAL.GPALSettings.ConsoleEvents))
1557 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{writeMode}] text [{useText}] in element [{elem.Ae.Current.LocalizedControlType}][{selector.Name}]", application, GPALObjectType.Application);
1559 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{writeMode}] text [{abbreviatedText}] in element [{elem.Ae.Current.LocalizedControlType}][{selector.Name}]", application, GPALObjectType.Application);
1562 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{writeMode}] XXXpasswordXXX in element [{elem.Ae.Current.LocalizedControlType}][{selector.Name}]", application, GPALObjectType.Application);
1564 FillInFrom(application, elem.Ae,
new UnitOfWork.ElementNode() { OffsetX = selector.OffsetX, OffsetY = selector.OffsetY }, useText, writeMode);
1565 if (++rowCount >= HowManyToActOn(application))
1570 UnitOfWork safeUOW = application.CurrentUOW;
1571 CallIfStatus handled = 0;
1574 IGPALGrid<string> tokens = GPAL.Grid.ToGPALObject();
1575 tokens.AddRow(
new List<string> { useText });
1576 if (
null != application.CurrentUOW.AppCallAfterFillIn)
1577 handled = application.CurrentUOW.AppCallAfterFillIn(application, tokens, 1);
1581 application.CurrentUOW = safeUOW;
1583 if (CallIfStatus.Terminate == handled)
1585 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"CallAfterFillIn handler [{application.CurrentUOW.AppCallAfterFillIn.GetInvocationList()[0].Method.Name}] requested program termination.", application, GPALObjectType.Application);
1586 throw new GPALException($
"CallAfterFillIn handler [{application.CurrentUOW.AppCallAfterFillIn.GetInvocationList()[0].Method.Name}] requested program termination.");
1597 internal static void Scroll(Application application,
object amount, ScrollTypes incrementsPercent, ScrollTypes horzVert)
1599 List<GPALAutomationElement> matchedElements;
1601 foreach (Selector selector
in application.CurrentUOW.WithSelectorList)
1603 if (SelectorType.Selector != selector.SelectorType)
1606 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
1609 if (
true == selector.DeleteMe ||
null == elems)
1611 PublishToEventHandler(application, selector, elems?.ToList());
1615 if (
null != matchedElements)
1616 elems =
new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
1618 if (0 == elems.Count)
1619 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selector [{selector.Name}] matched no elements, so there was nothing to scroll.", application, GPALObjectType.Application);
1623 foreach (GPALAutomationElement elem
in elems)
1625 if (
null != elem.Ae)
1627 Scroll(application, elem.Ae, selector.InteractionType, amount, incrementsPercent, horzVert);
1628 if (++rowCount >= HowManyToActOn(application))
1657 private static void SetRangeValue(Application application, RangeValuePattern rangeValue, AutomationElement ae,
object amount, ScrollTypes incrementsPercent)
1659 var now = rangeValue.Current;
1661 if (
true == now.IsReadOnly)
1662 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{ae.Current.ControlType.LocalizedControlType}] reports its range as read only, so it cannot be scrolled.", application, GPALObjectType.Application);
1665 double span = now.Maximum - now.Minimum;
1668 if (ScrollTypes.Percent == incrementsPercent)
1669 wanted = now.Minimum + (((double)(int)amount / 100.0) * span);
1675 ScrollAmount step = (ScrollAmount)amount;
1677 bool large = ScrollAmount.LargeIncrement == step || ScrollAmount.LargeDecrement == step;
1678 bool back = ScrollAmount.SmallDecrement == step || ScrollAmount.LargeDecrement == step;
1680 double by = large ? now.LargeChange : now.SmallChange;
1682 if (
double.IsNaN(by) || 0 == by)
1683 by = span * (large ? 0.1 : 0.01);
1685 wanted = now.Value + (back ? -by : by);
1690 wanted = wanted < now.Minimum ? now.Minimum : (wanted > now.Maximum ? now.Maximum : wanted);
1694 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"RangeValuePattern on [{ae.Current.ControlType.LocalizedControlType}]: range [{now.Minimum}] to [{now.Maximum}], value [{now.Value}], setting [{wanted}].", application, GPALObjectType.Application);
1696 rangeValue.SetValue(wanted);
1698 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"RangeValuePattern on [{ae.Current.ControlType.LocalizedControlType}]: value is now [{rangeValue.Current.Value}].", application, GPALObjectType.Application);
1700 catch (Exception ex)
1702 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to set a value of [{wanted}] on control [{ae.Current.ControlType.LocalizedControlType}].", application, GPALObjectType.Application, ex);
1721 private static void ScrollToPercent(Application application, AutomationElement ae, ScrollPattern scrollPattern,
int percent, ScrollTypes horzVert)
1723 bool vertical = ScrollTypes.Vertical == horzVert;
1724 var info = scrollPattern.Current;
1726 if (
false == (vertical ? info.VerticallyScrollable : info.HorizontallyScrollable))
1727 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{ae.Current.ControlType.LocalizedControlType}] is not scrollable {(vertical ? "vertically
" : "horizontally
")}.", application, GPALObjectType.Application);
1730 double page = vertical ? info.VerticalViewSize : info.HorizontalViewSize;
1731 double at = vertical ? info.VerticalScrollPercent : info.HorizontalScrollPercent;
1735 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"ScrollPattern on [{ae.Current.ControlType.LocalizedControlType}]: at [{at}], one view is [{page}] percent, walking to [{percent}].", application, GPALObjectType.Application);
1738 while (page <= Math.Abs(percent - at) && at != was && steps < STEP_LIMIT)
1741 at = OneStep(scrollPattern, vertical, percent > at,
true);
1745 double off = Math.Abs(percent - at);
1749 while (0 < off && at != was && steps < STEP_LIMIT)
1752 at = OneStep(scrollPattern, vertical, percent > at,
false);
1755 double closer = Math.Abs(percent - at);
1763 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"ScrollPattern on [{ae.Current.ControlType.LocalizedControlType}]: landed at [{at}] after [{steps}] step(s).", application, GPALObjectType.Application);
1773 private static double OneStep(ScrollPattern scrollPattern,
bool vertical,
bool forward,
bool large)
1775 ScrollAmount step = large
1776 ? (forward ? ScrollAmount.LargeIncrement : ScrollAmount.LargeDecrement)
1777 : (forward ? ScrollAmount.SmallIncrement : ScrollAmount.SmallDecrement);
1779 scrollPattern.Scroll(vertical ? ScrollAmount.NoAmount : step, vertical ? step : ScrollAmount.NoAmount);
1781 return vertical ? scrollPattern.Current.VerticalScrollPercent : scrollPattern.Current.HorizontalScrollPercent;
1785 private const int STEP_LIMIT = 500;
1802 internal static bool HardwareReaches(Application application)
1806 if (
true == application.applicationSettings.HiddenDesktop)
1810 if (
false == application.applicationSettings.HiddenDesktopHardwareReported)
1812 application.applicationSettings.HiddenDesktopHardwareReported =
true;
1814 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Hardware input cannot reach [{application.applicationSettings.HiddenDesktopName}] while it is hidden, so UIAutomation is used instead. Peek at the desktop to use hardware there.", application, GPALObjectType.Application);
1823 internal static void Scroll(Application application, AutomationElement ae, InteractionType interactionType,
object amount, ScrollTypes incrementsPercent, ScrollTypes horzVert)
1826 if ((Enums.InteractionType.Hardware == interactionType ||
true == application.applicationSettings.UseHardware) &&
true == HardwareReaches(application))
1827 GPAL.PublishSimpleEvent(GPALEventType.INFO,
"Hardware scrolling is not supported at this time. Find the scroll controls and click them instead.", application, GPALObjectType.Application);
1831 if (
true == ae.TryGetCurrentPattern(RangeValuePattern.Pattern, out
object rangeValuePattern))
1833 SetRangeValue(application, (RangeValuePattern)rangeValuePattern, ae, amount, incrementsPercent);
1835 else if (
true == ae.TryGetCurrentPattern(ScrollPattern.Pattern, out
object scrollPattern))
1837 if (ScrollTypes.Percent == incrementsPercent)
1838 ScrollToPercent(application, ae, (ScrollPattern)scrollPattern, (
int)amount, horzVert);
1841 ScrollAmount hAmount = ScrollTypes.Horizontal == horzVert ? (ScrollAmount)amount : ScrollAmount.NoAmount;
1842 ScrollAmount vAmount = ScrollTypes.Vertical == horzVert ? (ScrollAmount)amount : ScrollAmount.NoAmount;
1846 var before = ((ScrollPattern)scrollPattern).Current;
1848 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"ScrollPattern on [{ae.Current.ControlType.LocalizedControlType}]: scrollable h [{before.HorizontallyScrollable}] v [{before.VerticallyScrollable}], at h [{before.HorizontalScrollPercent}] v [{before.VerticalScrollPercent}], applying h [{hAmount}] v [{vAmount}].", application, GPALObjectType.Application);
1850 ((ScrollPattern)scrollPattern).Scroll(hAmount, vAmount);
1852 var after = ((ScrollPattern)scrollPattern).Current;
1854 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"ScrollPattern on [{ae.Current.ControlType.LocalizedControlType}]: now at h [{after.HorizontalScrollPercent}] v [{after.VerticalScrollPercent}].", application, GPALObjectType.Application);
1856 catch (Exception ex)
1858 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Unable to Scroll on control [{ae.Current.ControlType.LocalizedControlType}].", application, GPALObjectType.Application, ex); ;
1863 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{ae.Current.ControlType.LocalizedControlType}] offers neither RangeValuePattern nor ScrollPattern, so UIAutomation cannot scroll it. Point the selector at its ScrollBar, which does.", application, GPALObjectType.Application);
1876 internal static void FillInFrom(Application application, AutomationElement ae, UnitOfWork.ElementNode elementNode,
string useText, WriteMode writeMode)
1880 if ((Enums.InteractionType.Hardware == elementNode.InteractionType ||
true == application.applicationSettings.UseHardware) &&
true == HardwareReaches(application))
1882 HardwareFillInFrom(application,
new GPALAutomationElement(ae), elementNode.OffsetX, elementNode.OffsetY, useText, writeMode);
1934 if (
true == ae.TryGetCurrentPattern(ValuePattern.Pattern, out
object valuePattern))
1943 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unable to set focus on control [{ae.Current.ControlType.LocalizedControlType}], so the value is written to it without the focus.", application, GPALObjectType.Application);
1948 string content = WriteMode.Overwrite == writeMode ? string.Empty : ((ValuePattern)valuePattern).Current.Value;
1950 ((ValuePattern)valuePattern).SetValue(WriteMode.Append == writeMode ? content + useText
1951 : WriteMode.Insert == writeMode ? useText + content
1956 catch (Exception ex)
1958 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"ValuePattern on [{ae.Current.ControlType.ProgrammaticName}] would not take the text, so the next way of writing it is tried.", application, GPALObjectType.Application, ex);
1965 taken = SetNativeText(application, ae, useText, writeMode);
1971 if (
false == GPAL.NoFallbackRecoveryActions &&
true == HardwareReaches(application))
1973 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{ae.Current.ControlType.ProgrammaticName}] offers no way to write text into it, so it is typed into instead.", application, GPALObjectType.Application);
1975 HardwareFillInFrom(application,
new GPALAutomationElement(ae), elementNode.OffsetX, elementNode.OffsetY, useText, writeMode);
1978 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"[{ae.Current.ControlType.ProgrammaticName}] offers no way to write text into it, so it was left as it was.", application, GPALObjectType.Application);
1992 public static void HardwareFillInFrom(Application application, GPALAutomationElement element,
int offsetX,
int offsetY,
string text, WriteMode writeMode)
1994 ApplicationHelper.TopProcess(application.Process);
1996 HardwareFocus(application, element, offsetX, offsetY);
1998 if (WriteMode.Overwrite == writeMode)
2001 HardwareHelper.SendChar(
'a', ModifierKeys.Control,
true);
2005 else if (WriteMode.Append == writeMode)
2007 HardwareHelper.SendKey(GPAL.VK_END, ModifierKeys.Control);
2010 else if (WriteMode.Insert == writeMode)
2012 HardwareHelper.SendKey(GPAL.VK_HOME, ModifierKeys.Control);
2016 HardwareHelper.SendString(text, GPAL.TypingDelay);
2018 SettleInput(application);
2035 internal static void SettleInput(Application application)
2039 application?.Process?.WaitForInputIdle(INPUT_SETTLE_MS);
2050 private const int INPUT_SETTLE_MS = 2_000;
2057 public static void Focus(Application application)
2059 List<GPALAutomationElement> matchedElements;
2061 foreach (Selector selector
in application.CurrentUOW.WithSelectorList)
2063 if (SelectorType.Selector != selector.SelectorType)
2066 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2069 if (
true == selector.DeleteMe ||
null == elems)
2071 PublishToEventHandler(application, selector, elems?.ToList());
2075 if (
null != matchedElements)
2076 elems =
new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
2080 foreach (GPALAutomationElement elem
in elems)
2082 if (
null != elem.Ae)
2084 if ((Enums.InteractionType.Hardware == selector.InteractionType ||
true == application.applicationSettings.UseHardware) &&
true == HardwareReaches(application))
2086 int offsetX, offsetY;
2087 offsetX = selector.SelectorSettings.OffsetX;
2088 offsetY = selector.SelectorSettings.OffsetY;
2090 HardwareFocus(application, elem, offsetX, offsetY);
2097 if (
null != elem.Ae)
2100 HardwareFocus(application, elem, 0, 0);
2102 catch (Exception ex)
2104 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"Focus threw on selector [{selector.Name}], so the focus did not move.", application, GPALObjectType.Application, ex);
2108 if (++rowCount >= HowManyToActOn(application))
2121 public static void Hover(Application application, UnitOfWork currentUOW)
2123 List<GPALAutomationElement> matchedElements;
2125 foreach (Selector selector
in currentUOW.WithSelectorList)
2127 if (SelectorType.Selector != selector.SelectorType)
2130 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, currentUOW, selector, out
bool matchedAll, out matchedElements);
2133 if (
true == selector.DeleteMe ||
null == elems)
2135 PublishToEventHandler(application, selector, elems?.ToList());
2139 if (
null != matchedElements)
2140 elems =
new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
2144 foreach (GPALAutomationElement elem
in elems)
2146 if (
null != elem.Ae)
2148 if (0 == elem.Ae.Current.BoundingRectangle.Width && 0 == elem.Ae.Current.BoundingRectangle.Height)
2150 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Element has Zero Width and Height, skipping", application, GPALObjectType.Application);
2157 if (
false == HardwareReaches(application))
2159 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selector [{selector.Name}] cannot be moved to on a hidden desktop, since a hover is a mouse position and UIAutomation has no equivalent. Peek at the desktop to hover, or click, which does work there.", application, GPALObjectType.Application);
2161 else if (Enums.InteractionType.Hardware == selector.InteractionType ||
true == application.applicationSettings.UseHardware)
2164 HardwareMoveTo(application, elem.ImageMatch ?? elem, selector.OffsetX, selector.OffsetY);
2166 else if (0 != elem.Ae.Current.BoundingRectangle.Width && 0 != elem.Ae.Current.BoundingRectangle.Height)
2171 HardwareMoveTo(application, elem.ImageMatch ?? elem, selector.OffsetX, selector.OffsetY);
2175 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Focusing [{selector.Name}] threw, so it is moved to without the focus.", application, GPALObjectType.Application);
2176 HardwareMoveTo(application, elem.ImageMatch ?? elem, selector.OffsetX, selector.OffsetY);
2180 if (++rowCount >= HowManyToActOn(application))
2191 public static void MoveTo(Application application)
2193 List<GPALAutomationElement> matchedElements;
2195 foreach (Selector selector
in application.CurrentUOW.WithSelectorList)
2197 if (SelectorType.Selector != selector.SelectorType)
2200 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2203 if (
true == selector.DeleteMe ||
null == elems)
2205 PublishToEventHandler(application, selector, elems?.ToList());
2209 if (
null != matchedElements)
2210 elems =
new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
2216 if (
false == HardwareReaches(application))
2217 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selector [{selector.Name}] cannot be moved to on a hidden desktop, since moving the pointer is the mouse and UIAutomation has no equivalent. Peek at the desktop to move there, or click, which does work.", application, GPALObjectType.Application);
2219 foreach (GPALAutomationElement elem
in elems)
2221 if (
null != elem.Gae ||
null != elem.Ae)
2222 HardwareMoveTo(application, elem.ImageMatch ?? elem, selector.OffsetX, selector.OffsetY);
2224 if (++rowCount >= HowManyToActOn(application))
2244 internal static string WhatIsThere(AutomationElement parent)
2246 var names =
new List<string>();
2251 AutomationElement at = parent ?? AutomationElement.RootElement;
2253 foreach (AutomationElement child
in at.FindAll(TreeScope.Children, System.Windows.Automation.Condition.TrueCondition))
2255 string controlType = child.Current.ControlType.ProgrammaticName.Replace(
"ControlType.",
"");
2256 string automationId = child.Current.AutomationId;
2258 string className = child.Current.ClassName;
2260 names.Add(
string.IsNullOrEmpty(automationId)
2261 ? $
"{controlType}[@ClassName='{className}'] '{child.Current.Name}'"
2262 : $
"{controlType}[@AutomationId='{automationId}'][@ClassName='{className}'] '{child.Current.Name}'");
2265 retVal = 0 == names.Count
2266 ?
"It has no children."
2267 : $
"What is there: {string.Join(" |
", names)}";
2269 catch (Exception ex)
2271 retVal = $
"Its children could not be read: {ex.Message}";
2286 private static void MoveTo(AutomationElement element,
int offsetX,
int offsetY,
string appName)
2290 HardwareMoveTo(
null,
new GPALAutomationElement(element), offsetX, offsetY);
2292 catch (Exception ex)
2294 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $
"(ElementHelper): unable to move the mouse to the element in Application [{appName}].", element, GPALObjectType.Other, ex);
2304 private static void HardwareFocus(Application application, GPALAutomationElement element,
int OffsetX,
int OffsetY)
2306 if (
null != element.Ae && ControlType.Text == element.Ae.Current.ControlType)
2307 HardwareClick(application, element, ClickType.LeftClick, ModifierKeys.NONE, OffsetX, OffsetY);
2309 HardwareMoveTo(application, element, OffsetX, OffsetY);
2318 private static System.Drawing.Point HardwareMoveTo(Application application, GPALAutomationElement element,
int OffsetX,
int OffsetY)
2320 Rectangle rect2 =
new Rectangle(), iframeRect =
new Rectangle(0, 0, 0, 0);
2324 if (
null != element.Gae)
2326 rect2.Location = element.Gae.Location;
2327 rect2.Size = element.Gae.Size;
2331 rect2.Location =
new System.Drawing.Point((
int) element.Ae.Current.BoundingRectangle.Location.X, (
int) element.Ae.Current.BoundingRectangle.Location.Y);
2332 rect2.Size =
new System.Drawing.Size((
int) element.Ae.Current.BoundingRectangle.Width, (
int) element.Ae.Current.BoundingRectangle.Height);
2356 int offsetX = OffsetX;
2357 int offsetY = OffsetY;
2359 if (0 == offsetX && 0 == offsetY)
2361 int spreadX = Math.Max(0, Math.Min(2, (rect2.Width / 2) - 1));
2362 int spreadY = Math.Max(0, Math.Min(2, (rect2.Height / 2) - 1));
2364 offsetX = rect2.Width / 2 + Jitter.Next(-spreadX, spreadX + 1);
2365 offsetY = rect2.Height / 2 + Jitter.Next(-spreadY, spreadY + 1);
2368 System.Drawing.Point aimedAt =
new System.Drawing.Point(rect2.X + offsetX, rect2.Y + offsetY);
2374 if ((0 == rect2.Width || 0 == rect2.Height)
2375 &&
null != element.Ae
2376 &&
true == element.Ae.TryGetClickablePoint(out System.Windows.Point clickablePoint))
2377 aimedAt =
new System.Drawing.Point((
int)clickablePoint.X, (
int)clickablePoint.Y);
2379 if (
true == GPAL.SimulateMouse ||
true == application.CurrentUOW?.CurrentSelector?.SimulateMouse)
2380 HardwareHelper.MoveMouse(aimedAt.X, aimedAt.Y, 10, 10);
2382 HardwareHelper.MoveMouse(aimedAt.X, aimedAt.Y);
2389 private static Random _jitter;
2390 private static Random Jitter => _jitter ?? (_jitter =
new Random(Guid.NewGuid().GetHashCode()));
2396 public static void LeftClick(Application application, ModifierKeys modifierKeys = ModifierKeys.NONE)
2398 List<GPALAutomationElement> matchedElements;
2400 foreach (Selector selector
in application.CurrentUOW.WithSelectorList)
2402 if (SelectorType.Selector != selector.SelectorType)
2405 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2408 if (
true == selector.DeleteMe ||
null == elems)
2410 PublishToEventHandler(application, selector, elems?.ToList());
2414 if (
null != matchedElements)
2415 elems =
new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
2419 foreach (GPALAutomationElement elem
in elems)
2421 Click(application, selector, elem, ClickType.LeftClick, modifierKeys);
2423 if (++rowCount >= HowManyToActOn(application))
2434 public static bool GenericClick(Application application, ClickType clickType)
2436 List<GPALAutomationElement> matchedElements;
2437 bool matchedAll =
false;
2438 bool clickPerformed =
false;
2440 foreach (Selector sel
in application.CurrentUOW.WithSelectorList)
2442 if (SelectorType.Selector != sel.SelectorType)
2445 ElementHelper.FindElements(application, application.CurrentUOW, sel, out matchedAll, out matchedElements);
2447 if (0 == (matchedElements?.Count ?? 0))
2448 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selector [{sel.Name}] matched no elements, so there was nothing to [{clickType}].", application, GPALObjectType.Application);
2450 if (
null != matchedElements)
2454 foreach (GPALAutomationElement elem
in matchedElements)
2456 Click(application, sel, elem, clickType, ModifierKeys.NONE);
2457 clickPerformed =
true;
2459 if (++rowCount >= HowManyToActOn(application))
2464 return clickPerformed;
2475 public static void Click(Application application, Selector selector, GPALAutomationElement element, ClickType clickType, ModifierKeys modifierKeys)
2477 int offsetX, offsetY;
2481 if (
null != element.ImageMatch)
2482 element = element.ImageMatch;
2483 offsetX = selector.SelectorSettings.OffsetX;
2484 offsetY = selector.SelectorSettings.OffsetY;
2488 if (0 != element.MenuCommand)
2490 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Selector [{selector.Name}] is a menu item, so command [{element.MenuCommand}] was sent to the window it belongs to.", application, GPALObjectType.Application);
2492 PostMessage(element.MenuWindow, WM_COMMAND,
new IntPtr(element.MenuCommand), IntPtr.Zero);
2496 else if (ClickType.RightClick == clickType &&
false == HardwareReaches(application))
2498 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selector [{selector.Name}] cannot be right clicked on a hidden desktop, since UIAutomation offers no pattern for one. Peek at the desktop to right click there.", application, GPALObjectType.Application);
2500 else if ((Enums.InteractionType.Hardware == selector.InteractionType ||
null != element.Gae ||
true == application.applicationSettings.UseHardware || ClickType.RightClick == clickType)
2501 &&
true == HardwareReaches(application))
2502 HardwareClick(application, element, clickType, modifierKeys, offsetX, offsetY);
2503 else if (
null != element.Ae)
2505 UIAutomationClick(application, selector, element, clickType, modifierKeys);
2510 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Selector [{selector.Name}] is an image match, which only the mouse can click, and the mouse cannot reach [{application.applicationSettings.HiddenDesktopName}]. Peek at the desktop to click there.", application, GPALObjectType.Application);
2535 public static void UIAutomationClick(Application application, Selector selector, GPALAutomationElement element, ClickType clickType, ModifierKeys modifierKeys)
2538 bool invoked =
false;
2539 string control = element.Ae.Current.LocalizedControlType;
2541 if (ModifierKeys.NONE != modifierKeys)
2542 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"A click holding [{Browser.Browser.GetModifierKeys(modifierKeys)}] has no UIAutomation equivalent, so [{control}] is clicked with the mouse.", application, GPALObjectType.Application);
2543 else if (ClickType.LeftClick == clickType &&
true == element.Ae.TryGetCurrentPattern(SelectionItemPattern.Pattern, out objPattern))
2549 ((SelectionItemPattern)objPattern).Select();
2552 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Element [{NameOf(element)}] [{clickType}] clicked through SelectionItemPattern.", application, GPALObjectType.Application);
2556 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"SelectionItemPattern threw on control [{control}], falling back to the mouse.", application, GPALObjectType.Application);
2559 else if ((ClickType.LeftClick == clickType || ClickType.LeftDoubleClick == clickType)
2560 &&
true == element.Ae.TryGetCurrentPattern(InvokePattern.Pattern, out objPattern))
2566 ((InvokePattern)objPattern).Invoke();
2569 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Element [{NameOf(element)}] [{clickType}] clicked through InvokePattern.", application, GPALObjectType.Application);
2573 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"InvokePattern threw on control [{control}], falling back to the mouse.", application, GPALObjectType.Application);
2577 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"Control [{control}] offers no pattern for a [{clickType}], so the mouse is how it is clicked.", application, GPALObjectType.Application);
2581 if (
false == invoked)
2582 HardwareClick(application, element, clickType, modifierKeys, selector?.OffsetX ?? 5, selector?.OffsetY ?? 5);
2597 private static string NameOf(GPALAutomationElement element)
2599 string retVal =
null == element ||
null == element.Ae ?
"nothing" : element.Ae.Current.Name;
2601 if (
true ==
string.IsNullOrWhiteSpace(retVal) &&
null != element &&
null != element.Ae)
2602 retVal = element.Ae.Current.ControlType.ProgrammaticName;
2617 public static void HardwareClick(Application application, GPALAutomationElement element, ClickType clickType, ModifierKeys modifierKeys,
int OffsetX = 0,
int OffsetY = 0)
2621 if (ModifierKeys.NONE != modifierKeys)
2622 HardwareHelper.PressModifierKey(modifierKeys);
2627 System.Drawing.Point clickAt = HardwareMoveTo(application, element, OffsetX, OffsetY);
2632 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Element [{NameOf(element)}] [{clickType}] clicked with modifiers [{Browser.Browser.GetModifierKeys(modifierKeys)}] at [{clickAt.X},{clickAt.Y}] with the mouse.", application, GPALObjectType.Application);
2634 HardwareHelper.HardwareClick(clickAt.X, clickAt.Y, clickType);
2642 if (ModifierKeys.NONE != modifierKeys)
2643 HardwareHelper.ReleaseModifierKeys(modifierKeys);
2652 public static void DragAndDrop(Application application, ModifierKeys modifierKeys = ModifierKeys.NONE)
2654 var selectors =
new List<Selector>();
2656 foreach (Selector selector
in application.CurrentUOW.WithSelectorList)
2657 if (SelectorType.Selector == selector.SelectorType)
2658 selectors.Add(selector);
2663 if (2 == selectors.Count)
2664 DragToTarget(application, selectors[0], selectors[1], modifierKeys);
2666 DragByDelta(application, modifierKeys);
2684 private static void DragToTarget(Application application, Selector source, Selector target, ModifierKeys modifierKeys)
2686 Rectangle from = FirstRectangle(application, source);
2687 Rectangle to = FirstRectangle(application, target);
2689 if (
true == from.IsEmpty ||
true == to.IsEmpty)
2690 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $
"A drag needs both ends. Selector [{source.Name}] {(true == from.IsEmpty ? "matched nothing
" : "matched
")} and selector [{target.Name}] {(true == to.IsEmpty ? "matched nothing
" : "matched
")}.", application, GPALObjectType.Application);
2696 int grabX = from.X + (0 == source.OffsetX ? from.Width / 2 : source.OffsetX);
2697 int grabY = from.Y + (0 == source.OffsetY ? from.Height / 2 : source.OffsetY);
2699 int dropX = to.X + (0 == target.OffsetX ? to.Width / 2 : target.OffsetX) + target.DeltaX;
2700 int dropY = to.Y + (0 == target.OffsetY ? to.Height / 2 : target.OffsetY) + target.DeltaY;
2706 HardwareHelper.ReleaseStrayModifiers(modifierKeys);
2708 if (ModifierKeys.NONE != modifierKeys)
2709 HardwareHelper.PressModifierKey(modifierKeys);
2711 HardwareHelper.MouseDown(grabX, grabY, ClickType.LeftClick);
2712 HardwareHelper.DragTravel(dropX, dropY,
true == GPAL.SimulateMouse ||
true == source.SimulateMouse);
2713 HardwareHelper.MouseUp(dropX, dropY, ClickType.LeftClick);
2717 if (ModifierKeys.NONE != modifierKeys)
2718 HardwareHelper.ReleaseModifierKeys(modifierKeys);
2721 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Dragged [{source.Name}] from [{grabX},{grabY}] onto [{target.Name}] at [{dropX},{dropY}]", application, GPALObjectType.Application);
2731 private static Rectangle FirstRectangle(Application application, Selector selector)
2733 Rectangle retVal = Rectangle.Empty;
2735 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out
bool matchedAll, out List<GPALAutomationElement> matchedElements);
2737 if (
null != matchedElements && 0 < matchedElements.Count)
2738 elems =
new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
2740 if (
false == selector.DeleteMe &&
null != elems && 0 < elems.Count)
2743 GPALAutomationElement elem = elems[0].ImageMatch ?? elems[0];
2745 if (
null != elem.Gae)
2746 retVal =
new Rectangle(elem.Gae.Location, elem.Gae.Size);
2748 retVal =
new Rectangle(
2749 new System.Drawing.Point((
int)elem.Ae.Current.BoundingRectangle.X, (
int)elem.Ae.Current.BoundingRectangle.Y),
2750 new System.Drawing.Size((
int)elem.Ae.Current.BoundingRectangle.Width, (
int)elem.Ae.Current.BoundingRectangle.Height));
2753 PublishToEventHandler(application, selector, elems?.ToList());
2764 private static void DragByDelta(Application application, ModifierKeys modifierKeys)
2766 List<GPALAutomationElement> matchedElements;
2768 foreach (Selector selector
in application.CurrentUOW.WithSelectorList)
2770 if (SelectorType.Selector != selector.SelectorType)
2773 ReadOnlyCollection<GPALAutomationElement> elems = ElementHelper.FindElements(application, application.CurrentUOW, selector, out
bool matchedAll, out matchedElements);
2775 if (
true == selector.DeleteMe ||
null == elems)
2777 PublishToEventHandler(application, selector, elems?.ToList());
2781 if (
null != matchedElements)
2782 elems =
new ReadOnlyCollection<GPALAutomationElement>(matchedElements);
2786 foreach (GPALAutomationElement found
in elems)
2791 GPALAutomationElement elem = found.ImageMatch ?? found;
2793 if (
null != elem.Gae)
2794 rect =
new Rectangle(elem.Gae.Location, elem.Gae.Size);
2796 rect =
new Rectangle(
2797 new System.Drawing.Point((
int)elem.Ae.Current.BoundingRectangle.X, (
int)elem.Ae.Current.BoundingRectangle.Y),
2798 new System.Drawing.Size((
int)elem.Ae.Current.BoundingRectangle.Width, (
int)elem.Ae.Current.BoundingRectangle.Height));
2807 int grabX = rect.X + (0 == selector.OffsetX ? rect.Width / 2 : selector.OffsetX);
2808 int grabY = rect.Y + (0 == selector.OffsetY ? rect.Height / 2 : selector.OffsetY);
2810 int endX = grabX + selector.DeltaX;
2811 int endY = grabY + selector.DeltaY;
2816 HardwareHelper.ReleaseStrayModifiers(modifierKeys);
2818 if (ModifierKeys.NONE != modifierKeys)
2819 HardwareHelper.PressModifierKey(modifierKeys);
2821 HardwareHelper.MouseDown(grabX, grabY, ClickType.LeftClick);
2822 HardwareHelper.DragTravel(endX, endY,
true == GPAL.SimulateMouse ||
true == selector.SimulateMouse);
2823 HardwareHelper.MouseUp(endX, endY, ClickType.LeftClick);
2827 if (ModifierKeys.NONE != modifierKeys)
2828 HardwareHelper.ReleaseModifierKeys(modifierKeys);
2831 if (++rowCount >= HowManyToActOn(application))
2844 public static void ExpandMenuItem(Application application, AutomationElement automationElement)
2846 if (
true == GPAL.SimulateMouse ||
true == application.CurrentUOW.CurrentSelector?.SimulateMouse)
2848 HardwareHelper.MoveMouse((
int)automationElement.Current.BoundingRectangle.X, (
int)automationElement.Current.BoundingRectangle.Y, 10, 10);
2849 HardwareHelper.LeftClick((
int)automationElement.Current.BoundingRectangle.X, (
int)automationElement.Current.BoundingRectangle.Y);
2856 ExpandCollapsePattern pattern = automationElement.GetCurrentPattern(ExpandCollapsePattern.Pattern) as ExpandCollapsePattern;
2861 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unable to expand menuitem for Control [{automationElement.Current.LocalizedControlType}], trying InvokePattern.", application, GPALObjectType.Application);
2864 InvokePattern pattern = automationElement.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
2867 catch (Exception ex2)
2877 if (
false == HardwareReaches(application))
2878 GPAL.PublishSimpleEvent(GPALEventType.DEBUG,
"A menu would not open through UIAutomation and the mouse cannot open it either, so what is on it is read from the window's own menu.", application, GPALObjectType.Application, ex2);
2881 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"Unable to expand menuitem for Control [{automationElement.Current.LocalizedControlType}], using hardware fallback.", application, GPALObjectType.Application);
2883 Random random =
new Random(10007);
2885 int offsetX = random.Next(1, (
int)automationElement.Current.BoundingRectangle.Width - 2);
2886 int offsetY = random.Next(1, (
int)automationElement.Current.BoundingRectangle.Height - 2);
2887 ElementHelper.HardwareClick(
null,
new GPALAutomationElement(automationElement), Enums.ClickType.LeftClick, Enums.ModifierKeys.NONE, offsetX, offsetY);
2897 public static void SwitchToTab(Application application)
2899 List<GPALAutomationElement> matchedElements;
2900 bool matchedAll =
false;
2902 foreach (Selector sel
in application.CurrentUOW.WithSelectorList)
2904 if (SelectorType.Selector != sel.SelectorType)
2907 ElementHelper.FindElements(application, application.CurrentUOW, sel, out matchedAll, out matchedElements);
2909 if (
null != matchedElements)
2917 foreach (GPALAutomationElement elem
in matchedElements)
2919 if (
null == elem.Ae)
2922 if (
true == elem.Ae.TryGetCurrentPattern(SelectionItemPattern.Pattern, out
object selectionPattern))
2929 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Switching to [{elem.Ae.Current.Name}] through SelectionItemPattern, which reads Selected [{elem.Selected}] before the switch.", application, GPALObjectType.Application);
2931 ((SelectionItemPattern)selectionPattern).Select();
2933 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{elem.Ae.Current.Name}] reads Selected [{elem.Selected}] after the switch.", application, GPALObjectType.Application);
2935 catch (Exception ex)
2937 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);
2939 if (
true == MsaaHelper.Select(application, elem.Ae))
2940 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Switched to [{elem.Ae.Current.Name}] through MSAA.", application, GPALObjectType.Application);
2941 else if (
true == HardwareReaches(application))
2942 HardwareClick(application, elem, ClickType.LeftClick, ModifierKeys.NONE, sel.OffsetX, sel.OffsetY);
2944 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"The tab [{elem.Ae.Current.Name}] was not switched to. Neither SelectionPattern nor MSAA would select it and the mouse cannot reach it here.", application, GPALObjectType.Application);
2947 else if (
true == MsaaHelper.Select(application, elem.Ae))
2952 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"Switched to [{elem.Ae.Current.Name}] through MSAA.", application, GPALObjectType.Application);
2954 else if (
true == HardwareReaches(application))
2956 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{elem.Ae.Current.Name}] publishes no selection through UIAutomation or MSAA, so the tab is switched to with the mouse.", application, GPALObjectType.Application);
2958 HardwareClick(application, elem, ClickType.LeftClick, ModifierKeys.NONE, sel.OffsetX, sel.OffsetY);
2965 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $
"The tab [{elem.Ae.Current.Name}] was not switched to. It publishes no selection through UIAutomation or MSAA, so switching it is the mouse, and the mouse cannot reach [{application.applicationSettings.HiddenDesktopName}] while it is hidden.", application, GPALObjectType.Application);
2968 if (++rowCount >= HowManyToActOn(application))
2992 private static int HowManyToActOn(Application application)
2994 return int.MaxValue == application.CurrentUOW.WithAllThatMatch ? 1 : application.CurrentUOW.WithAllThatMatch;
3010 private static AutomationElement WritableFor(Application application, AutomationElement element)
3012 AutomationElement retVal = element;
3014 if (
false == element.TryGetCurrentPattern(RangeValuePattern.Pattern, out
object unused)
3015 &&
false == element.TryGetCurrentPattern(ValuePattern.Pattern, out unused))
3017 AutomationElement inside = element.FindFirst(TreeScope.Descendants,
new OrCondition(
3018 new PropertyCondition(AutomationElement.IsRangeValuePatternAvailableProperty,
true),
3019 new PropertyCondition(AutomationElement.IsValuePatternAvailableProperty,
true)));
3023 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{element.Current.ControlType.ProgrammaticName}] carries no value of its own, so the [{inside.Current.ControlType.ProgrammaticName}] inside it is what takes one.", application, GPALObjectType.Application);
3035 [DllImport(
"user32.dll")]
3036 private static extern bool EnumChildWindows(IntPtr parent, EnumChildProc callback, IntPtr lParam);
3038 private delegate
bool EnumChildProc(IntPtr handle, IntPtr lParam);
3040 [DllImport(
"user32.dll")]
3041 private static extern IntPtr SendMessage(IntPtr handle,
int message, IntPtr wParam, IntPtr lParam);
3043 [DllImport(
"user32.dll", CharSet = CharSet.Unicode, SetLastError =
true)]
3044 private static extern IntPtr SendMessage(IntPtr handle,
int message, IntPtr wParam,
string lParam);
3046 [DllImport(
"user32.dll", CharSet = CharSet.Unicode, SetLastError =
true)]
3047 private static extern int SendMessage(IntPtr handle,
int message,
int wParam, System.Text.StringBuilder lParam);
3049 private const int WM_KILLFOCUS = 0x0008;
3050 private const int WM_SETTEXT = 0x000C;
3051 private const int WM_GETTEXT = 0x000D;
3052 private const int WM_GETTEXTLENGTH = 0x000E;
3059 private static string NativeTextOf(IntPtr handle)
3061 int length = SendMessage(handle, WM_GETTEXTLENGTH, 0,
null);
3062 System.Text.StringBuilder text =
new System.Text.StringBuilder(length + 1);
3064 SendMessage(handle, WM_GETTEXT, text.Capacity, text);
3066 return text.ToString();
3088 private static bool SetNativeText(Application application, AutomationElement element,
string useText, WriteMode writeMode)
3090 bool retVal =
false;
3091 IntPtr handle =
new IntPtr(element.Current.NativeWindowHandle);
3099 if (WriteMode.Overwrite != writeMode)
3100 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{element.Current.ControlType.ProgrammaticName}] is added to rather than replaced, which its window cannot be asked to do, so it is typed into instead.", application, GPALObjectType.Application);
3101 else if (IntPtr.Zero != handle)
3109 bool answered = IntPtr.Zero != SendMessage(handle, WM_SETTEXT, IntPtr.Zero, useText);
3111 retVal =
true == answered
3112 && (
true ==
string.IsNullOrEmpty(useText) ||
true == NativeTextOf(handle).Contains(useText));
3115 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{element.Current.ControlType.ProgrammaticName}] publishes no value that can be written, so the text was written to the window holding it.", application, GPALObjectType.Application);
3116 else if (
true == answered)
3117 GPAL.PublishSimpleEvent(GPALEventType.INFO, $
"[{element.Current.ControlType.ProgrammaticName}] said it took the text written to its window but is not holding it, so it is typed into instead.", application, GPALObjectType.Application);
3135 private static AutomationElement NativeWritableFor(AutomationElement element)
3137 AutomationElement retVal =
null;
3138 IntPtr handle =
new IntPtr(element.Current.NativeWindowHandle);
3140 if (IntPtr.Zero != handle)
3142 List<IntPtr> inside =
new List<IntPtr>();
3144 EnumChildWindows(handle, (child, unused) => { inside.Add(child);
return true; }, IntPtr.Zero);
3146 foreach (IntPtr child
in inside)
3148 AutomationElement fromWindow = AutomationElement.FromHandle(child);
3153 if (
true == fromWindow.TryGetCurrentPattern(ValuePattern.Pattern, out
object unused))
3155 retVal = fromWindow;
3174 private static void Commit(AutomationElement element)
3176 IntPtr handle =
new IntPtr(element.Current.NativeWindowHandle);
3178 if (IntPtr.Zero != handle)
3179 SendMessage(handle, WM_KILLFOCUS, IntPtr.Zero, IntPtr.Zero);
3196 private static bool SetNativeValue(Application application, AutomationElement element,
string value)
3198 bool retVal =
false;
3199 AutomationElement writable = NativeWritableFor(element);
3201 if (
null != writable &&
true == writable.TryGetCurrentPattern(ValuePattern.Pattern, out
object valuePattern))
3203 ((ValuePattern)valuePattern).SetValue(value);
3207 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{element.Current.ControlType.ProgrammaticName}] keeps its value in a window UIAutomation does not list, so [{value}] was written there and the control was told to read it.", application, GPALObjectType.Application);
3215 public static void SetRange(Application application,
double rangeValue)
3217 List<GPALAutomationElement> matchedElements;
3218 bool matchedAll =
false;
3220 foreach (Selector sel
in application.CurrentUOW.WithSelectorList)
3222 if (SelectorType.Selector != sel.SelectorType)
3225 ElementHelper.FindElements(application, application.CurrentUOW, sel, out matchedAll, out matchedElements);
3227 if (
null != matchedElements)
3229 foreach (GPALAutomationElement elem
in matchedElements)
3231 if (
null == elem.Ae)
continue;
3237 AutomationElement writable = WritableFor(application, elem.Ae);
3244 if (
true == writable.TryGetCurrentPattern(RangeValuePattern.Pattern, out
object rangeValuePattern))
3248 ((RangeValuePattern)rangeValuePattern).SetValue(rangeValue);
3252 catch (Exception ex)
3254 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"RangeValuePattern on [{writable.Current.ControlType.ProgrammaticName}] refused [{rangeValue}], so the next way of setting it is tried.", application, GPALObjectType.Application, ex);
3258 if (
false == taken &&
true == writable.TryGetCurrentPattern(ValuePattern.Pattern, out
object valuePattern))
3262 ((ValuePattern)valuePattern).SetValue(rangeValue.ToString());
3271 catch (Exception ex)
3273 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"ValuePattern on [{writable.Current.ControlType.ProgrammaticName}] refused [{rangeValue}], so the next way of setting it is tried.", application, GPALObjectType.Application, ex);
3281 taken = SetNativeValue(application, elem.Ae, rangeValue.ToString());
3286 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $
"[{elem.Ae.Current.ControlType.ProgrammaticName}] offers no value to set through UIAutomation, so it is typed into instead.", application, GPALObjectType.Application);
3288 HardwareFillInFrom(application, elem, sel.OffsetX, sel.OffsetY, rangeValue.ToString(), WriteMode.Overwrite);
Settings for the current selector. Use this only for debugging.