Handle the busy status during async/long running operations
Radical provides an overlay adorner to handle busy/long running operations, that for simple scenarios just works as expected. Sometimes there are cases when we need to handle a much more complex scenario such as the following:
As a user I want to be able to start a long running operation, and if, after a certain amount of time, the operation is not completed I want to be able to cancel the operation itself.
Let us start drawing the UI for the above requirements:
Seems complex but it is not, we are using an ellipse element to create an animated spinning icon, pretty standard WPF stuff, the ellipse element, and a button is enclosed in a grid that is enclosed in the content of the BusyStatusManager, the button enabled status is bound to a property in the ViewModel. And then we have a simple button that triggers the long running operation in the ViewModel.
Q: Why the outer grid is wrapped in an AdornerDecorator element?
This a pretty complex WPF adorner requirement, the above code is contained in the Radical samples that are structured in the following manner:
The top most element is a Window;
Inside the Window there is a grid:
the left column contains the sample navigation menu;
the right column is a region (a Radical UI Composition region) that hosts the selected sample;
Attached to the Window there is a MainViewModel;
The MainViewModel exposes a property, SelectedSample, that is the currently selected sample view model that will be bound to the region content;
So by default the WPF visual tree will be something similar to:
Window <–> MainViewModel
AdornerDecorator (automatically added by WPF)
Grid
region container
Sample UserControl <–> SampleViewModel
At runtime the Radical adorner decorator engine looks for an AdornerDecorator to attach the adorner to, and finds the one child of the Window, now if you look at the ViewModel positions you can immediately notice that the data binding engine will look for properties on the wrong ViewModel, in this case, so we need to constraint the position of the adding another AdornerDecorator:
Window <–> MainViewModel
AdornerDecorator (automatically added by WPF)
Grid
region container
AdornerDecorator
Sample UserControl <–> SampleViewModel
From the view model point of view things are not so complicated as they appear in at the first look:
We have a bunch of properties to control the status of the UI and 2 methods to control the async work, the Worker class is just a wrapper around the Task API to simplify the threshold elapsed management that is done using a Timer.
What happens, when the user pushes the “button”, is that:
A long running job is started (15”);
The “please wait…” UI adorner is displayed;
After 5” the “Cancel” button is activated because the threshold to wait for the task to complete is exhausted;
The long running job continues;
if the user presses the “cancel” button a cancel request is injected into the worker;
otherwise the long running task is allowed to complete;