I've created a dockpane that adds layers to a new map pane - this can be a long running process.
I think a progressbar would be nice for the user to see how things are going.
What is the ESRI way or best way of doing things like this? Put a ProgressBar control on the dockpane? Or have a ProgressDiaglog popup?
I see no concrete examples using the standard WPF ProgressBar control on ESRI GitHub, only the ProgressDialog - which is the main reason I am asking the question.
Solved! Go to Solution.
Regarding your question:
What is the ESRI way or best way of doing things like this? Put a ProgressBar control on the dockpane? Or have a ProgressDiaglog popup?
I guess this depends on the workflow that your add-in implements: if your workflow depends on the layers that you're loading i would recommend to use the ProgressDialog popup - since you have to wait for those layers loading before you do any further work. If you just load those layers for reference and the layers are not required (optional) for further work you can use a WPF ProgressBar on your dockpane.
Regarding your question:
I see no concrete examples using the standard WPF ProgressBar control on ESRI GitHub, only the ProgressDialog - which is the main reason I am asking the question.
The example you are looking for is here:
Since you likely update the UI from a background thread (i.e. QueuedTask.Run) make sure that you properly dispatch your progress update to the UI thread as shown here: arcgis-pro-sdk-community-samples/Overlay3DDockpaneViewModel.cs at 6d7d39d6169b59d34be63e415cdd88d561...
private void ProgressUpdate(string sText, int iProgressValue, int iProgressMax)
{
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
{
if (_iProgressMax != iProgressMax) MaxProgressValue = iProgressMax;
else if (_iProgressValue != iProgressValue)
{
ProgressValue = iProgressValue;
ProgressText = (iProgressValue == iProgressMax) ? "Done" : $@"{(iProgressValue * 100 / iProgressMax):0}%";
}
if (sText != _previousText) UpdateStatus = sText;
_previousText = sText;
_iProgressValue = iProgressValue;
_iProgressMax = iProgressMax;
}
else
{
ProApp.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
(Action)(() =>
{
if (_iProgressMax != iProgressMax) MaxProgressValue = iProgressMax;
else if (_iProgressValue != iProgressValue)
{
ProgressValue = iProgressValue;
ProgressText = (iProgressValue == iProgressMax) ? "Done" : $@"{(iProgressValue * 100 / iProgressMax):0}%";
}
if (sText != _previousText) UpdateStatus = sText;
_previousText = sText;
_iProgressValue = iProgressValue;
_iProgressMax = iProgressMax;
}));
}
}
And finally if you want to show progress in WPF and you don't have proper percentage completion feedback (0-100%) in your load routines you can also use the 'CircularAnimationControl' as shown in step 15 of this example:
Regarding your question:
What is the ESRI way or best way of doing things like this? Put a ProgressBar control on the dockpane? Or have a ProgressDiaglog popup?
I guess this depends on the workflow that your add-in implements: if your workflow depends on the layers that you're loading i would recommend to use the ProgressDialog popup - since you have to wait for those layers loading before you do any further work. If you just load those layers for reference and the layers are not required (optional) for further work you can use a WPF ProgressBar on your dockpane.
Regarding your question:
I see no concrete examples using the standard WPF ProgressBar control on ESRI GitHub, only the ProgressDialog - which is the main reason I am asking the question.
The example you are looking for is here:
Since you likely update the UI from a background thread (i.e. QueuedTask.Run) make sure that you properly dispatch your progress update to the UI thread as shown here: arcgis-pro-sdk-community-samples/Overlay3DDockpaneViewModel.cs at 6d7d39d6169b59d34be63e415cdd88d561...
private void ProgressUpdate(string sText, int iProgressValue, int iProgressMax)
{
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
{
if (_iProgressMax != iProgressMax) MaxProgressValue = iProgressMax;
else if (_iProgressValue != iProgressValue)
{
ProgressValue = iProgressValue;
ProgressText = (iProgressValue == iProgressMax) ? "Done" : $@"{(iProgressValue * 100 / iProgressMax):0}%";
}
if (sText != _previousText) UpdateStatus = sText;
_previousText = sText;
_iProgressValue = iProgressValue;
_iProgressMax = iProgressMax;
}
else
{
ProApp.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
(Action)(() =>
{
if (_iProgressMax != iProgressMax) MaxProgressValue = iProgressMax;
else if (_iProgressValue != iProgressValue)
{
ProgressValue = iProgressValue;
ProgressText = (iProgressValue == iProgressMax) ? "Done" : $@"{(iProgressValue * 100 / iProgressMax):0}%";
}
if (sText != _previousText) UpdateStatus = sText;
_previousText = sText;
_iProgressValue = iProgressValue;
_iProgressMax = iProgressMax;
}));
}
}
And finally if you want to show progress in WPF and you don't have proper percentage completion feedback (0-100%) in your load routines you can also use the 'CircularAnimationControl' as shown in step 15 of this example:
I'll be honest - it kind of blows my mind what's going on here!
Basically, if you ignore the logic Wolf has implemented to check whether to change progress value and text or not, the pattern Wolf is using (in WPF) is the same standard pattern as with old school WinForms for updating the UI from a non-UI thread. Namely:
Check if you are on the UI thread, if so, update progress directly. If not, invoke the update on the UI thread first.
So the pattern resolves to:
private void UpdateProgress(...) {
//whatever "update progress" means - change a counter, text, etc
NotifyPropertyChanged("ProgressValue");//Assuming something is bound to this
}
private void UpdateProgressSafely(...) {
//FrameworkApplication and System.Windows.Application are the same thing
if (FrameworkApplication.Current.Dispatcher.CheckAccess())
//On the UI - do update directly
UpdateProgress(...);
else {
//We are on a background thread, invoke on the UI
FrameworkApplication.Current.Dispatcher.BeginInvoke((Action)(() => UpdateProgress(...)));
Which is pretty much copy/paste ready. UpdateProgressSafely() can be called from any thread. The last thing probably worth some explanation is this intuitive piece of syntax:
//what is all that "(Action)(() => .....) for?
FrameworkApplication.Current.Dispatcher.BeginInvoke((Action)(() => UpdateProgress(...)));
Well, let's remove all that "(Action)(.....)" cast reducing the syntax to a more legible:
FrameworkApplication.Current.Dispatcher.BeginInvoke(() => UpdateProgress(...));
where "() => ..." is the standard anonymous delegate usage. However, intellisense will complain that:
It "Cannot convert lambda expression to type 'Delegate' because it is not a delegate type" and you will get a compiler error. BeginInvoke has numerous overloads and without the "Action" cast the compiler cannot resolve which overload we want it to use. We have to help it by providing an explicit "Action" that wraps the delegate "() => ..." bringing us back full circle to "BeginInvoke( (Action) (() => ....) )".
To simplify the syntax, you can also add your own utility method like this in your Module that can be used to call any method safely on the UI:
//in your module or some helper class
public void RunOnUIThread(Action action) {
if (FrameworkApplication.Current.Dispatcher.CheckAccess())
action();
else
FrameworkApplication.Current.Dispatcher.BeginInvoke(action);
}
//usage
await QueuedTask.Run(() => {
//some length process
//...
Module1.Current.RunOnUIThread(() => UpdateProgess(50, "working..."));