top of page

TaskDialogIndirect ... as a progress bar

This post is one of a series providing implementation examples of Windows API Functions, Types, Enums and Consts using VBA. The code in this post can be used as-is, however, if you regularly (or even just occasionally) work with Windows API declarations in VBA, you may want to see the posts Automatically add Windows API declaration(s) and Using 'F1' to view Windows API web pages which explain some of the functionality that can be added to the VBE by VBE_Extras.

This is the fourth of four posts relating to TaskDialog and TaskDialogIndirect. This post provides an example of how to use the TaskDialogIndirect Windows API Function along with some associated Types, Enums and Consts. To make the code in this post work, you will also need the code from the first post in the series, TaskDialog and TaskDialogIndirect helper functionality.


TaskDialogIndirect provides functionality way beyond that of a normal MsgBox (and way beyond that of a TaskDialog ... for which see the TaskDialog ... MsgBox plus post). This post focuses on using TaskDialogIndirect as a progress bar ... to see TaskDialogIndirect used as a dialog designed for user-input, see post TaskDialogIndirect ... supercharged MsgBox.


While you can show TaskDialogIndirect with a progress bar combined with any other element, I don't think it makes sense to do so with, for example, radio buttons, command links or multiple buttons. As such, the Class that I show, below, wrapping TaskDialogIndirect allows only a sub-set of those elements to be added, namely (and in addition, obviously, to the progress bar itself and a single 'Cancel' button)


  • Both a 'main instruction' heading and 'content' within the body of the dialog

  • An icon in the body of the dialog (either a built-in icon or a custom icon)

  • A footer

  • You can set a specific width (or allow it to set its own width)

  • Flags to control other behaviour such as displaying right-to-left

  • A callback to handle the progress bar, clicks of hyperlinks, and to allow for custom handling for various other 'events'


Just before I share some demo code, if you want to follow-along with the examples in this post, then add a standard Module to your Project and add these declarations at the top. Each of them are commented to explain what they are used for ... and you'll see how they are used later on in this post.

Here are a couple of examples of TaskDialogIndirect with a progress bar along with example code of how to generate these dialogs using the CTaskDialogProgressBar Class Module which is provided below ... these Subs will go into the same standard Module as the code just above ... don't run them yet as you will need to add the CTaskDialogProgressBar Class Module and the TaskDialogCallbackProc Function before the code will compile.


A 'basic' example



A task dialog showing a progress bar

An example with an icon, a footer (with a hyperlink) ... and is wider



A task dialog showing a progress bar, an icon and a footer

The Class Module code


So this is the code that actually generates the TaskDialogIndirect with a progress bar ... you should add this to your Project as a Class Module named CTaskDialogProgressBar ...


The code is documented to explain what is going on. Some things to note are:


  • It includes a TODO that you will want to review ... you can identify TODO comments in your code using the Tasks command of VBE_Extras - see Sometimes it's just the little things - #4: Tasks (aka TODOs) for details

  • TaskDialog (and TaskDialogIndirect) require Common Controls version 6. If the code is running in a host application that is using Common Control version 5 then the Show() Function includes code to create and activate (and subsequently deactivate and release) an 'activation context' which temporarily loads a Common Controls version 6 manifest. See the TaskDialog and TaskDialogIndirect helper functionality post for further details.

  • To read more about why Type TASKDIALOGCONFIG is declared the way it is, and what Enum TDC is for, see the "The TASKDIALOGCONFIG and TASKDIALOG_BUTTON Types" and "So what's the 'union'?" sections of the TaskDialogIndirect ... supercharged MsgBox post


The callback


So this is where the real action happens when using TaskDialogIndirect with a progress bar ... the progress bar will do nothing without the callback. As with the two examples, above, you use the SetCallback member of CTaskDialogProgressBar to assign a Function as the callback.


The first thing to note is that the value passed in to SetCallback is a Long/Ptr being the address of the Function to be called as the callback. The UtilsTaskDialog.FunctionPointerToValue Function is used to create this Long/Ptr using VBA's AddressOf keyword.


It is essential that the Function passed in to UtilsTaskDialog.FunctionPointerToValue uses the exact 'signature' as is shown in the example code (below) for the TaskDialogCallbackProc Function ... the actual name of the Function and the names of the parameters are not important (I'd suggest you use the same parameter names ... the name of the Function would need to change if, for example, you wanted to have multiple callbacks in the same Project) but the value types of the parameters, the fact that they are all ByVal and the return type of the Function must all be as shown. Additionally, for AddressOf to work, the Function must be in a standard Module.


SetCallback also has a second, optional, parameter which is also a Long/Ptr. You can pass a value into this which is then passed through to the callback as its lpRefData parameter. This can be very useful, for example, if you wanted one callback Function to handle the behaviour for various different cases of showing TaskDialogIndirect with a progress bar. In the above examples, the mlLONG_RUNNING_PROCESS_ID constant is passed in, and is then checked for in TaskDialogCallbackProc.


This is my example implementation ... this code should go into the same standard Module as the two examples and the initial declarations (of mbIsLongRunningProcessStillRunning etc) ...

The key difference in the way this callback is called (compared to the callback in the TaskDialogIndirect ... supercharged MsgBox post), is that the TDF_CALLBACK_TIMER flag of the dwFlags member of TASKDIALOGCONFIG is set, meaning that "the task dialog's callback is called approximately every 200 milliseconds" with the msg parameter being set to TDN_TIMER. Any functionality can then be implemented to update the progress bar (and the wider dialog) as required.


In the above example (and I stress: this is only an example!) there are different ways of managing the 'long running process' (i.e. the work that is to be done while the progress bar is being shown) and managing the progress bar. In this example, we wait for the dialog to be created (the callback receives a TDN_CREATED msg) and then wait for the first timer (the callback receives a TDN_TIMER msg) and then kick off the 'long running process'.


In the above example, the Cancel button is also disabled and any clicks on the close button (ie the "red X") in the dialog's title bar are ignored by returning S_FALSE. Depending on your requirements, you might leave the Cancel button enabled to allow the user to exit from the 'long running process'.


Additionally, the code above shows how to handle clicks on hyperlinks.


So ... the final part of the puzzle is the LongRunningProcessWithProgressBarShowing method ... here it is ... again this goes in the same standard Module ...



This is where the logic of your 'long running process' would be. That process can, obviously, be anything at all ... whatever it is that your code is doing that takes enough such that you want to show the user a progress bar. In this example, all the code is doing is calling the Sleep Windows API Function to simulate a delay.


As well, the code is managing the progress bar ... calling the various ProgressBarXxx methods in the UtilsTaskDialog module to, for example, set whether the 'Cancel' button is enabled or disabled, set the amount of work done and work to be done, update the 'content' and 'main instruction' text in the dialog and, most importantly, to close the dialog when the work is completed.


Now you can try running the code of the two examples that you copied into the same standard Module at the start of this post.


Finally ...


The code here is intended to be an example of using the specific Windows API Functions / Types / Enums and Consts … it is not intended to be production-ready code … you may want to extend and re-work the code to be production ready, for example: the Show() Function actually returns a Boolean to indicate whether the dialog was successfully shown; this code has no error handling; the two TODOs should be reviewed.


bottom of page