Monday, November 17, 2008

Using SpawnWaitDialog


A common request for installation programs is to display a small modal dialog box while a lengthy action is taking place (similar to SdShowMsg, for those familiar with InstallScript programming). The nearest built-in equivalent in MSI is a dialog box triggered with the SpawnWaitDialog control event, which displays a modal dialog box until a specified condition is true.

For example, suppose you have a button reading “Perform lengthy action…” on one of your dialog boxes, and you want a dialog box similar to the following to be displayed while the action is taking place:

Dialog to spawn

You can create this dialog using the Dialog and Control tables, the main point being to make sure the dialog (called SpawnMe in the Dialog table) has the modal attribute (value 2) set.

For this example, I’ve defined the lengthy action to be an asynchronous DLL action, with the following implementation.

#pragma comment(lib, "msi.lib")

#include
#include
#include

UINT __stdcall LengthyAction(MSIHANDLE hInstall)
{
Sleep(5000); // <-- put long-running code here

// set property that indicates the action is finished
MsiSetProperty(hInstall, TEXT("DoneWithLengthyAction"), TEXT("X"));
return ERROR_SUCCESS;
}

Naturally, the code that takes a long time will take the place of the Sleep function above. (Someone once asked me for a way to slow down an installation program, which is about the fifth-funniest installation-related question I’ve been asked.) As with all custom action DLLs, you’ll probably want to use a .def file or the like to suppress name decoration:

LIBRARY WaitThenSetProperty

EXPORTS
LengthyAction

A good place for this kind of helper DLL is in the Binary table. The action that calls the LengthyAction function from WaitThenSetProperty.dll has type 193, asynchronous DLL. Predictably, in the CustomAction table I’ve named the action CallLengthyAction.

To tie everything together, your “Perform lengthy action…” button will have two control events: one to launch the action, one to spawn the wait dialog box while the action is running. The first one should be familiar: a DoAction control event with target CallLengthyAction and condition 1.

The second control event is a SpawnWaitDialog control event, with argument SpawnMe (the name of the dialog to spawn) and condition DoneWithLengthyAction (a condition indicating when the dialog is no longer needed, using the property defined in the DLL action code).

When running the project, the initial dialog box might look as follows:

Before and after spawned dialog

When the user clicks the big button, and While the action is running (that is, while DoneWithLengthyAction is unset), the spawned dialog box appears over the normal dialog box as follows:

Spawned dialog

Once the action has finished (that is, once the condition DoneWithLengthyAction is true; that is, once the action sets the DoneWithLengthyAction property to any value), the spawned dialog vanishes, and the dialog box appears as it did in the first figure.

Closing remarks:

  • An obvious refinement is to disable the big button after the action has completed; otherwise, the button remains active, but the spawned dialog box will not appear again. (Easy pop quiz: Why not?)
  • As you know, actions in the UI sequence should not make system changes, but instead just query the target system. For actions in the Execute sequence, setting up ActionText and ActionData so that a description and ticks appear on the progress dialog is the recommended approach.

No comments: