cTaskDialog 1.4 UniversalWhat is TaskDialog?
Say goodbye to unsightly, primitive messageboxes forever with the new enhanced TaskDialog.
Updated released 08 Oct 2023
Attachment 178039
Download Attachment: Attachment 190043
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. VB6 can use many modern Wndows features, and the TaskDialog is one of the most useful.
**Recently Added Features**
Project Update - 17 Jan 2024
Additional undocumented Common Buttons added (abort, retry, ignore, and continue).
Project Update - 08 Oct 2023
Updating this post to Version 1.3 Universal. The features are the same with some bug fixes, but this version universally supports: VB6, VBA6, VBA7 32bit, VBA7 64bit, twinBASIC 32bit, and twinBASIC 64bit..
IMPORTANT: For compatibility, this version no longer uses self-subclassing, and like earlier versions, once again requires mTDHelper.bas in all projects. (mTDSample.bas is still only for the demo form).
Project Update - 21 March 2020
This version has some important improvements to functionality, including a very significant expansion of multi-page functionality, and some bug fixes, one of them critical.
R2: A quick same-night update was made to fix an issue where the footer icon would go blank if it was changed during runtime if it was part of a mutli-page dialog.
Full Details
Project Update - 30 August 2019
Class is now self-contained, no longer requires a separate .bas file (though the sample still has one for resources used by the sample project-- but it's not for redistribution).
Several bug fixes including missing flag and flag conflicts.
Left-right-center alignment for controls.
Major Update - 01 March 2017
Logo images, dropdown buttons, autoclose, DPI sensitivity, and plenty of improvements have brought cTaskDialog to its 1.0 release.
See this post for more details, more pictures, and sample code!
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. The sample project includes Sub Main(), and see LaVolpe's Manifest Creator to create the manifests.
Setting Up cTaskDialog
Your project must include cTaskDialog.cls and mTDHelper.bas
mTDSample.bas is used only for the sample project.
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:
In Brief: The simple TaskDialog()Code:Private WithEvents TaskDialog1 As cTaskDialog
Private Sub Form_Load()
Set TaskDialog1 = New cTaskDialog
End Sub
TaskDialogIndirect is the real rockstar, but the class supports the regular TaskDialog as well.
Function SimpleDialog(sMessage As String, Optional dwBtn As TDBUTTONS = TDCBF_OK_BUTTON, Optional sTitle As String, Optional sMainText As String, Optional dwIco As TDICONS, Optional hwndOwner As Long, Optional hInst As Long) As TDBUTTONS
The first 3 arguments are the same order as MsgBox, so it's a very quick replacement with the only requirement being to change the buttons argument. Usage is very simple:
As with other implementations, you can specify the index of an icon in the resource file for your app, or another app/dll by changing the hInstance.Code:Dim td As TDBUTTONS
td = TaskDialog1.SimpleDialog("Is TaskDialogIndirect going to be better than this?", TDCBF_YES_BUTTON, App.Title, "This is regular old TaskDialog", TD_SHIELD_GRAY_ICON, Me.hWnd, App.hInstance)
Label1.Caption = "ID of button clicked: " & bt
But that's not really why we're here. Let's begin using the main ShowDialog() function that uses TaskDialogIndirect.
We'll start simple, with a box 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.
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.Code: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
If you put your text in .Content and nothing in .MainInstruction, it will look like the dialog on the bottom.Attachment 178040
Attachment 178041
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:
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 bottom.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
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.https://www.vbforums.com/images/ieimages/2014/09/6.gif
https://www.vbforums.com/images/ieimages/2014/09/7.gif
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.
Note that we have removed the .CommonButtons. If you specify buttons there as well, they will appear in addition to your custom buttons. We'll get to that hIcon option when we cover custom icons.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
https://www.vbforums.com/images/ieimages/2014/09/3.jpg
Radio buttons are added the exact same way as custom 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
https://www.vbforums.com/images/ieimages/2014/09/4.jpg
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. 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=""http://www.microsoft.com"">Microsoft.com</a>"
.IconMain = TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.CommonButtons = TDCBF_CLOSE_BUTTON
.Flags = TDF_ENABLE_HYPERLINKS
.ShowDialog
End With
Private Sub TaskDialog1_HyperlinkClick(ByVal lPtr As Long)
Call ShellExecuteW(0, 0, lPtr, 0, 0, SW_SHOWNORMAL)
End Sub
https://www.vbforums.com/images/ieimages/2014/09/5.jpg
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 expands the icons you can show without using an hIcon.
TDF_USE_SHELL32_ICONID, TDF_USE_IMAGERES_ICONID
Simply specify if you're going to use one of those sources by adding the above to the flags, and set the icon to the index you want. Only one can be used, so both header and footer index will come from the chosen DLL. Standard icons (the TDICONS group) work regardless of the source of other icons. 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_ICONID
.IconMain = 18
.IconFooter = 35
.Title = "cTaskDialog Project"
.CommonButtons = TDCBF_CLOSE_BUTTON
.ShowDialog
End With
https://www.vbforums.com/images/ieimages/2014/09/6.jpg
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- allowing any size, color depth, and # of entries. Then, the ResIcontoHICON function gives the hIcon. Also any other function returning an hIcon will work. Icons can 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, by adding TDF_USE_HICON_MAIN/TDF_USE_HICON_FOOTER to the flags. If you create an hIcon, don't forget to destroy it when you're done.
The icon size can't 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.
Due to the limitations on what icons can be put in a VB project res file icon group, a different method is needed. If you do want to use those anyway, add .hInst = App.hInstanceCode:Dim hIconM As Long, hIconF As Long
hIconM = ResIconTohIcon("ICO_CLOCK", 32, 32)
hIconF = ResIconTohIcon("ICO_HEART", 16, 16)
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 = hIconM
.IconFooter = hIconF
.Title = "cTaskDialog Project"
.CommonButtons = TDCBF_CLOSE_BUTTON
.ShowDialog
End With
Call DestroyIcon(hIconM)
Call DestroyIcon(hIconF)
Shell32/imageres and fully custom icons are supported for buttons as well. When you use .AddCustomButton and .SetCommonButtonIcon, you can specify an hIcon, or an index and add TDF_USE_<shell32/imageres>_ICONID_BUTTON.
Code:hIcon1 = ResIconToHICON("ICO_CLOCK", 16, 16)
.Flags = TDF_USE_SHELL32_ICONID_BUTTON Or TDF_USE_COMMAND_LINKS
.CommonButtons = TDCBF_CLOSE_BUTTON Or TDCBF_NO_BUTTON
.AddCustomButton 103, "Button 1"
.AddCustomButton 102, "Button 2", hIcon2
.SetWindowsButtonIconSize ICO_32
.SetCommonButtonIcon TDCBF_NO_BUTTON, hIcon1
https://www.vbforums.com/images/ieimages/2014/09/1.gif
https://www.vbforums.com/images/ieimages/2014/09/2.gif
The last basic feature is the verification checkbox. Here's an example with that, and all the other text fields.
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
https://www.vbforums.com/images/ieimages/2014/09/3.gif
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."
https://www.vbforums.com/images/ieimages/2014/09/10.jpg 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
End With
Private Sub TaskDialog1_DialogCreated(ByVal hWnd As Long)
If bRunProgress Then
Timer1.Interval = 1000
Timer1.Enabled = True
TaskDialog1.ProgressSetRange 0, 60
End If
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
https://www.vbforums.com/images/ieimages/2014/09/4.gif
Microsoft didn't document it very well, but it did provide a mechanism to have multiple pages. With multiple pages, you can have a command that takes the user forward to the next dialog in the sequence, or back to the previous one. Setting this up is a little complicated, but not too bad.
1) Create a new cTaskDialog and initialize the same as any other dialog.
2) Fill out all the configuration info you need before calling the first dialog.
3) When you add a button to a dialog to go forward or back, you must also use .SetButtonHold, otherwise the dialog closes when the button is clicked.
4) Add code to call .NavigatePage in the .ButtonClick event.
5) The new page will send a .Navigated event; this is instead of .DialogCreated. Also note that only the dialog that exits and returns a final result will send the .DialogDestroyed event.
Code:Private WithEvents TaskDialog2 As cTaskDialog
Set TaskDialog2 = New cTaskDialog
With TaskDialog2
.Init
.Content = "Here's a whole new dialog with all the options."
.CommonButtons = TDCBF_OK_BUTTON
.IconMain = TD_SHIELD_OK_ICON
.Title = "cTaskDialog Project - Page 2"
End With
With TaskDialog1
.Init
.MainInstruction = "You can now have multiple pages."
.Content = "Click Next Page to continue."
.Flags = TDF_USE_COMMAND_LINKS
.AddCustomButton 200, "Next Page" & vbLf & "Click here to continue to the next TaskDialog"
.CommonButtons = TDCBF_YES_BUTTON Or TDCBF_NO_BUTTON
.IconMain = TD_SHIELD_WARNING_ICON
.SetButtonHold 200
.Title = "cTaskDialog Project - Page 1"
.ShowDialog
End With
Private Sub TaskDialog1_ButtonClick(ByVal ButtonID As Long)
If ButtonID = 200 Then
TaskDialog1.NavigatePage TaskDialog2
End If
End Sub
https://www.vbforums.com/images/ieimages/2014/09/5.gif
Here are the progress bar calls:
.ProgressSetRange(Min,Max) Sets the range .ProgressSetState(state) Sets the progress bar state; ePBST_NORMAL for a green bar, ePBST_PAUSED for yellow, ePBST_ERROR for red. .ProgressSetValue(value) Sets the value of the progress bar. .ProgressSetType(value) Allows switching between a regular bar and marquee style bar. 0 for regular, 1 for marquee. .ProgressStartMarquee([speed]) Starts the marquee; the bar must be set to marquee style, either originally, or changed to it, in order for this to work. If speed is not specified, the default of 30 is used. .ProgressStopMarquee() Stops the marquee.
Here's the code for the everything box at the top; note how the use of the cancel button adds the X and icon to the title bar (those can appear without a cancel button using the TDF_ALLOW_DIALOG_CANCELLATION flag).
Code:
Private bRunMarquee As Boolean
Dim hIconM As Long, hIconF As Long
hIconM = ResIconTohIcon("ICO_CLOCK", 32, 32)
hIconF = ResIconTohIcon("ICO_HEART", 16, 16)
With TaskDialog1
.Init
.MainInstruction = "Let's see it all!"
.Content = "Lots and lots of features are possible, thanks <a href=" & Chr(34) & "http://www.microsoft.com" & Chr(34) & ">Microsoft</a>"
.IconMain = hIconM
.IconFooter = hIconF
.Flags = TDF_USE_HICON_MAIN Or TDF_USE_HICON_FOOTER Or TDF_ENABLE_HYPERLINKS Or TDF_USE_COMMAND_LINKS Or TDF_SHOW_MARQUEE_PROGRESS_BAR Or TDF_CAN_BE_MINIMIZED
.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!"
.CommonButtons = TDCBF_RETRY_BUTTON Or TDCBF_CANCEL_BUTTON Or TDCBF_CLOSE_BUTTON Or TDCBF_YES_BUTTON
.AddCustomButton 101, "YeeHaw!" & vbLf & "Some more information describing YeeHaw"
.AddCustomButton 102, "NEVER!!!"
.AddCustomButton 103, "I dunno?" & vbLf & "Or do i?"
.AddRadioButton 110, "Let's do item 1"
.AddRadioButton 111, "Or maybe 2"
.AddRadioButton 112, "super secret option"
.EnableRadioButton 112, 0
.EnableButton 102, 0
.SetButtonElevated TD_RETRY, 1
bRunMarquee = True
.ShowDialog
bRunMarquee = False
End With
Private Sub TaskDialog1_DialogCreated(ByVal hWnd As Long)
If bRunProgress Then
Timer1.Enabled = True
TaskDialog1.ProgressSetRange 0, 60
End If
If bRunMarquee Then
TaskDialog1.ProgressStartMarquee
End If
End Sub
That's the basic feature set. The class allows an infinite number of customizations to take place from here.
Download Attachment: Attachment 190043
(continued in next post)
[
With VBA64 now fully working, I'm updating this post to v1.3.8 Universal. This attachment on this post contains the VB6 project and form sample updated to work with the universal version (LongPtr), as well as the twinBASIC demo project.
IMPORTANT: For compatibility, this version no longer uses self-subclassing, and like earlier versions, once again requires mTDHelper.bas in all projects. (mTDSample.bas is still only for the demo form).