cTaskDialog
cTaskDialog is the sequel to my previous TaskDialogIndirect project, mTaskDialog. This version adds support for all TaskDialogIndirect features, including the progress bar, timer feedback, updating the text and icons while the dialog is open, and events for all the notifications. It's also much easier to use.
What is TaskDialog?
TaskDialog, introduced in Windows Vista, is a massive upgrade to the MessageBox. While not as simple, it offers a huge array of features... custom icons, custom button text, command link style buttons, expanded info button, a footer, a checkbox for 'i read this' or 'don't show this again' type messages, radio buttons, hyperlinks, and more.
This project can be used to create a simple messagebox like the older ones, to an extremely complex dialog with all the features mentioned, even all at once!
Before using cTaskDialog
The TaskDialog was introduced with version 6.0 of the common controls, with Windows Vista. That means a manifest is required for your compiled application, and for VB6.exe in order to use it from the IDE. In addition, your project must start from Sub Main() and initialize the common controls before any forms are loaded. The sample project includes Sub Main(), and see
LaVolpe's Manifest Creator to create the manifests.
Setting Up cTaskDialog
Once you've got a project using the modern common controls, cTaskDialog is similar in use to a lot of other class modules, like the other common controls.
To initialize the class, put the following at the start of a form and in the Form_Load code:
Code:
Private WithEvents TaskDialog1 As cTaskDialog
Private Sub Form_Load()
Set TaskDialog1 = New cTaskDialog
End Sub
Now, you're all ready to begin using it. Let's start with a simple messagebox like we've seen before.
Creating this box is very straightforward. Unlike the previous incarnation, you don't have to worry about anything you're not using.
Code:
Private Sub Command2_Click()
With TaskDialog1
.MainInstruction = "This is a simple dialog."
.CommonButtons = TDCBF_YES_BUTTON Or TDCBF_NO_BUTTON
.IconMain = TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.ShowDialog
If .ResultMain = TD_YES Then
Label1.Caption = "Yes Yes Yes!"
ElseIf .ResultMain = TD_NO Then
Label1.Caption = "Nope. No. Non. Nein."
Else
Label1.Caption = "Cancelled."
End If
End With
End Sub
That's all it takes for a basic messagebox. The .Init() call resets the dialog. If you want to re-use all the previous settings and just change a couple things, it can be skipped.
Now that basic usage is covered, let's get down to what you really came for: advanced features!
Here's a few basic changes that make a much more fancy looking dialog:
Code:
.Init
.MainInstruction = "You're about to do something stupid."
.Content = "Are you absolutely sure you want to continue with this really bad idea?"
.CommonButtons = TDCBF_YES_BUTTON Or TDCBF_NO_BUTTON
.IconMain = TD_SHIELD_WARNING_ICON 'TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.ShowDialog
This produces the following:
![]()
The TaskDialog supports several special shield icons that create the colored bar uptop. If you use a regular icon, or a custom icon, it will look like the dialog on the right.
All the other text fields are added the same way, so I'm just going to skip over those. One thing to note, with expanded information set, the little expando button appears automatically when you set those fields and requires no additional code to operate; where it appears is set by a flag, which is described later. Also note that the major text fields can be changed while the dialog is open, just set it again the same way.
One of the big features is the ability to customize the text on the buttons. Due to limitations in VB, I've implemented them by using a .AddButton function. You can assign the button the same id as one of the regular buttons, or give it a unique id. Custom buttons can be removed with .ClearCustomButtons so a full Init() call isn't needed.
Code:
With TaskDialog1
.Init
.MainInstruction = "You're about to do something stupid."
.Content = "Are you absolutely sure you want to continue with this really bad idea?"
.IconMain = TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.AddCustomButton 101, "YeeHaw!"
.AddCustomButton 102, "NEVER!!!"
.AddCustomButton 103, "I dunno?"
.ShowDialog
Label1.Caption = "ID of button clicked: " & .ResultMain
End With
Note that we have removed the .CommonButtons. If you specify buttons there as well, they will appear in addition to your custom buttons.
Radio buttons are added the exact same way as common buttons; and the ID of the radio button selected is found in the .ResultRad property.
Code:
.AddRadioButton 110, "Let's do item 1"
.AddRadioButton 111, "Or maybe 2"
.AddRadioButton 112, "super secret option"
.ShowDialog
Label1.Caption = "ID of button clicked: " & .ResultMain
Label2.Caption = "ID of radio button selected: " & .ResultRad
One of the other biggies are Hyperlinks. These require a few additional steps. First, you need to include TDF_ENABLE_HYPERLINKS in the .Flags property. Then, you add in the hyperlink as normal html, but the url needs to be in quotes, so you'll need chr$(34). Then you'll need to use one of the events for the class. The most common thing to do is just execute the link, so that's what's shown here, but you could also get the URL back from the pointer and do something else with it. You must use ShellExecuteW, not ShellExecuteA (which is normally what just plain ShellExecute points to). The declare is included in the sample project.
Code:
With TaskDialog1
.Init
.MainInstruction = "Let's see some hyperlinking!"
.Content = "Where else to link to but <a href=" & Chr(34) & "http://www.microsoft.com" & Chr(34) & ">Microsoft.com</a>"
.IconMain = TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.CommonButtons = TDCBF_CLOSE_BUTTON
.Flags = TDF_ENABLE_HYPERLINKS
.ShowDialog
Label1.Caption = "ID of button clicked: " & .ResultMain
Label2.Caption = "ID of radio button selected: " & .ResultRad
End With
Private Sub TaskDialog1_HyperlinkClick(ByVal lPtr As Long)
Call ShellExecuteW(0, 0, lPtr, 0, 0, SW_SHOWNORMAL)
End Sub
Let's talk about custom icons. You can have a custom icon for both the main icon and the footer icon.
Thanks to the brilliant idea of Schmidt
over here, the option to specify an icon id from either shell32.dll or imageres.dll, the two main Windows icon libraries, has been added. This massively expands the icons you can show without having to provider an hIcon.
TDF_USE_SHELL32_MAINICONID, TDF_USE_SHELL32_FOOTERICONID
TDF_USE_IMAGERES_MAINICONID, TDF_USE_IMAGERES_FOOTERICONID
Simply specify if you're going to use one of those sources by adding the above to the flags (depending on which you're using for one; only one for main and one for footer), and set the icon to the index you want. NOTE: These do not run from 0-# of icons, so if your icon browser is telling you that, you need to use a different one, otherwise the dialog may display the wrong icon, or not show at all if the id doesn't exist.
Code:
With TaskDialog1
.Init
.MainInstruction = "Show me the icons!"
.Content = "Yeah, that's the stuff."
.Footer = "Got some footer icon action here too."
.Flags = TDF_USE_SHELL32_MAINICONID Or TDF_USE_IMAGERES_FOOTERICONID
.IconMain = 18
.IconFooter = 24
.Title = "cTaskDialog Project"
.CommonButtons = TDCBF_CLOSE_BUTTON
.ShowDialog
End With
You can also specify a truly custom icon from a .ico file on disk, in your resource file, or anywhere you can get an hIcon from.
The sample project uses a method I adapted from Leandro Ascierto's cMenuImage. It gets around VB's limitations on icons by adding them to the resource file as a custom resource, and not an icon. This way, you can include any size and any color depth and any number of them inside the .ico. Then, the ResIcontoHICON function will give you the hIcon you need for cTaskDialog. But remember, any other function returning an hIcon will work. Icons can also be updated while the dialog is open by another .IconMain= or .IconFooter= statement. You can use a standard icon for main and custom for footer, and vice versa, or both. When you're going to use a custom icon, you must include TDF_USE_HICON_MAIN/TDF_USE_HICON_FOOTER in the flags.
The icon size can't really be changed much; the main icon will be distorted but not larger if you give it a larger size, although you can make it a smaller size. The footer icon won't change at all.
Code:
With TaskDialog1
.Init
.MainInstruction = "What time is it?"
.Content = "Is is party time yet???"
.Footer = "Don't you love TaskDialogIndirect?"
.Flags = TDF_USE_HICON_MAIN Or TDF_USE_HICON_FOOTER
.IconMain = ResIconTohIcon("ICO_CLOCK", 32, 32)
.IconFooter = ResIconTohIcon("ICO_HEART", 16, 16)
.Title = "cTaskDialog Project"
.CommonButtons = TDCBF_CLOSE_BUTTON
.ShowDialog
Label1.Caption = "ID of button clicked: " & .ResultMain
End With
Due to the severe limitations on what icons can be put in a VB project res file in the actual icon group, I strongly recommend a different method. But if you want to try anyway I believe you can just set pszIcon.. to its ID, and not set any of the icon flags.
The last basic feature is the verification checkbox. Here's an example with that, and all the other text fields. Note also what happens in this example when no buttons are specified anywhere: the OK button appears by default.
Code:
With TaskDialog1
.Init
.MainInstruction = "Let's see all the basic fields."
.Content = "We can really fit in a lot of organized information now."
.Title = "cTaskDialog Project"
.Footer = "Have some footer text."
.CollapsedControlText = "Click here for some more info."
.ExpandedControlText = "Click again to hide that extra info."
.ExpandedInfo = "Here's a whole bunch more information you probably don't need."
.VerifyText = "Never ever show me this dialog again!"
.IconMain = TD_INFORMATION_ICON
.IconFooter = TD_ERROR_ICON
.ShowDialog
Label1.Caption = "ID of button clicked: " & .ResultMain
End With
One of the major stylistic differences are the CommandLink buttons. When using the Command Link style, the first line is considered the main text, and lines are made into sub-text. Note that the line is broken with vbLf only; not vbCrLf. vbCrLf results in the text not being smaller on Win7 x64, I haven't tested other systems but it should be the same.
With the custom button sample from above, these changes are made:
Code:
.Flags = TDF_USE_COMMAND_LINKS
.AddCustomButton 101, "YeeHaw!" & vbLf & "Put some additional information about the command here."
and that is what the dialog now looks like.
That covers all the basic functionality.
Advanced Features
The TaskDialog supports having a progress bar, both regular and marquee. To enable it, include the TDF_SHOW_PROGRESS_BAR or the TDF_SHOW_MARQUEE_PROGRESS_BAR flag (you can switch back and forth between them while the dialog is open if you want). Getting it to show up is the easy part, linking it to actual events in your program is where it gets a little tricky. There's some events that are provided that will help out...
TaskDialog_DialogCreated is triggered when the dialog is displayed, then all the buttons, the radio buttons, the expando button, the checkbox, and hyperlinks all have events when the user clicks them. In addition to that, TaskDialog_Timer is sent approximately every 200ms and includes a variable telling you how many ms has elapsed since the dialog appeared, or since it was reset with the .ResetTimer() call. The example shows a basic counter, but you can go further and enable/disable buttons and use hyperlinks to control things too.
Code:
Private bRunProgress As Boolean
Private lSecs As Long
With TaskDialog1
.Init
.MainInstruction = "You're about to do something stupid."
.Content = "Are you absolutely sure you want to continue with this really bad idea? I'll give you a minute to think about it."
.IconMain = TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.Footer = "Really, think about it."
.Flags = TDF_USE_COMMAND_LINKS Or TDF_SHOW_PROGRESS_BAR Or TDF_CALLBACK_TIMER
.AddCustomButton 101, "YeeHaw!" & vbLf & "Put some additional information about the command here."
.AddCustomButton 102, "NEVER!!!"
.AddCustomButton 103, "I dunno?"
.VerifyText = "Hold up!"
bRunProgress = True
.ShowDialog
Label1.Caption = "ID of button clicked: " & .ResultMain
End With
Private Sub TaskDialog1_DialogCreated(ByVal hWnd As Long)
Timer1.Interval = 1000
Timer1.Enabled = True
TaskDialog1.ProgressSetRange 0, 60
End Sub
Private Sub TaskDialog1_Timer(ByVal TimerValue As Long)
If lSecs > 60 Then
Timer1.Enabled = False
bRunProgress = False
Else
TaskDialog1.ProgressSetValue lSecs
TaskDialog1.Footer = "You've been thinking for " & lSecs & " seconds now..."
End If
End Sub
Private Sub TaskDialog1_VerificationClicked(ByVal Value As Long)
If Value = 1 Then
Timer1.Enabled = False
bRunProgress = False
Else
bRunProgress = True
Timer1.Enabled = True
End If
End Sub
Private Sub Timer1_Timer()
lSecs = lSecs + 1
End Sub
That's the basic feature set. The class allows an infinite number of customizations to take place from here.