A button press arrives on the UI thread. If the handler drives a browser there, the window stops repainting for as long as the automation takes: a progress bar goes from empty to full in one step at the end, and a log control written to along the way shows nothing until the run is over. Attaching the handler with WithCallbackOffUIThread runs it on a thread of its own instead, and the press returns at once. Nothing else changes. Every GPAL control can be read and written from any thread, and GPAL marshals each call back to the UI thread itself, so the handler updates a label or a progress bar exactly as it would have on the UI thread. It moves every handler a control event uses, so a tree view selection or a data grid cell edit goes to its own thread just as a button press does. Two stay on the UI thread on purpose: a drag handler sets args.Effect and a key handler sets args.Handled, and the event reads the answer the moment the handler returns, so running one later would set it after it was wanted. GPAL says which it did with a WARNING when the control is built. A handler left on WithCallback that holds the window for more than two seconds is reported the same way, naming the seconds it held and the method to move it to.
// One method is the whole difference
GPALButton runButton = (GPALButton)GPAL
// This runs on its own thread
void OnRunPressed(object sender, EventArgs e)
{
runButton
progress
progress
ready
progress
runButton
}
Background handlers are queued per control rather than started as they arrive, so one runs at a time and they run in the order the events came in. That matters because these events are not all rare: TextChanged fires on every keystroke and ValueChanged on every click of a spinner, and a thread apiece would have several copies of the same handler racing to update the same label, with the last to finish winning rather than the last one asked. Queueing removes the race without putting the work back on the UI thread, so a type-ahead lookup still runs off the window and still answers in the order it was typed. A button pressed twice runs its workflow twice in a row rather than twice at once against the same browser.
Nothing is dropped, so a handler slower than the events arrive builds a queue that keeps draining after you stop typing or clicking. GPAL names it once five are waiting, saying how many are in line for which control. The usual cause is not fast typing but a handler writing to its own control: the write raises that control's event, the event queues the handler again, and it climbs from there. A handler doing the same thing on the UI thread is caught differently, as a callback entered while it was still running.
A workflow raised from a button is the form's guest, and a guest that fails should not take the house down with it. Windows Forms funnels anything thrown on the UI thread through one place, and GPAL subscribes to it when a form is shown. An exception that reaches there is published as an EXCEPTION event and the form stays standing, so whoever pressed the button can read the log tab and press it again rather than watch the program vanish. This is automatic. Show and ShowDialog both arrange it, and there is nothing to call.
// Nothing to wire up
GPALButton watchButton = GPAL
GPAL
// Send the run's events to the log control and they arrive from any thread
GPAL
A handler attached with WithCallback runs on the UI thread and is covered by this. A handler attached with WithCallbackOffUIThread is covered too, because GPAL owns the thread it put the handler on and publishes anything thrown there as an EXCEPTION event naming the control. One handler ending in an exception does not stop the ones queued behind it. Work you start yourself with Task.Run is on a thread GPAL never saw, so wrap that in a try and publish the failure yourself, or the run ends quietly with the button still disabled.
Reading is marshalled the same way writing is. Asking a checkbox whether it is checked or a combo box which index is selected works from a background thread and answers with what the control shows right now. A control that has gone, because the form was closed while the workflow was still running, is treated as gone rather than as an error: a write does nothing, a read answers with the last value GPAL was given for that control, and the workflow carries on. That holds for a form that closes in the middle of a call as well as before one, which is the case worth designing for, since a long automation and an impatient user meet fairly often.
// Read from the workflow thread, exactly as you would from the handler
void OnRunPressed(object sender, EventArgs e)
{
int engine = engineCombo
bool headless = headlessCheck
logBox
// If the user closes the form here, these calls stop mattering rather than throwing
RunTheWorkflow(engine, headless);
}
Showing off some plain text in these paragraphs eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Lorem ipsum dolor sit amet consectetur adipisicing elit. Quo veniam mollitia excepturi animi eum illum non libero sapiente provident assumenda, delectus voluptatum nobis sed dolorem adipisci laudantium incidunt. Error, ratione?
Here you can find different accents and emphasis sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
This is a link and how it could look like bestlinkinthebeautifulworld. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Here's just some classic bold text adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam notBoldSecondbestlinkinthebeautifulworld illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at adipisicing elit libero vel voluptas? Expedita, adipisicing facere dolores voluptatem ad ab rem assumenda soluta!
Other cuple of colors in case we want to emphasize several ways adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam adipisicing elit illo nostrum corporis at voluptatem libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta! Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quod veniam, quam ad expedita laborum sed at voluptates culpa ipsam ut vel. Ullam temporibus a mollitia quod aliquam ratione exercitationem nesciunt.
Lorem ipsum dolor sit amet consectetur adipisicing elit. Obcaecati, iste distinctio veritatis eligendi laboriosam illo nostrum corporis at libero vel voluptas? Expedita, facere dolores voluptatem ad ab rem assumenda soluta!
Lorem ipsum dolor sit amet consectetur adipisicing elit. Repudiandae quas consequuntur illo numquam assumenda autem exercitationem distinctio perspiciatis in natus. Eius dicta similique ipsam ipsa minima, nemo quae enim tempore.
GPAL
.CallIfNotFound(GenericCallIfNotFound)
.WithPublishToConsole();
//System.Drawing.Rectangle windowSize = new System.Drawing.Rectangle(10, 10, 1500, 1024);
// NOTE: we have to set browser = before we execute any steps
// this is due to the 'GenericCallIfNotFound' which might throw an exception, and BankScraper will not have the browser set when it calls scraper.Close()
// until the complete fluent line gets executed (meaning every step, meaning browser is not set until everything else succeeds)
browser = GPAL.Browser
.WithBrowserType(Enums.BrowserType.Chrome)
.WithProfileDataDirectory(ChromeProfileLocation)
.WithUseAutomationEngine(AutomationEngine.Selenium)
.WithWindowSize(new System.Drawing.Rectangle(0,0,1920,1080))
.ToGPALObject();