GPAL - Generally Positive Automation Library v1.0
GPAL The Fluent Automation LIbrary
Loading...
Searching...
No Matches
Application.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
17//extern alias mUIA;
18using System;
19using System.Collections.Generic;
20using System.Diagnostics;
21using System.IO;
22using System.Linq;
23using System.Text;
24using System.Threading;
25using System.Threading.Tasks;
26using System.Windows.Automation;
29using static GenerallyPositive.Enums;
30
32{
48 public class Application
50 {
85 public delegate CallIfStatus CallIfDelegate(Application application, List<IGPALAutomationElement> foundElements, List<IGPALAutomationElement> matchedElements, Selector selector, bool matchedAll);
111 public delegate CallIfStatus CallAfterFillInDelegate(Application application, IGPALGrid<string> tokens, int tokenIdx);
112
113 #region <Working Variables>
117 internal ApplicationSettings applicationSettings { get; set; }
118 private dynamic persistenceList;
122 internal UnitOfWork CurrentUOW { get; set; } = null;
123 private UnitOfWork persistentUOW = null;
124 #endregion <Working Variables>
140 public Application ToGPALObject()
141 {
142 return this;
143 }
144
160 internal Application()
161 {
162 CurrentUOW = new UnitOfWork();
163 persistentUOW = new UnitOfWork();
164 persistenceList = new List<UnitOfWork>
165 {
166 persistentUOW
167 };
168 applicationSettings = new ApplicationSettings(this);
169 }
170 #region <Private>
176 private bool CheckWaitFor()
177 {
178 bool matchFound = true;
179 List<GPALAutomationElement> foundElements = null;
180
181 // an earlier action on this same unit of work already waited these selectors in successfully, so
182 // waiting again is redundant - nothing can have moved under us, since any selector added after an
183 // action starts a fresh unit of work. the action still resolves its own elements either way.
184 if (null != CurrentUOW.WaitForSatisfied)
185 {
186 GPAL.PublishSimpleEvent(GPALEventType.DEBUG, $"Selectors for this unit of work already found at [{CurrentUOW.WaitForSatisfied:HH:mm:ss.fff}], skipping redundant wait.", this, GPALObjectType.Application);
187 return true;
188 }
189
190 if (WaitTime.Never < CurrentUOW.WaitForInMs)
191 {
192 // we are waiting on selector(s)
193 foundElements = ElementHelper.WaitFor(this, CurrentUOW.WaitForInMs, out matchFound);
194
195 if (0 < foundElements?.Count)
196 CurrentUOW.WaitForSatisfied = DateTime.Now;
197 }
198
199 return matchFound;
200 }
207 private bool InAction(bool checkWaitFor)
208 {
209 ApplicationHelper.TopProcess(this.Process); // ensure the browser window is on top, can use selenium or hardware (mouse click) topping. firefox will only top with hardware
210
211 CurrentUOW.ActionCalled = true;
212
213 SelectorListMaintenanceSweep(CurrentUOW); // purge any nodes marked 'deleteme' before iterating list
214 SelectorListMaintenanceSweep(persistentUOW); // purge any nodes marked 'deleteme' before iterating list
215 CheckPersistentSelectors(); // check if there are persistent selectors to deal with
216
217 // TODO: CAVEAT: WaitFor will call the CallIf handlers, which might result in unexpected behavior?
218 if (true == checkWaitFor)
219 CheckWaitFor();
220
221 // always attempt the gated action, same as the browser side. WaitFor only holds the workflow until
222 // something turns up, it is not a match test - and the action does its own element lookup anyway,
223 // so a genuinely missing element surfaces there where it gets logged and can be debugged, rather
224 // than the action being silently skipped here.
225 return true;
226 }
231 private static void SelectorListMaintenanceSweep(dynamic CurrentUOW)
232 {
233 for (int idx = 0; idx < CurrentUOW.WithSelectorList.Count; idx++)
234 if (true == CurrentUOW.WithSelectorList[idx].DeleteMe)
235 CurrentUOW.WithSelectorList.RemoveAt(idx);
236
237 for (int idx = 0; idx < CurrentUOW.InSelectorList.Count; idx++)
238 if (true == CurrentUOW.InSelectorList[idx].DeleteMe)
239 CurrentUOW.InSelectorList.RemoveAt(idx);
240 }
245 private void CheckPersistentSelectors()
246 {
247 // all we have to do is walk the list, try to find the element and the callif callbacks will do the work
248 if (0 < (persistentUOW.WithSelectorList?.Count ?? 0))
249 foreach (Selector selector in persistentUOW.WithSelectorList)
250 ElementHelper.FindElements(this, persistentUOW, selector, out bool matchedAll, out List<GPALAutomationElement> elems);
251 }
252 #endregion <Private>
253 #region <Application Actions>
258 {
259 get
260 {
261 if (null != Process)
262 ApplicationHelper.ShowWindow(Process.MainWindowHandle, 3); // 1= SW_NORMAL - show normal, 3 = max, 2 = min
263 return this;
264 }
265 }
266
270 {
271 get
272 {
273 ApplicationHelper.ShowWindow(Process.MainWindowHandle, 2); // 1= SW_NORMAL - show normal, 3 = max, 2 = min
274 return this;
275 }
276 }
277
281 {
282 get
283 {
284 ApplicationHelper.ShowWindow(Process.MainWindowHandle, 1); // 1= SW_NORMAL - show normal, 3 = max, 2 = min
285 return this;
286 }
287 }
288
292 {
293 if (true == InAction(true))
294 ApplicationHelper.ResizeWindow(ApplicationSettings.LastWindowHandle, width, height);
295 return this;
296 }
297
301 {
302 if (true == InAction(true))
303 ApplicationHelper.MoveWindow(ApplicationSettings.LastWindowHandle, x, y);
304 return this;
305 }
306
315 {
316 DatabaseHelper.TokenizeDatabase(CurrentUOW, inputDatabase);
317 if (true == InAction(true))
318 ApplicationHelper.FillInWithTokens(this, inputDatabase.Tokens, WriteMode.Append);
319 return this;
320 }
321
330 {
331 ((IGPALFileInternal)inputFile).TokenList = FileHelper.TokenizeFile(CurrentUOW, inputFile);
332
333 if (true == InAction(true))
334 ApplicationHelper.FillInWithTokens(this, ((IGPALFileInternal)inputFile).TokenList, WriteMode.Append);
335
336 return this;
337 }
338
346 public IAllowSelectorActionOrAnySelector AppendFrom(IGPALGrid<string> inputGrid)
347 {
348 if (true == InAction(true))
349 ApplicationHelper.FillInWithTokens(this, inputGrid, WriteMode.Append);
350
351 return this;
352 }
353
360 {
361 if (true == InAction(true))
362 ElementHelper.FillInFrom(this, textToUse, WriteMode.Append);
363 return this;
364 }
365
372 public IAllowAllExceptParameters Open([Directory()] string applicationPath)
373 {
374 IntPtr processHandle = IntPtr.Zero;
375 Process[] processes;
376 StringBuilder parameters = new StringBuilder();
377 bool first = true;
378
379 foreach (string parameter in applicationSettings.Parameters)
380 {
381 if (false == first)
382 parameters.Append(" ");
383 else
384 first = false;
385 parameters.Append(parameter);
386 }
387
388 if (null != Process)
389 {
390 GPAL.PublishSimpleEvent(GPALEventType.ERROR, "Unable to Open/control more than one program. Please use another Application.", this, GPALObjectType.Application);
391 return this;
392 }
393
394 ApplicationPath = applicationPath;
395
396 // CAVEAT: see if process is open and attach to it? not sure this is what we want
397 // maybe we should just launch a new instance?
398 try
399 {
400 processes = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(applicationPath));
401 bool attached = false;
402 if (null != processes && 0 < processes.Length)
403 {
404 // An already-running instance should have a window quickly; probe each briefly and
405 // skip windowless/background matches rather than blocking for the full timeout.
406 foreach (Process process in processes)
407 {
408 applicationSettings.Process = process;
409 AutomationElement existingRoot = WaitForRootAutomationElement(process, 2000);
410 if (null != existingRoot)
411 {
412 applicationSettings.RootAutomationElement = existingRoot;
413 processHandle = process.MainWindowHandle;
414 attached = true;
415 break;
416 }
417 }
418 }
419
420 if (false == attached)
421 {
422 applicationSettings.Process = Process.Start(applicationPath, parameters.ToString());
423
424 // Do not guess with a fixed sleep: poll until the main window exists and its root
425 // automation element is actually retrievable, up to WaitForWindowTimeout seconds.
426 AutomationElement root = WaitForRootAutomationElement(applicationSettings.Process,
427 Math.Max(1, applicationSettings.WaitForWindowTimeout) * 1000);
428
429 if (null == root)
430 {
431 string msg = $"Timed out after [{applicationSettings.WaitForWindowTimeout}]s waiting for the main window / root automation element of [{applicationPath}].";
432 GPAL.PublishSimpleEvent(GPALEventType.ERROR, msg, this, GPALObjectType.Application);
433 throw new GPALException(msg);
434 }
435
436 applicationSettings.RootAutomationElement = root;
437 processHandle = applicationSettings.Process.MainWindowHandle;
438 }
439 }
440 catch (Exception ex)
441 {
442 GPAL.PublishSimpleEvent(GPALEventType.EXCEPTION, $"Unable to launch [{applicationPath}].", this, GPALObjectType.Application, ex);
443 return this;
444 }
445
446 if (null == applicationSettings.Process)
447 GPAL.PublishSimpleEvent(GPALEventType.ERROR, $"Unable to launch [{applicationPath}].", this, GPALObjectType.Application);
448 else
449 {
450 ApplicationHelper.TopProcess(applicationSettings.Process);
451 CurrentUOW.ActionCalled = true;
452 }
453
454 return this;
455 }
456
464 private AutomationElement WaitForRootAutomationElement(Process process, int timeoutMs)
465 {
466 if (null == process)
467 return null;
468
469 Stopwatch stopwatch = Stopwatch.StartNew();
470
471 // Let a GUI app pump its message loop and create its window. This throws immediately for a
472 // process with no message loop (e.g. a console app), so it never wastes the timeout on those.
473 try { process.WaitForInputIdle(timeoutMs); } catch { /* no message loop; fall through to polling */ }
474
475 while (stopwatch.ElapsedMilliseconds < timeoutMs)
476 {
477 try
478 {
479 process.Refresh();
480 IntPtr handle = process.MainWindowHandle;
481 if (IntPtr.Zero != handle)
482 {
483 AutomationElement root = AutomationElement.FromHandle(handle);
484 if (null != root)
485 return root;
486 }
487 }
488 catch
489 {
490 // Window/handle not ready yet (or transiently invalid); keep polling until timeout.
491 }
492
493 Thread.Sleep(100);
494 }
495
496 return null;
497 }
502 public void Close()
503 {
504 Process.Close();
505 Process = null;
506 }
507
516 {
517 DatabaseHelper.TokenizeDatabase(CurrentUOW, inputDatabase);
518
519 if (true == InAction(true))
520 ApplicationHelper.FillInWithTokens(this, CurrentUOW.InputDatabase.Tokens, WriteMode.Overwrite);
521
522 return this;
523 }
524
533 {
534 ((IGPALFileInternal)inputFile).TokenList = FileHelper.TokenizeFile(CurrentUOW, inputFile);
535 if (true == InAction(true))
536 ApplicationHelper.FillInWithTokens(this, ((IGPALFileInternal)CurrentUOW.InputFile).TokenList, WriteMode.Overwrite);
537 return this;
538 }
539
547 public IAllowSelectorActionOrAnySelector FillInFrom(IGPALGrid<string> inputGrid)
548 {
549 if (true == InAction(true))
550 ApplicationHelper.FillInWithTokens(this, (IGPALGrid<string>)inputGrid, WriteMode.Overwrite);
551 return this;
552 }
553
560 {
561 if (true == InAction(true))
562 ElementHelper.FillInFrom(this, textToUse, WriteMode.Overwrite);
563 return this;
564 }
565
572 {
573 if (null != selector) WithSelector(selector);
574 if (true == InAction(true))
575 ElementHelper.Focus(this);
576 return this;
577 }
578
586 {
587 if (null != selector) WithSelector(selector);
588 if (true == InAction(true))
589 ElementHelper.Hover(this, CurrentUOW);
590 return this;
591 }
592
600 {
601 if (true == InAction(true))
602 ElementHelper.FillInFrom(this, textToUse, WriteMode.Insert);
603 return this;
604 }
605
614 {
615 DatabaseHelper.TokenizeDatabase(CurrentUOW, inputDatabase);
616 if (true == InAction(true))
617 ApplicationHelper.FillInWithTokens(this, CurrentUOW.InputDatabase.Tokens, WriteMode.Insert);
618 return this;
619 }
620
628 public IAllowSelectorActionOrAnySelector InsertFrom(IGPALGrid<string> inputGrid)
629 {
630 if (true == InAction(true))
631 ApplicationHelper.FillInWithTokens(this, (IGPALGrid<string>)inputGrid, WriteMode.Insert);
632 return this;
633 }
634
643 {
644 ((IGPALFileInternal)inputFile).TokenList = FileHelper.TokenizeFile(CurrentUOW, inputFile);
645 if (true == InAction(true))
646 ApplicationHelper.FillInWithTokens(this, ((IGPALFileInternal)CurrentUOW.InputFile).TokenList, WriteMode.Insert);
647 return this;
648 }
649
656 {
657 if (null != selector) WithSelector(selector);
658 if (true == InAction(true))
659 ElementHelper.LeftClick(this);
660 return this;
661 }
662
667 public IAllowSelectorActionOrAnySelector LeftClick(Enums.ModifierKeys modifierKeys)
668 {
669 if (true == InAction(true))
670 ElementHelper.LeftClick(this, modifierKeys);
671 return this;
672 }
673
679 {
680 if (null != selector) WithSelector(selector);
681 if (true == InAction(true))
682 ElementHelper.GenericClick(this, ClickType.LeftDoubleClick);
683 return this;
684 }
685
691 {
692 if (null != selector) WithSelector(selector);
693 if (true == InAction(true))
694 ElementHelper.GenericClick(this, ClickType.MiddleClick);
695 return this;
696 }
697
703 {
704 if (null != selector) WithSelector(selector);
705 if (true == InAction(true))
706 ElementHelper.MoveTo(this);
707 return this;
708 }
709
715 {
716 if (null != selector) WithSelector(selector);
717 if (true == InAction(true))
718 ElementHelper.DragAndDrop(this);
719 return this;
720 }
721
727 public IAllowSelectorActionOrAnySelector DragAndDrop(ModifierKeys modifierKeys)
728 {
729 if (true == InAction(true))
730 ElementHelper.DragAndDrop(this, modifierKeys);
731 return this;
732 }
733
739 public IAllowSelectorActionOrAnySelector PressModifierKey(ModifierKeys modifierKeys)
740 {
741 HardwareHelper.PressModifierKey(modifierKeys);
742 return this;
743 }
744
751 {
752 HardwareHelper.ReleaseModifierKeys(modifierKeys);
753 return this;
754 }
755
776 {
777 if (true == InAction(true))
778 HardwareHelper.SendKey(VKCode);
779 return this;
780 }
781
787 {
788 HardwareHelper.SendString(textToSend, GPAL.TypingDelay);
789 return this;
790 }
791
797 {
798 if (null != selector) WithSelector(selector);
799 if (true == InAction(true))
800 ElementHelper.SwitchToTab(this);
801 return this;
802 }
803
808 public IAllowAllActionAndAnySelector SetRange(double rangeValue)
809 {
810 if (true == InAction(true))
811 ElementHelper.SetRange(this, rangeValue);
812 return this;
813 }
814
821 {
822 if (null != selector) WithSelector(selector);
823 if (true == InAction(true))
824 ElementHelper.GenericClick(this, ClickType.RightClick);
825 return this;
826 }
827
836 public IAllowAfterWaitForAndSizeControl WaitFor(int timeoutInTicks)
837 {
838 // we are done waiting on any selectors since we performed an action
839 // so, we just wait/sleep
840 if (true == CurrentUOW.ActionCalled)
841 Thread.Sleep(timeoutInTicks);
842 else
843 CurrentUOW.WaitForInMs = timeoutInTicks;
844
845 return this;
846 }
847
855 {
856 if (true == InAction(true))
857 ApplicationSettings.LastWindowHandle = FormHelper.WaitForWindow(windowTitle, ApplicationSettings.WaitForWindowTimeout);
858 return this;
859 }
860
868 {
869 if (true == InAction(true))
870 ApplicationSettings.LastWindowHandle = FormHelper.WaitForWindowRegex(windowTitleRegex, ApplicationSettings.WaitForWindowTimeout);
871 return this;
872 }
873
880 {
881 for (; 0 < iterations; iterations--)
882 ElementHelper.Scroll(this, ScrollAmount.SmallIncrement, ScrollTypes.Increments, ScrollTypes.Horizontal);
883
884 for (; 0 > iterations; iterations++)
885 ElementHelper.Scroll(this, ScrollAmount.SmallDecrement, ScrollTypes.Increments, ScrollTypes.Horizontal);
886
887 return this;
888 }
889
896 {
897 for (; 0 < iterations; iterations--)
898 ElementHelper.Scroll(this, ScrollAmount.LargeIncrement, ScrollTypes.Increments, ScrollTypes.Horizontal);
899
900 for (; 0 > iterations; iterations++)
901 ElementHelper.Scroll(this, ScrollAmount.LargeDecrement, ScrollTypes.Increments, ScrollTypes.Horizontal);
902 return this;
903 }
904
910 {
911 if (0 > percent || 100 < percent)
912 {
913 GPAL.PublishSimpleEvent(GPALEventType.WARNING, $"Invalid percantage value [{percent}], unable to scroll using supplied values. Normalizing to nearest endpoint.", this, GPALObjectType.Application);
914 int newPercent = (0 > percent ? 0 : (100 < percent ? 100 : percent));
915 }
916 ElementHelper.Scroll(this, percent, ScrollTypes.Percent, ScrollTypes.Horizontal);
917 return this;
918 }
919
926 {
927 for (; 0 < iterations; iterations--) // + = down
928 ElementHelper.Scroll(this, ScrollAmount.SmallIncrement, ScrollTypes.Increments, ScrollTypes.Vertical);
929
930 for (; 0 > iterations; iterations++) // - = up
931 ElementHelper.Scroll(this, ScrollAmount.SmallDecrement, ScrollTypes.Increments, ScrollTypes.Vertical);
932
933 return this;
934 }
935
942 {
943 for (; 0 < iterations; iterations--)
944 ElementHelper.Scroll(this, ScrollAmount.LargeIncrement, ScrollTypes.Increments, ScrollTypes.Vertical);
945
946 for (; 0 > iterations; iterations++)
947 ElementHelper.Scroll(this, ScrollAmount.LargeDecrement, ScrollTypes.Increments, ScrollTypes.Vertical);
948 return this;
949 }
950
956 {
957 ElementHelper.Scroll(this, percent, ScrollTypes.Percent, ScrollTypes.Vertical);
958 return this;
959 }
960 #endregion <Application Actions>
961 #region <Helpers>
966 public void RemoveCallIfHandlerEverywhere(Application.CallIfDelegate func)
967 {
968 CurrentUOW.AppCallIfFound.Remove(func);
969 CurrentUOW.AppCallIfNotFound.Remove(func);
970
971 // TODO: do the same for inselectorlist
972 foreach (Selector selector in CurrentUOW.WithSelectorList)
973 {
974 selector.SelectorSettings.AppCallIfFound.Remove(func);
975 selector.SelectorSettings.AppCallIfNotFound.Remove(func);
976 }
977
978 foreach (Selector selector in CurrentUOW.InSelectorList)
979 {
980 selector.SelectorSettings.AppCallIfFound.Remove(func);
981 selector.SelectorSettings.AppCallIfNotFound.Remove(func);
982 }
983
984 persistentUOW.AppCallIfFound.Remove(func);
985 persistentUOW.AppCallIfNotFound.Remove(func);
986
987 foreach (Selector selector in persistentUOW.WithSelectorList)
988 {
989 selector.SelectorSettings.AppCallIfFound.Remove(func);
990 selector.SelectorSettings.AppCallIfNotFound.Remove(func);
991 }
992 }
993 #endregion <Helpers>
994 #region <Application Settings>
1001 {
1002 Name = appName;
1003 return this;
1004 }
1005
1015 {
1016 persistentUOW.AppCallIfFound.Add(persistentCallIfFound);
1017
1018 return this;
1019 }
1020
1030 {
1031 persistentUOW.AppCallIfNotFound.Add(persistentCallIfNotFound);
1032
1033 return this;
1034 }
1035
1045 {
1046 // when would we ever add another persistent uow?
1047 // normal uow is once action is called, any withselector is on the next uow, but persistents don't act like this
1048 if (true == CurrentUOW.ActionCalled)
1049 {
1050 persistentUOW = new UnitOfWork();
1051 persistenceList.Add(persistentUOW);
1052 }
1053
1054 selector.Application = this;
1055 //persistentUOW.SelectorType = SelectorType.WithSelector;
1056 persistentUOW.WithSelectorList.Add(selector);
1057 return this;
1058 }
1059
1068 {
1069 selector.ElementsFoundAndMatchedCount = 0; // in case it is reused
1070
1071 if (true == CurrentUOW.ActionCalled)
1072 {
1073 CurrentUOW = new UnitOfWork();
1074 }
1075
1076 // clone so if this selector is reused, each UOW list will be unique and we won't change the selector by changing the browser it is associated with
1077 // NOTE: cloning disassociates with actual program selector, so we can't set any queryable properties on Selector like 'ElementsFoundAndMatchedCount'
1078 // ApplicationSettings.MySelector = new Selector(this, selector); // MySelector temp hold for other browser syntax
1079 ApplicationSettings.MySelector = selector;
1080
1081 CurrentUOW.WithSelectorList.Add(ApplicationSettings.MySelector);
1082
1083 return this;
1084 }
1085 // TODO: How to deal with tokens. To consume a row for each row, but to also repeat the same token down the page?
1098 public IAllowAfterAnySelectorExceptWithAll WithAllThatMatch(int rowCount = int.MaxValue)
1099 {
1100 CurrentUOW.WithAllThatMatch = rowCount;
1101 return this;
1102 }
1103
1104 #region <GetGrid / SaveTo / Pagination>
1105 // ─────────────────────────────────────────────────────────────────────────────────────
1106 // Data extraction out of a desktop application, mirroring the Browser GetGrid API.
1107 // A single selector that resolves to a UIA grid/table control is read structurally via
1108 // GridPattern/TablePattern; otherwise each selector is a column and repeating matches are
1109 // the rows (the WithAllThatMatch model). Missing cells use GPAL.ErrorPlaceholder. The
1110 // result lands in CurrentUOW.RetGrid so SaveTo*/pagination reuse the shared, engine-neutral
1111 // helpers (FileHelper.SaveToDelimited, GPAL.Converter) exactly as the Browser side does.
1112 // ─────────────────────────────────────────────────────────────────────────────────────
1119 public IAllowWithHeaderOrFileActions GetGrid(ref IGPALGrid<string> returnGrid)
1120 {
1121 CurrentUOW.GetGridCalled = true;
1122 CurrentUOW.RetGrid.Clear();
1123
1124 if (!TryGetGridFromControl())
1125 GetGridFromSelectors();
1126
1127 // Do not take ownership of a grid the caller passed in:
1128 // - null in -> hand them GPAL's internal grid.
1129 // - grid in -> add our rows into THEIR grid and keep their reference so repeated GetGrid calls accumulate.
1130 if (null == returnGrid)
1131 returnGrid = (IGPALGrid<string>)CurrentUOW.RetGrid;
1132 else
1133 returnGrid.Add((IGPALGrid<string>)CurrentUOW.RetGrid);
1134
1135 return this;
1136 }
1137
1138 // Table-native: a single selector that resolves to a control exposing the UIA GridPattern
1139 // is read cell-by-cell via GetItem(row, col); TablePattern column headers become the header
1140 // row. Returns false when this is not a grid control so the selector-column path runs.
1141 private bool TryGetGridFromControl()
1142 {
1143 if (1 != CurrentUOW.WithSelectorList.Count
1144 || SelectorType.Selector != CurrentUOW.WithSelectorList[0].selectorSettings.SelectorType)
1145 return false;
1146
1147 Selector sel = CurrentUOW.WithSelectorList[0];
1148 bool matchedAll; List<GPALAutomationElement> matched;
1149 var found = ElementHelper.FindElements(this, CurrentUOW, sel, out matchedAll, out matched);
1150 AutomationElement ae = (null != found && found.Count > 0) ? found[0].Ae : null;
1151 object gpObj;
1152 if (null == ae || !ae.TryGetCurrentPattern(GridPattern.Pattern, out gpObj))
1153 return false;
1154
1155 GridPattern grid = (GridPattern)gpObj;
1156 int rows = grid.Current.RowCount;
1157 int cols = grid.Current.ColumnCount;
1158
1159 List<string> headers = null;
1160 object tpObj;
1161 if (ae.TryGetCurrentPattern(TablePattern.Pattern, out tpObj))
1162 {
1163 AutomationElement[] colHeaders = ((TablePattern)tpObj).Current.GetColumnHeaders();
1164 if (null != colHeaders && colHeaders.Length > 0)
1165 {
1166 headers = new List<string>();
1167 foreach (AutomationElement h in colHeaders)
1168 headers.Add(h.GetText() ?? string.Empty);
1169 }
1170 }
1171
1172 int cap = (int.MaxValue == CurrentUOW.WithAllThatMatch)
1173 ? int.MaxValue : CurrentUOW.WithAllThatMatch;
1174
1175 for (int r = 0; r < rows && CurrentUOW.RetGrid.Count() < cap; r++)
1176 {
1177 var row = new List<string>(cols);
1178 for (int c = 0; c < cols; c++)
1179 {
1180 string text;
1181 try
1182 {
1183 AutomationElement cell = grid.GetItem(r, c);
1184 text = (null != cell) ? (cell.GetText() ?? string.Empty) : GPAL.ErrorPlaceholder;
1185 }
1186 catch
1187 {
1188 text = GPAL.ErrorPlaceholder;
1189 }
1190 row.Add(text);
1191 }
1192 CurrentUOW.RetGrid.AddRow(row);
1193 }
1194
1195 if (null != headers && headers.Count > 0
1196 && (null == CurrentUOW.HeaderList || 0 == CurrentUOW.HeaderList.Count))
1197 CurrentUOW.HeaderList = headers;
1198
1199 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"GetGrid: [{sel.Name}] resolved to a UIA grid ([{rows}] x [{cols}]).", this, GPALObjectType.Application);
1200 return true;
1201 }
1202
1203 // Column-per-selector extraction: each selector is a column, repeating matches are the rows.
1204 // Mirrors the Browser column-zip (pad short columns, dedup rows, honor WithAllThatMatch and
1205 // multi-page paging) but reads cells with the UIA .Ae.GetText() the app side already uses.
1206 private void GetGridFromSelectors()
1207 {
1208 var names = new List<string>();
1209 foreach (Selector s in CurrentUOW.WithSelectorList)
1210 names.Add(s.Name);
1211
1212 var seenRows = new HashSet<string>();
1213 bool capReached = false;
1214 int cap = (int.MaxValue == CurrentUOW.WithAllThatMatch)
1215 ? int.MaxValue : CurrentUOW.WithAllThatMatch;
1216
1217 for (int page = 0; page < CurrentUOW.PageCount && !capReached; page++)
1218 {
1219 var columns = new List<List<string>>();
1220 int maxRows = 0;
1221
1222 foreach (Selector sel in CurrentUOW.WithSelectorList)
1223 {
1224 var col = new List<string>();
1225 if (SelectorType.Selector == sel.selectorSettings.SelectorType)
1226 {
1227 bool matchedAll; List<GPALAutomationElement> matched;
1228 var found = ElementHelper.FindElements(this, CurrentUOW, sel, out matchedAll, out matched);
1229 List<GPALAutomationElement> elems =
1230 (!matchedAll && null != matched && matched.Count > 0)
1231 ? matched
1232 : (null != found ? found.ToList() : new List<GPALAutomationElement>());
1233 foreach (GPALAutomationElement e in elems)
1234 col.Add((null != e.Ae) ? (e.Ae.GetText() ?? string.Empty) : GPAL.ErrorPlaceholder);
1235 }
1236 else if (SelectorType.Data == sel.selectorSettings.SelectorType)
1237 col.Add(sel.SelectorPath);
1238 else if (SelectorType.DataFunc == sel.selectorSettings.SelectorType)
1239 col.Add(sel.selectorSettings.DataFunction());
1240
1241 columns.Add(col);
1242 if (col.Count > maxRows) maxRows = col.Count;
1243 }
1244
1245 // pad short columns so every row has a cell per column
1246 for (int i = 0; i < columns.Count; i++)
1247 while (columns[i].Count < maxRows)
1248 columns[i].Add($"{GPAL.ErrorPlaceholder} : {names[i]}");
1249
1250 for (int r = 0; r < maxRows && !capReached; r++)
1251 {
1252 var row = new List<string>(columns.Count);
1253 for (int c = 0; c < columns.Count; c++)
1254 row.Add(columns[c][r]);
1255
1256 string key = string.Join("", row);
1257 if (!seenRows.Add(key))
1258 continue; // duplicate row from a prior page/scroll pass
1259 CurrentUOW.RetGrid.AddRow(row);
1260 if (CurrentUOW.RetGrid.Count() >= cap)
1261 capReached = true;
1262 }
1263
1264 if (!capReached && page < CurrentUOW.PageCount - 1)
1265 if (!ApplicationHelper.GotoNextPage(this))
1266 {
1267 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"GetGrid: no next page after page [{page + 1}].", this, GPALObjectType.Application);
1268 break;
1269 }
1270 }
1271
1272 if (null == CurrentUOW.HeaderList || 0 == CurrentUOW.HeaderList.Count)
1273 CurrentUOW.HeaderList = names;
1274 }
1275
1278 {
1279 if (true == CurrentUOW.GetGridCalled)
1280 {
1281 CurrentUOW.GetGridCalled = false;
1282 CurrentUOW.HeaderList.Clear();
1283 }
1284 CurrentUOW.HeaderList.Add(header);
1285 return this;
1286 }
1287
1289 public IAllowWithHeaderOrFileActions WithGridToSave(IGPALGrid<string> gridToSave)
1290 {
1291 CurrentUOW.RetGrid = gridToSave;
1292 return this;
1293 }
1294
1297 {
1298 CurrentUOW.NextPageButton = selector;
1299 return this;
1300 }
1301
1304 {
1305 get
1306 {
1307 CurrentUOW.InfiniteScroll = true;
1308 return this;
1309 }
1310 }
1311
1313 public IAllowGetGridAndFillInFrom WithPages(int numberOfPages)
1314 {
1315 CurrentUOW.PageCount = numberOfPages;
1316 return this;
1317 }
1318
1319 // Resolve the output column names for a grid save, handed to the Converter as column metadata
1320 // (WithColumnNames), NOT injected as a data row. Precedence, highest first, with a short higher list
1321 // topped up from the next level down:
1322 // 1. CurrentUOW.HeaderList - the workflow's .WithHeader columns (override)
1323 // 2. the output GPALFile's ColumnList - the file's own columns (wildcard/.Next replication of the
1324 // first entry happens at expansion time, so entry 0 is representative here)
1325 // 3. selector names - the ultimate global, always defined
1326 // Whether the names are written is governed by the output file's FirstLineIsColumnNames.
1327 private List<string> ResolveHeaderColumns(GPALFile file)
1328 {
1329 List<string> selectorNames = CurrentUOW.WithSelectorList?.Where(s => null != s.Name).Select(s => s.Name).ToList()
1330 ?? new List<string>();
1331
1332 List<string> fileColumns = new List<string>();
1333 IGPALGrid<string> columnList = ((IGPALFileInternal)file).FileSettings.ColumnList;
1334 if (null != columnList && 0 < columnList.Count() && null != columnList[0])
1335 fileColumns = new List<string>(columnList[0]);
1336
1337 List<string> resolved = (null != CurrentUOW.HeaderList && 0 < CurrentUOW.HeaderList.Count)
1338 ? new List<string>(CurrentUOW.HeaderList)
1339 : new List<string>();
1340 for (int i = resolved.Count; i < fileColumns.Count; i++)
1341 resolved.Add(fileColumns[i]);
1342 for (int i = resolved.Count; i < selectorNames.Count; i++)
1343 resolved.Add(selectorNames[i]);
1344 return resolved;
1345 }
1346
1352 {
1353 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Saving [{CurrentUOW.RetGrid?.Rows ?? 0}] rows of window data to [{file?.Filename}].", this, GPALObjectType.Application);
1354
1355 var converter = GPAL.Converter.WithInput(CurrentUOW.RetGrid).WithColumnNames(ResolveHeaderColumns(file));
1356 // Naming headers via WithHeader is clear intent to write them; declare it so the output emits a
1357 // header row unless the output file explicitly turns it off (WithFirstLineIsColumnNames(false)).
1358 if (null != CurrentUOW.HeaderList && 0 < CurrentUOW.HeaderList.Count)
1359 converter.WithFirstLineHasColumnNames(true);
1360 converter.SaveTo(file);
1361 return this;
1362 }
1363
1369 {
1370 GPAL.PublishSimpleEvent(GPALEventType.INFO, $"Appending [{CurrentUOW.RetGrid?.Rows ?? 0}] rows of window data to [{file?.Filename}].", this, GPALObjectType.Application);
1371
1372 GPAL.Converter.WithInput(CurrentUOW.RetGrid).WithColumnNames(ResolveHeaderColumns(file)).AppendTo(file);
1373 return this;
1374 }
1375 #endregion <GetGrid / SaveTo / Pagination>
1382 {
1383 ApplicationSettings.WaitForWindowTimeout = waitTimeInSeconds;
1384 return this;
1385 }
1386
1392 {
1393 ApplicationSettings.Parameters.Add(parameter);
1394 return this;
1395 }
1396
1402 {
1403 ApplicationSettings.UseHardware = trueFalse;
1404 return this;
1405 }
1406
1416 public IAllowAfterAnySelector CallIfFound(Application.CallIfDelegate callIfFoundDelegate)
1417 {
1418 CurrentUOW.AppCallIfFound.Add(callIfFoundDelegate);
1419 return this;
1420 }
1421
1431 public IAllowAfterAnySelector CallIfNotFound(Application.CallIfDelegate callIfNotFoundDelegate)
1432 {
1433 CurrentUOW.AppCallIfNotFound.Add(callIfNotFoundDelegate);
1434 return this;
1435 }
1436
1444 {
1445 CurrentUOW.AppCallAfterFillIn = callAfterFillIn;
1446 return this;
1447 }
1448 #region <Getters/Setters>
1456 {
1457 get
1458 {
1459 return applicationSettings.Process;
1460 }
1461 internal set
1462 {
1463 applicationSettings.Process = value;
1464 }
1465 }
1466
1470 {
1471 get
1472 {
1473 return applicationSettings.ManagedRootAutomationElement;
1474 }
1475
1476 internal set
1477 {
1478 applicationSettings.ManagedRootAutomationElement = value;
1479 }
1480 }
1481
1484 public AutomationElement RootAutomationElement
1485 {
1486 get
1487 {
1488 return applicationSettings.RootAutomationElement;
1489 }
1490
1491 internal set
1492 {
1493 applicationSettings.RootAutomationElement = value;
1494 }
1495 }
1496
1502 public string Name
1503 {
1504 get
1505 {
1506 return ApplicationSettings.Name;
1507 }
1508
1509 internal set
1510 {
1511 ApplicationSettings.Name = value;
1512 }
1513 }
1514
1519 public string ApplicationPath
1520 {
1521 get
1522 {
1523 return applicationSettings.ApplicationPath;
1524 }
1525 internal set
1526 {
1527 applicationSettings.ApplicationPath = value;
1528 }
1529 }
1530
1534 internal ApplicationSettings ApplicationSettings
1535 {
1536 get
1537 {
1538 return applicationSettings;
1539 }
1540 }
1541 #endregion <Getters/Setters>
1542 #endregion <Application Settings>
1543 }
1544}
1545
Application object that contains the fluent methods to create your Application workflow....
string ApplicationPath
The path of the program you Opened. This get is intended for debugging only.
IAllowSelectorActionOrAnySelector SendKey(byte VKCode)
Type special characters like Enter, Page Up, Page Down, Tab, etc. Use GPAL.VK constants for ease of ...
IAllowSelectorActionOrAnySelector FillInFrom(GPALFile inputFile)
FillIn data from the file overwriting text in the input Selector(s) defined in the UOW One token (on...
IAllowAfterWaitForAndSizeControl WithWaitForWindowTimeout(int waitTimeInSeconds)
Define the time in seconds to WaitForWindow/Regex.
IAllowApplicationSettingsOrOpen WithParameter(string parameter)
Define the parameters to use in the Open command.
IAllowSelectorActionOrAnySelector ScrollHorizontalPercent(int percent)
Element's horizontal scroll position as a percentage of the total content area within the AutomationE...
IAllowAllActionAndAnySelector SwitchToTab(Selector selector=null)
Switch to any TabControl defined in the current UOW. This will hardware click the tab control if the ...
IAllowSelectorActionOrAnySelector DragAndDrop(ModifierKeys modifierKeys)
Performs a drag from the source element to the target element, both found via Selector(s) defined in ...
object ManagedRootAutomationElement
The root UI Automation element (AutomationElement) for the managed application's main window.
IAllowApplicationSettingsOrOpen WithName(string appName)
Sets the application's name, used when publishing events.
IAllowWithPagesAndGridActions WithInfiniteScroll
Scroll to load more rows between pages. Not supported for desktop apps (single page).
IAllowSelectorActionOrAnySelector ScrollHorizontalSmall(int iterations=1)
Scroll the element horizontally in a small increment. https://docs.microsoft.com/en-us/windows/win32...
IAllowAfterAnySelector CallIfNotFound(Application.CallIfDelegate callIfNotFoundDelegate)
Add a Unit of Work level CallIfNotFound handler to be called if any defined selectors DO NOT find/mat...
IAllowSelectorActionOrAnySelector DragAndDrop(Selector selector=null)
Performs a drag from the source element to the target element, both found via Selector(s) defined in ...
Application ToGPALObject()
Return a GPAL.Application object so that GPAL.Application does not have to be cast.
IAllowAllActionAndAnySelector SetRange(double rangeValue)
Set the value of a range-based control (e.g. a slider) found via Selector(s) defined in the UOW.
IAllowPersistentCallBack PersistentCallIfFound(CallIfDelegate persistentCallIfFound)
Add a persistent CallIfFound handler to be called if any defined persistent selectors find/match elem...
IAllowAllApplicationAndAllSelector Restore
Restore the application to 'original' size, unminimize.
IAllowAfterAnySelector CallIfFound(Application.CallIfDelegate callIfFoundDelegate)
Add a Unit of Work level CallIfFound handler to be called if any defined selectors find/match element...
IAllowSelectorActionOrAnySelector PressModifierKey(ModifierKeys modifierKeys)
Press and hold a modifier key (Alt, Control, Shift) using hardware emulation.
IAllowSelectorActionOrAnySelector SendString(string textToSend)
Send the string using hardware emulation.
IAllowSelectorActionOrAnySelector Hover(Selector selector=null)
Hover the mouse over the current element found via the Selector. Only the first element per Selector ...
IAllowAfterWaitForAndSizeControl WaitForWindowRegex(string windowTitleRegex)
Wait for a Windows window with title that matches the regex pattern to appear on the desktop....
IAllowAfterWaitForAndSizeControl WaitFor(int timeoutInTicks)
Either wait the amount of time specified or wait UP TO the amount of time specified waiting for eleme...
IAllowSelectorActionOrAnySelector ReleaseModifierKey(ModifierKeys modifierKeys)
Release a previously pressed modifier key (Alt, Control, Shift) using hardware emulation.
IAllowSelectorActionOrAnySelector InsertFrom(IGPALGrid< string > inputGrid)
Insert the data from the grid at the beginning of the text in the input Selector(s) defined in the UO...
IAllowAfterAnySelector CallAfterFillIn(CallAfterFillInDelegate callAfterFillIn)
Add a handler to call after a row of tokens is consumed and after all inputs are filled in with data....
IAllowSelectorActionOrAnySelector LeftClick(Enums.ModifierKeys modifierKeys)
Left click the element found for Selector(s) defined in the UOW, optionally emulating pressing a modi...
string Name
A name you provide to be used when publishing events. Use .WithName to provide your own friendly nam...
IAllowSelectorActionOrAnySelector InsertFrom(GPALDatabase inputDatabase)
Insert the text from the database at the beginning of the input Selector(s) defined in the UOW One t...
IAllowAfterWaitForAndSizeControl WaitForWindow(string windowTitle)
Wait for a Windows window with title to appear on the desktop. Used in conjunction with ....
IAllowSelectorActionOrAnySelector FillInFrom(GPALDatabase inputDatabase)
FillIn data from the database overwriting text in the input Selector(s) defined in the UOW One token...
IAllowApplicationSettingsOrOpen WithUseHardware(bool trueFalse)
Force all workflow element interactions to use hardware emulation (UIAutomation is default).
IAllowAllApplicationAndAllSelector MoveWindow(int x, int y)
Move the window last matched by WaitForWindow to the specified screen coordinates.
IAllowSelectorActionOrAnySelector FillInFrom(string textToUse)
FillIn the text from the string overwriting text in the first input in the UOW. Should probably only ...
delegate CallIfStatus CallAfterFillInDelegate(Application application, IGPALGrid< string > tokens, int tokenIdx)
Delegate callback for the CallAfterFillIn EventHandler which will be invoked after each row of tokens...
IAllowSelectorActionOrAnySelector InsertFrom(string textToUse)
Insert the text from the string at the beginning of the first input in the UOW. Should probably only ...
IAllowSelectorActionOrAnySelector Focus(Selector selector=null)
Focus the current element found via the Selector. Only the first element per Selector found will be f...
delegate CallIfStatus CallIfDelegate(Application application, List< IGPALAutomationElement > foundElements, List< IGPALAutomationElement > matchedElements, Selector selector, bool matchedAll)
Delegate callback for the CallIfFound/CallIfNotFound EventHandlers which will be invoked when a selec...
IAllowSelectorActionOrAnySelector LeftClick(Selector selector=null)
Left click the element found for Selector(s) defined in the UOW One left click for the first element...
IAllowSelectorActionOrAnySelector RightClick(Selector selector=null)
Right click the element found for Selector(s) defined in the UOW One right click for the first eleme...
IAllowSelectorActionOrAnySelector MiddleClick(Selector selector=null)
Middle click the element found for Selector(s) defined in the UOW.
IAllowAllApplicationAndAllSelector Minimize
Minimize the application to the taskbar.
IAllowWithHeaderOrFileActions WithGridToSave(IGPALGrid< string > gridToSave)
Supply a GPALGrid (perhaps built elsewhere) to be saved by a following SaveTo*.
IAllowAllActionAndAnySelector AppendTo(GPALFile file)
Append the retrieved grid. The output format and delimiter are determined by the GPALFile (its extens...
IAllowSelectorActionOrAnySelector WithSelector(Selector selector)
Add a selector to the current UOW. Selectors make up a Unit of Work (UOW) to perform actions upon....
IAllowSelectorActionOrAnySelector ScrollVerticalLarge(int iterations=1)
Scroll the element vertically in a large increment. https://docs.microsoft.com/en-us/windows/win32/a...
IAllowAfterAnySelectorExceptWithAll WithAllThatMatch(int rowCount=int.MaxValue)
Indicates the Selectors refer to/match repeating, multiple elements on the page. Use after all your ...
IAllowSelectorActionOrAnySelector FillInFrom(IGPALGrid< string > inputGrid)
FillIn data from the grid overwriting text in the input Selector(s) defined in the UOW One token (on...
IAllowGetGridAndFillInFrom WithPages(int numberOfPages)
Number of result pages to retrieve. Use with WithNextPageButton.
IAllowSelectorActionOrAnySelector ScrollVerticalPercent(int percent)
Element's vertical scroll position as a percentage of the total content area within the AutomationEle...
IAllowSelectorActionOrAnySelector LeftDoubleClick(Selector selector=null)
Left double click the element found for Selector(s) defined in the UOW.
IAllowAllApplicationAndAllSelector ResizeWindow(int width, int height)
Resize the window last matched by WaitForWindow to the specified pixel dimensions.
Process Process
The current Windows process for the program run via Open. Only one program can be Opened/controlled ...
IAllowAllActionAndAnySelector SaveTo(GPALFile file)
Save the retrieved grid. The output format and delimiter are determined by the GPALFile (its extensio...
IAllowSelectorActionOrAnySelector AppendFrom(GPALDatabase inputDatabase)
Append the data from the database to the end of the input Selectors defined in the UOW One token (on...
IAllowSelectorActionOrAnySelector AppendFrom(GPALFile inputFile)
Append the data from the input file to the end of the input Selectors defined in the UOW One token (...
IAllowSelectorActionOrAnySelector AppendFrom(string textToUse)
Append the given text to the first input in the UOW. Should probably only be used with one selector a...
AutomationElement RootAutomationElement
The starting AutomationElement for the application. All xpath searches start from this AutomationElem...
IAllowSelectorActionOrAnySelector InsertFrom(GPALFile inputFile)
Insert the data from the file at the beginning of the text in the input Selector(s) defined in the UO...
IAllowAllExceptParameters Open([Directory()] string applicationPath)
Launch the application specified in the path. Find the root automation element and place into applic...
IAllowSelectorActionOrAnySelector ScrollHorizontalLarge(int iterations=1)
Scroll the element horizontally in a large increment. https://docs.microsoft.com/en-us/windows/win32...
IAllowPersistence WithPersistentSelector(Selector selector)
Add a persistent selector to the persistent UOW. Persistent selectors are always looked for whenever ...
IAllowSelectorActionOrAnySelector MoveTo(Selector selector=null)
Move the mouse cursor to the element found for Selector(s) defined in the UOW.
IAllowAllApplicationAndAllSelector Maximize
Maximize the application to full-screen.
IAllowPersistentCallBack PersistentCallIfNotFound(CallIfDelegate persistentCallIfNotFound)
Add a persistent CallIfNotFound handler to be called if any defined persistent selectors DO NOT find/...
IAllowSelectorActionOrAnySelector AppendFrom(IGPALGrid< string > inputGrid)
Append the data from the input grid to the end of the input Selectors defined in the UOW One token (...
void RemoveCallIfHandlerEverywhere(Application.CallIfDelegate func)
Helper method for GPAL applications to remove a handler from all UOWs. Envisioned to be used in a per...
IAllowWithHeaderOrFileActions GetGrid(ref IGPALGrid< string > returnGrid)
Retrieve the current selectors' data (or a table/grid control's data) into a GPALGrid....
IAllowWithHeaderOrFileActions WithHeader(string header)
Column header for exported data. Overrides selector names / table headers, in order.
IAllowSelectorActionOrAnySelector ScrollVerticalSmall(int iterations=1)
Scroll the element vertically in a small increment. https://docs.microsoft.com/en-us/windows/win32/a...
IAllowWithPagesAndGridActions WithNextPageButton(Selector selector)
The control to click to load the next page when paging through results.
void Close()
Close/terminate the application. This terminates the fluent interface and nothing can be chained to ...
static void TopProcess(Process process)
Brings the given process's main window to the foreground/top of the z-order, if it is not already.
static void FillInWithTokens(Application.Application application, List< IGPALGrid< string > > tokenList, WriteMode writeMode)
Enter tokens into form controls for each grid of tokens in the list.
File-side plumbing behind the fluent chain: writing a unit of work's data out in a delimited format,...
Definition FileHelper.cs:55
Class to define database usage. Currently only used for input from a table, sql or stored procedure....
IGPALGrid< string > Tokens
The rows/columns returned from the database. Tokens are loaded upon calling any [Append/FillIn/Inser...
Thrown where GPAL deliberately ends the workflow, such as a CallIf handler returning CallIfStatus....
GPAL File object instantied with GPAL.File Used to load tokens into a GPALGrid [rows/columns].
Definition GPALFile.cs:36
Everything starts here, all the GPAL controls and global settings using fluent syntax are here....
Definition GPAL.cs:49
static IAllowConverterInput Converter
New GPAL Convertor.
Definition GPAL.cs:560
static void PublishSimpleEvent(GPALEventType gPALEventType, string msg, dynamic gPALObject=null, Enums.GPALObjectType gPALObjectType=GPALObjectType.None, Exception ex=null)
Publish a message to either the information channel or exception channel (if exception passed in) Pub...
Definition GPAL.cs:2406
GPAL Selector used to locate Application and Browser elements. Instantiated with GPAL....
Definition Selector.cs:56
string SelectorPath
Returns the first defined selector path string.
Definition Selector.cs:754
string Name
The name you gave this selector, or one assigned by GPAL [selector1, selector2...] Used in Informati...
Definition Selector.cs:858
SelectorType SelectorType
Type of selector: A selector for an element, or literal data or a data function for dynamic data not ...
Everything revolves around the Unit of Work. A Unit of Work is defined as one or more selectors betw...
Definition UnitOfWork.cs:39
IAllowSelectorActionOrAnySelector SendString(string textToSend)
Simulates typing via hardware key events, one character at a time. Honors GPAL.TypingDelay (see GPAL....