-
Notifications
You must be signed in to change notification settings - Fork 1
ProgressHandler Class
Bootstrapper.Phases.ProgressHandler
The UI displays progress using a progress bar with two text blocks below it for a package name and a message. The view model that manages this display exposes the ProcessProgressReport
method. A ProgressReport
contains the three pieces of data that the UI can display. Additionally, it treats null
different from String.Empty
. A null value can be used to prevent the display from changing a data point, while String.Empty
can be used to explicitly clear it.
From Bootstrapper.ViewModels.ProgressViewModel
public void ProcessProgressReport(ProgressReport report) { Progress = report.Progress; if (report.Message != null) Message = report.Message; if (report.PackageName != null) Package = report.PackageName; }
Earlier, when discussing the UI facade, it was mentioned that a Progress<T>
instance would be used to dispatch reports to this method. But, only if there's a UI being displayed. Otherwise, the ProgressReporter
property is left null.
From Bootstrapper.Models.WpfFacade Initialize method
if (!IsUiShown) return; ... ProgressReporter = new Progress<ProgressReport>(r => _shellVm.ProgressVm.ProcessProgressReport(r));
We have the UI facade that can dispatch reports and a view model that can receive them. A helper method to report to the UI will be useful. Using optional parameters that default to null will make it easier to use.
private void ReportProgress(string message = null, string packageId = null)
{
if (_model.UiFacade.ProgressReporter != null)
{
var report = new ProgressReport
{
Message = message,
Progress = CalculateProgress()
};
if (packageId != null)
report.PackageName = _model.State.GetPackageName(packageId);
_model.UiFacade.ProgressReporter.Report(report);
}
}
Plan is usually fast and WiX doesn't provide progress info for it.
We can send a message that the plan phase has started.
var action = _model.State.PlannedAction.ToString().ToLower();
ReportProgress($"Planning {action}");
The apply phase is performed in two stages.
- The packages to install are either extracted from the bundle or downloaded from a web service. Either way, the packages are cached locally so they can be executed.
- Windows Installer executes using the data in each package.
WiX reports progress for acquiring the cache and Windows Installer reports progress while executing against each package. This means the progress percentage can go from 0% to 100% a couple of times if both of these stages are performed. To accommodate this, we'll define some fields to help calculate progress.
- We need an object to lock because atomic calculation will require multiple fields.
- We need to know how many stages will be performed and need to track the progress of both stages.
private readonly object _progressLock = new object();
private int _progressStages;
private int _cacheProgress;
private int _executeProgress;
The Apply phase starts with OnApplyBegin
. The event argument has a property indicating how many stages will be performed. We should save this to accurately report progress.
lock (_progressLock)
_progressStages = e.PhaseCount;
We can also report that the apply phase has started.
ReportProgress("Applying changes");
There are several events involved in caching and reporting progress.
- CacheAcquireProgress
- CachePayloadExtractProgress
- CacheVerifyProgress
- CacheContainerOrPayloadVerifyProgress
The handlers for each are nearly identical, only differing in in the message they send. Uncle Bob say what? Well, this isn't a tutorial on clean code. 😃
lock (_progressLock)
_cacheProgress = e.OverallPercentage;
ReportProgress("Retrieving", e.PackageOrContainerId);
When caching is finished, we want to set this stage's progress to 100%.
lock (_progressLock)
_cacheProgress = 100;
ReportProgress();
Execution of the packages begins after the packages are cached.
Here, we're going to report progress when running embedded. For that we need to send the percentage of the executing package as well as the overall percentage. The overall percentage is what we've been calculating for the UI, so let's start with doing that calculation. While we're at it, we'll save the execution stage's progress.
int overallProgress;
lock (_progressLock)
{
_executeProgress = e.OverallPercentage;
overallProgress = CalculateProgress();
}
Next, we'll report progress to the UI as before, but we'll also send a message to our BA host if we're running embedded.
ReportProgress(null, e.PackageId);
if (_model.State.Display == Display.Embedded)
_model.Engine.SendEmbeddedProgress(e.ProgressPercentage, overallProgress);
When completing each package, we want to clear the display of any messages sent to the UI.
ReportProgress(string.Empty, string.Empty);
← Previous: CancelHandler Class || Next: ShellViewModel Class →