# VBForums CodeBank > CodeBank - Visual Basic 6 and earlier >  [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs

## fafalone

*cTaskDialog 1.2*
Say goodbye to unsightly, primitive messageboxes forever with the new enhanced TaskDialog.
_Updated released 21 March 2020_


*Download Attachment:* cTaskDialog12-R2.zip



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 windows features, and the TaskDialog is one of the most useful

***Recently Added Features***
*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!


*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*

Your project must include cTaskDialog.cls. No other files are required for use in your own projects.
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:


```
Private WithEvents TaskDialog1 As cTaskDialog

Private Sub Form_Load()
Set TaskDialog1 = New cTaskDialog
End Sub
```

*In Brief: The simple TaskDialog()*
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:


```
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
```

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.

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.


```
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
```

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.
If you put your text in .Content and nothing in .MainInstruction, it will look like the dialog on the bottom.



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:



```
    .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
```

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.

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.



```
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. We'll get to that hIcon option when we cover custom icons.


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.



```
    .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. 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.



```
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
```




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.


```
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
```




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.


```
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)
```

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.hInstance

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. 


```
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
```






The last basic feature is the verification checkbox. Here's an example with that, and all the other text fields.


```
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:



```
    .Flags = TDF_USE_COMMAND_LINKS
    .AddCustomButton 101, "YeeHaw!" & vbLf & "Put some additional information about the command here."
```




*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.



```

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
```





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.



```
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
```






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).


```

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.


(continued in next post)
*Download Attachment:* cTaskDialog12-R2.zip



[
This project now has a Universal version that works in twinBASIC x86/x64 and VBA7 x84/x64, in addition to VB6 and VBA6.

For now I'm leaving the version in this thread up, but the next feature update will use the universal codebase (you could drop in cTaskDialog.cls and modTDHelper from the universal codebase into the VB6 version in this thread too).

[twinBASIC/VB6/VBA7] TaskDialogIndirect: cTaskDialog universal implementation x86/x64 

There's a neat workaround for the fact alignment issues make it difficult to call in VBA7x64  :Smilie:

----------


## fafalone

(continued, too long)
*CUSTOMIZATIONS*
Below covers the additional custom controls. 
See the full change logs and other upgrades for Version 0.8, and for Version 1.0.
Starting in v0.7, I began adding additional controls. First was the input box.
First, a basic input box with the default positioning:


```
With TaskDialog1
    .Init
    .MainInstruction = "Hello World"
    .Content = "Input Required"
    .Flags = TDF_INPUT_BOX
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .IconMain = TD_INFORMATION_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog

    Label5.Caption = .ResultInput
    If .ResultMain = TD_OK Then
        Label1.Caption = "Yes Yes Yes!"
    Else
        Label1.Caption = "Cancelled."
    End If
End With
```




We can also position it next to the buttons:


```
With TaskDialog1
    .Init
    .MainInstruction = "Input Required"
    .Content = "Tell me what I want to know!" & vbCrLf '& vbCrLf
    .Flags = TDF_INPUT_BOX
    .InputAlign = TDIBA_Buttons
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .IconMain = TD_INFORMATION_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog
```




The last placement option is to use it as footer input. It's designed to use the normal footer icon, but have a textbox instead of a label:


```
With TaskDialog1
    .Init
    .Content = "Something somesuch hows-it what-eva" '& vbCrLf
    .Flags = TDF_INPUT_BOX 'Or TDF_USE_SHELL32_ICONID
    .InputAlign = TDIBA_Footer
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .IconFooter = TD_INFORMATION_ICON
    .VerifyText = "Check here if you want to provide extra info below:"
    .Title = "cTaskDialog Project"
    .Footer = "$input"
    .ParenthWnd = Me.hWnd
    .ShowDialog
```




Here's a practical example showing it with default text (which is pre-selected automatically):


```
With TaskDialog1
    .Init
    .MainInstruction = "Duplicates"
    .Content = "If you want to exclude an Artists name from the search:" & vbCrLf & vbCrLf
    .Flags = TDF_INPUT_BOX Or TDF_VERIFICATION_FLAG_CHECKED 'Or TDF_USE_SHELL32_ICONID
    .AddCustomButton 100, "Continue"
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .IconMain = TD_SHIELD_ICON
    .Title = "cTaskDialog Project"
    .InputText = "Enter Artist name here."
    .VerifyText = "Exclude Jingles"
    .ParenthWnd = Me.hWnd
    .ShowDialog
```

Note the spacing; there's always a single line break like the first one (appended if not present), but using a double-break relaxes the spacing a bit.


If CommandLinks are being used, the default alignment (content) places the textbox between the content and the first commandlink:


```
With TaskDialog1
    .Init
    .MainInstruction = "You're about to do something stupid."
    .Content = "First, tell me why?"
    .IconMain = TD_INFORMATION_ICON
    .Title = "cTaskDialog Project"
    .Flags = TDF_USE_COMMAND_LINKS Or TDF_INPUT_BOX
    .AddCustomButton 101, "YeeHaw!" & vbLf & "Put some additional information about the command here."
    .AddCustomButton 102, "NEVER!!!"
    .AddCustomButton 103, "I dunno?"
    
    .ShowDialog
```

There's also compatibility with the expanded-info control in the default positioning. The position of the box is automatically adjusted when the expando control is clicked.


Finally here's a detailed practical application that shows some more features. The textbox can be set as a password box. We can then combine that with a button hold, so now when enter or ok is pressed, we check the input and only let OK execute if it's a match- while cancel still closes it right away.


```
Set TaskDialogPW = New cTaskDialog
With TaskDialogPW
    .Init
    .MainInstruction = "Authorization Required"
    .Content = "The password is: password"
    .Flags = TDF_INPUT_BOX
    .InputIsPassword = True
    .InputAlign = TDIBA_Buttons
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .SetButtonElevated TD_OK, 1
    .SetButtonHold TD_OK
    .Footer = "Enter your password then press OK to continue."
    .IconFooter = TD_INFORMATION_ICON
    .IconMain = TD_SHIELD_ERROR_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog
```

Then we need to handle the ok button click to check the password:


```
Private Sub TaskDialogPW_ButtonClick(ByVal ButtonID As Long)
If ButtonID = TD_OK Then
    If TaskDialogPW.InputText = "password" Then
        TaskDialogPW.CloseDialog
    Else
        TaskDialogPW.Footer = "Wrong password, please try again."
        TaskDialogPW.IconFooter = TD_ERROR_ICON
    End If
End If
End Sub
```






After the success of the Input Box in 0.7, version 0.8 of cTaskDialog adds even more useful controls added by custom flag. There's now 4 types of controls:
TDF_INPUT_BOX The textbox from 0.7
TDF_COMBO_BOX A combo box that can either be an editable one or a dropdown list. It's a ComboBoxEx (ImageCombo), so it accepts an imagelist in the form of an HIMAGELIST.
TDF_DATETIME Shows a datetime control; either a single one for date or time, or two controls for date AND time. 
TDF_SLIDER A standard slider control with detailed options for the range and ticks.

Following in the tradition of the existing control options, you can add one of each of these (up to 3) in the same 3 areas where the inputbox could be placed: in the main content area, next to the buttons (replaces the expando and/or verify controls if placed here), or as the footer (the footer icon shows and is properly aligned, but old footer text is covered). Thanks to some painstaking calculations and testing, all these controls are additional options: you can use any number and combination of the built-in controls along with the new custom ones, including the expando control, and a space for them is automatically created. The creation and usage of the new controls follows the exact same easy format of the rest of the class.

Here's some selected samples; the attached project contains many additional ones showing the different options.



```
himlSys = GetSystemImagelist(SHGFI_SMALLICON) 'any image list will do; make your own with ImageList_Create or IImageList
With TaskDialog3
    .Init
    .MainInstruction = "Duplicates"
    .Content = "If you want to exclude an Artists name from the search:"
    .Flags = TDF_VERIFICATION_FLAG_CHECKED Or TDF_COMBO_BOX
    .AddCustomButton 100, "Continue"
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .IconMain = TD_SHIELD_ICON
    .Title = "cTaskDialog Project"
    .ComboCueBanner = "Cue Banner Text"
    .ComboSetInitialState "", 5
'    .ComboSetInitialItem 1
    .ComboImageList = himlSys
    .ComboAddItem "Item 1", 6
    .ComboAddItem "Item 2", 7
    .ComboAddItem "Item 3", 8
    .VerifyText = "Exclude Jingles"
    .ParenthWnd = Me.hWnd
    .ShowDialog

    Label3.Caption = "Checked? " & .ResultVerify
    Label7.Caption = .ResultComboText
    Label9.Caption = .ResultComboIndex
    If .ResultMain = 100 Then
        Label1.Caption = "Continue!"
    Else
        Label1.Caption = "Cancelled."
    End If
End With
```




Here we add pre-selected users to our password demo. The partial code below shows how dual custom controls are used:

```
    .Flags = TDF_INPUT_BOX Or TDF_COMBO_BOX
    .ComboStyle = cbtDropdownList
    .InputIsPassword = True
    .InputAlign = TDIBA_Buttons
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .SetButtonElevated TD_OK, 1
    .SetButtonHold TD_OK
    .ComboAlign = TDIBA_Content
    .ComboSetInitialItem 0
    .ComboImageList = himlSys
    .ComboAddItem "User 1", 6
    .ComboAddItem "User 2", 7
    .ComboAddItem "User 3", 8
    .Footer = "Enter your password then press OK to continue."
    .IconFooter = TD_INFORMATION_ICON
    .IconMain = TD_SHIELD_ERROR_ICON
```




The most basic Date control. This can also be a time control.

```
    .Init
    .MainInstruction = "Hello World"
    .Content = "Pick a day, any day" & vbCrLf & vbCrLf
    .Flags = TDF_DATETIME
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .IconMain = TD_INFORMATION_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog

    Label11.Caption = .ResultDateTime
```




Many common options are supported. This example shows combining the new controls with the built-in ones, and setting a limited range in the date and time controls. When the two controls are used, both the chosen date and time are represented by .ResultDateTime.

```
Dim dTimeMin As Date, dTimeMax As Date

dTimeMin = DateSerial(Year(Now), Month(Now), Day(Now)) + TimeSerial(13, 0, 0)
dTimeMax = DateAdd("d", 7, dTimeMin)
dTimeMax = DateAdd("h", 4, dTimeMax)

With TaskDialog1
    .Init
    .MainInstruction = "Date Ranges"
    .Content = "Pick a time, limited to sometime in the next 7 days, between 1pm and 6pm" & vbCrLf & vbCrLf
    .Flags = TDF_DATETIME Or TDF_USE_COMMAND_LINKS
    .DateTimeType = dttDateTime
    .DateTimeAlign = TDIBA_Content
    .DateTimeSetRange True, True, dTimeMin, dTimeMax
    .DateTimeSetInitial dTimeMin
    .AddCustomButton 101, "Set Date" & vbLf & "Apply this date and time to whatever it is you're doing."
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .IconMain = TD_INFORMATION_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog

    Label11.Caption = .ResultDateTime
```




Here's a highly customized slider combined with showing the auto-positioning that allows the expando control to be used, even with built-in controls further upping the complexity.

```
    .Init
    .MainInstruction = "Sliding on down"
    .Content = "Pick a number"
    .Flags = TDF_SLIDER Or TDF_USE_COMMAND_LINKS
    .SliderSetRange 0, 100, 10
    .SliderSetChangeValues 10, 20
    .SliderTickStyle = SldTickStyleBoth
    .SliderValue = 50
    .SliderAlign = TDIBA_Content
    .ExpandedControlText = "ExpandMe"
    .ExpandedInfo = "Expanded"
    .AddCustomButton 100, "CommandLink"
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .IconMain = TD_INFORMATION_ICON
```




Finally, here's an example of using 3 of the new controls together in combination with two of the built-in controls, as well as the option to replace the shield icon with one from shell32.dll, while maintaining the gradient background.

```
himlSys = GetSystemImagelist(SHGFI_SMALLICON)
Dim hIconF As Long
hIconF = IconToHICON(LoadResData("ICO_CLIP", "CUSTOM"), 16, 16)
With TaskDialog1
    .Init
    .MainInstruction = "Schedule Event"
    .Content = "Pick action to schedule. Provide a date, and optionally a specific time. You can also set a name below."
    .Flags = TDF_DATETIME Or TDF_INPUT_BOX Or TDF_COMBO_BOX Or TDF_USE_HICON_FOOTER Or TDF_USE_SHELL32_ICONID Or TDF_KILL_SHIELD_ICON Or TDF_USE_COMMAND_LINKS
    .DateTimeType = dttDateTimeWithCheckTimeOnly
    .DateTimeAlign = TDIBA_Buttons
    .ComboAlign = TDIBA_Content
    .ComboStyle = cbtDropdownList
    .ComboSetInitialItem 1
    .ComboImageList = himlSys
    .ComboAddItem "Do Thing #1", 2
    .ComboAddItem "Do Thing #2", 7
    .ComboAddItem "Do Thing #3", 8
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .InputText = "New Event 1"
    .InputAlign = TDIBA_Footer
    .IconMain = TD_SHIELD_GRADIENT_ICON
    .IconFooter = hIconF
    .IconReplaceGradient = 276
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .AddCustomButton 102, "Schedule" & vbLf & "Additional information here."
    .AddRadioButton 110, "Apply to this account only."
    .AddRadioButton 111, "Apply to all accounts."
    .ShowDialog

    Label2.Caption = "Radio: " & .ResultRad
    Label5.Caption = .ResultInput
    Label7.Caption = .ResultComboText
    Label9.Caption = .ResultComboIndex
    Label11.Caption = .ResultDateTime
    If .ResultDateTimeChecked = 0 Then
        Label13.Caption = "Time unchecked."
    Else
        Label13.Caption = "Time checked."
    End If
    If .ResultMain = 102 Then
        Label1.Caption = "Scheduled."
    Else
        Label1.Caption = "Cancelled."
    End If
End With
```





Finally Version 1.0 added high DPI support and a few extra features.
When creating the dialog, you specify an auto-close time in seconds. When you retrieve the property while the dialog is active, it returns the time remaining, allowing you to do things like tie it into a progress bar of doom:


```
With TaskDialogAC
    .Init
    .MainInstruction = "Do you wish to do somethingsomesuch?"
    .Flags = TDF_CALLBACK_TIMER Or TDF_USE_COMMAND_LINKS Or TDF_SHOW_PROGRESS_BAR
    .Content = "Execute it then, otherwise I'm gonna peace out."
    .AddCustomButton 101, "Let's Go!" & vbLf & "Really, let's go."
    .CommonButtons = TDCBF_CLOSE_BUTTON
    .IconMain = IDI_QUESTION
    .IconFooter = TD_ERROR_ICON
    .Footer = "Closing in 15 seconds..."
    .Title = "cTaskDialog Project"
    .AutocloseTime = 15 'seconds
    .ParenthWnd = Me.hwnd
    .ShowDialog
End With

'Then:
Private Sub TaskDialogAC_DialogCreated(ByVal hwnd As Long)
TaskDialogAC.ProgressSetRange 0, 15
TaskDialogAC.ProgressSetState ePBST_ERROR
End Sub

Private Sub TaskDialogAC_Timer(ByVal TimerValue As Long)
On Error Resume Next
TaskDialogAC.Footer = "Closing in " & TaskDialogAC.AutocloseTime & " seconds..."
TaskDialogAC.ProgressSetValue 15 - TaskDialogAC.AutocloseTime
On Error GoTo 0
End Sub
```

When a dialog times out, it returns a TD_CANCEL main result (it also does this if you hit 'x').


Next up, logo images. Now, there's a very wide variety of image processing techniques. Rather than accept a file name and limit things to one method, the class module must simple be passed an HBITMAP, which you can acquire in a multitude of ways. The demo project uses GDI+, so a generic set of standard images is supported. The logo image can go in two places, in the buttons position (means no controls, custom or built-in, can be placed there):

```
'some initializing settings from the picture are omitted
    Dim hBmp As Long
    Dim sImg As String
    sImg = App.Path & "\vbf.jpg"
    Dim cx As Long, cy As Long
    hBmp = hBitmapFromFile(sImg, cx, cy)
With TaskDialog1
    .Init
    '[...]
    .SetLogoImage hBmp, LogoBitmap, LogoButtons
    .ShowDialog
End With
Call DeleteObject(hBmp) 'do not free image until the dialog is closed, otherwise it won't show.
```




Two more features of the logos are support for transparency, and support for custom offsets from the edges. Transparency, due to the way TaskDialogs work with that infernal DirectUIHWND, require some processing... the background color isn't correctly reported so it's transparent to the wrong color. This is resolved by custom-setting the background it's transparent to to the current RBG of the actual visible pixels. If we always just used the standard window background, it wouldn't work with the shield gradients. Don't worry, it's all handled for you. Just set the alignment and desired offsets:

```
    .SetLogoImage hBmp, LogoBitmap, LogoTopRight, 4, 4
```

(as suggested by 'LogoBitmap', there's also 'LogoIcon' that you need to use for .ico's)


Finally we get to drop down buttons. It's a pretty rare thing to require, but once subclassing had to be brought in for a host of other uses, why not add it? Note that the demo project prints a debug message when the dropdown arrow is clicked, I didn't include the large amount of code to then generate a menu at that spot. If you need help with doing that, let me know in this thread or by PM, as I do have that code written.
At this time, it's only possible to make a split button out of a custom button; which has to be a normal button-- so command links aren't supported. Turning a button into a split button is done by ID (and only 1 button can be made into a split one at this time):


```
    .AddCustomButton 123, " SuperButton  " 
    .SetSplitButton 123
```

Note the extra space padding-- this is required for the button to look right. I'll look into automating the addition of spaces in the future but for now it's gotta be manually done.
Icons work but aren't perfect:

You might want to consider customizing the icon by having some blank pixels on the left.




*See post #4 for events, properties, and methods*

*Thanks*

Thanks very much to LaVolpe for helping me get the callbacks hyperlinks going with this project. Also Julius Laslo's TaskDialogIndirect implementation for the delcares/constants and the general principle of calling this monster of a function; and Randy Birch's simple TaskDialog implementation for getting me started.


*Download Attachment:* cTaskDialog12-R2.zip



---
Last updated 21 March 2020

Feel free to do whatever you want with this code, all I ask is you provide credit to me as I have done with others.

All bug reports, comments, criticisms, and ideas on how to improve the project are absolutely welcome!

----------


## Bonnie West

> All bug reports, comments, criticisms, and ideas on how to improve the project are absolutely welcome!


The TaskDialogCallbackProc callback function can be reduced and made more efficient at the same time by simply modifying the type of its last parameter:



```
Public Function TaskDialogCallbackProc(ByVal hWnd As Long, ByVal uNotification As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal lpRefData As cTaskDialog) As Long
    ''***DO NOT CALL THIS FUNCTION***
    TaskDialogCallbackProc = lpRefData.ProcessCallback(hWnd, uNotification, wParam, lParam)
End Function
```

The ProcessCallback's arguments are best passed ByVal because passing pointers (ByRef) to Longs are slightly slower than passing them by value.

InitCommonControlsEx's return value should be Long instead of Boolean (the incorrect declaration doesn't appear to cause any problems, though).




> ... but the url needs to be in quotes, so you'll need chr$(34).


Double quotes can be embedded in a string literal by escaping it with another double quote. This avoids potentially expensive string concatenations:



```
.Content = "Where else to link to but <a href=""http://www.microsoft.com"">Microsoft.com</a>"
```




> 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.
> 
> . . .
> 
> Due to the severe limitations on what icons can be put in a VB project res file in the actual icon group, ...


Actually, the problem lies with the default resource compiler that came with VB6 (RC.EXE). It doesn't understand the latest icon formats, such as the 256x256 PNG-compressed 32bpp "Heart" icon in your resource file. By using a newer version of RC.EXE (such as the one from the Vista (or later) SDK), it now becomes possible to include _proper_ icon resources that take advantage of the latest icon formats.




> Then, the ResIcontoHICON function will give you the hIcon you need for cTaskDialog. But remember, any other function returning an hIcon will work.


Don't forget to destroy those hIcons too! Or else, each time ResIconTohIcon() is called with the same resource ID, the previously created icon from that resource ID gets leaked!




> You should call *DestroyIcon* for icons created with *CreateIconFromResourceEx*.





> .ParenthWnd - May or may not matter; I haven't had any problems not setting it.


Despite its name, the hwndParent member of the TASKDIALOGCONFIG structure specifies the owner window of the task dialog. It is akin to invoking .Show vbModal, Me.

----------


## fafalone

*ADDITIONAL DOCUMENTATION
*
Here's a full list of the flags, public events, properties and methods:

*Full Class Call List*
*Flags*
TDF_ENABLE_HYPERLINKS
 Allows hyperlinks in standard form.

TDF_USE_HICON_MAIN
Required to use a custom icon.

TDF_USE_HICON_FOOTER
Required to use a custom icon.

TDF_ALLOW_DIALOG_CANCELLATION
Adds an X in the top right and a copy of the main icon to the titlebar.

TDF_USE_COMMAND_LINKS


TDF_USE_COMMAND_LINKS_NO_ICON


TDF_EXPAND_FOOTER_AREA
When expanded info is shown, it's placed at the bottom.

TDF_EXPANDED_BY_DEFAULT
Expanded information is shown by default and the button shows its expanded state.

TDF_VERIFICATION_FLAG_CHECKED
Makes the checkbox checked by default.

TDF_SHOW_PROGRESS_BAR
Shows a regular progress bar.

TDF_SHOW_MARQUEE_PROGRESS_BAR
Use ProgressStartMarquee

TDF_CALLBACK_TIMER
Sends the Timer event every 200ms

TDF_POSITION_RELATIVE_TO_WINDOW
Center on ParenthWnd instead of screen.

TDF_RTL_LAYOUT
Right-to-left layout.

TDF_NO_DEFAULT_RADIO_BUTTON


TDF_CAN_BE_MINIMIZED


TDF_NO_SET_FOREGROUND


TDF_USE_SHELL32_ICONID
(Custom flag) Icon is loaded from shell32.dll

TDF_USE_IMAGERES_ICONID
(Custom flag) Icon is loaded from imageres.dll

TDF_USE_SHELL32_ICONID_BUTTON
(Custom flag) Button icon is loaded from shell32.dll

TDF_USE_IMAGERES_ICONID_BUTTON
(Custom flag) Button icon is loaded from imageres.dll

TDF_EXEC_HYPERLINKS
(Custom flag) Automatically launch link as-is with explorer (uses default browser for web)

TDF_USE_SHELL32_ICONID_BUTTON
(Custom flag) IconMain/IconFooter specify an icon id in shell32.dll

TDF_USE_IMAGERES_ICONID_BUTTON
(Custom flag) Same, but with imageres.dll

TDF_KILL_SHIELD_ICON
(Custom flag) Allows the colored background from the TD_SHIELD_x icons to be used with just the text and no shield icon

TDF_INPUT_BOX
(Custom flag) Adds an Input box control.

TDF_COMBO_BOX
(Custom flag) Adds Combo box; .ComboType controls edit or list

TDF_DATETIME
(Custom flag) Adds date control, time control, or both

TDF_SLIDER
(Custom flag) Adds Slider control



*Events*
DialogCreated(hWnd As Long)
Occurs when the dialog becomes visible. Returns dialog's hWnd, also available via .hWndDlg

ButtonClick(ButtonID As Long)


HyperlinkClick(lPtr As Long)
Contains a pointer to a string containing the URL passed through the callback lParam.

Timer(TimerValue As Long)
Every 200ms if flag set; value contains millisecs since created or timer reset

DialogDestroyed()


RadioButtonClick(ButtonID As Long)


DialogConstucted(hWnd As Long)
Occurs after the dialog is created in memory, but before it's shown to the user

VerificationClicked(Value As Long)
0=unchecked, 1=checked

ExpandButtonClicked(Value As Long)
0=collapsed, 1=expanded

Help()
Triggered by the user pressing F1 in the dialog.

DropdownButtonClicked(ByVal hwnd As Long)
(Custom event) Fired when a user clicks the arrow of a split button made with .SetSplitButton

ComboItemChanged(ByVal iNewItem As Long)
(Custom event) For use with the Combo Box custom control

ComboDropdown()
(Custom event) For use with the Combo Box custom control

InputBoxChange(sText As String)
(Custom event) For use with the Input Box custom control

DateTimeChange(ByVal dtNew As Date, ByVal lCheckStatus As Long)
(Custom event) For use with the date/time controls

SliderChange(ByVal lNewValue As Long)
(Custom event) For use with the slider custom control

AutoClose()
(Custom event) Fired when the dialog is closed in response to a .AutoCloseTime set timeout


*Properties*
.AutocloseTime
Get/Let, Modify if open
Specifies, in seconds, a timeout after which the dialog automatically closes. While running, retrieves the time remaining.

.CollapsedControlText
Get/Let
For the text next to the round expando control while not expanded

.ComboAlign
Get/Let
The position of the combo box, in the content area, by the buttons, or in the footer

.ComboAlignInFooter
Get/Let
For combo boxes in the footer, left-center-right alignment

.ComboCueBanner
Get/Let, Modify if open
The cue banner text for editable combo boxes

.ComboDropWidth
Get/Let, Modify if open
Width of the dropdown list.

.ComboHeight
Get/Let
The height of the dropdown list of the combo control. Default is 115px (DPI scaled)

.ComboImageList
Get/Let
Associates the combo control with an HIMAGELIST (API/COM imagelists)

.ComboIndex
Get/Let, Modify if open
The currently selected combo item

.ComboOffsetX
Get/Let
Manual adjustment for position offset.

.ComboStyle
Get/Let
Normal dropdown (editable) or dropdown list

.ComboText
Get
The current text of the combo box.

.ComboWidth
Get/Let
Override the automatic width value

.CommonButtons
Get/Let
For the standard OK, Cancel, Yes, No, etc buttons

.Content
Get/Let, Modify if open
The primary message text

.DateTimeAlign
Get/Let
Date/Time control position (content, buttons, footer)

.DateTimeAlignInButtons
Get/Let
If in content area, left-right-center alignment

.DateTimeAlignInContent
Get/Let
If in content area, left-right-center alignment

.DateTimeAlignInFooter
Get/Let
If in footer area, left-right-center alignment

.DateTimeChecked
Get/Let, Modify if open
A value* representing the current check state; can be used to set default state

.DateTimeOffsetX
Get/Let
Manual adjustment for position offset.

.DateTimeType
Get/Let
Date, Time, or Date+Time with checkbox options

.DateTimeValue
Get/Let, Modify if open
The current date and/or time, as a Date type

.DefaultButton
Get/Let
Specifies the default button by ID; can be either a common button or custom button ID

.DefaultCustomControl
Get/Let
If multiple custom controls are present, override the automatic calculation of which one gets focus

.DefaultRadioButton
Get/Let
The ID of the default radio button

.DPIScaleX
Get/Let
The current X scaling factor; will be 1 for 100% DPI

.DPIScaleY
Get/Let
The current Y scaling factor; will be 1 for 100% DPI

.ExpandedControlText
Get/Let
The text next to the round expando button when expanded

.ExpandedInfo
Get/Let, Modify if open
The extra information shown/hidden by the expando control

.Flags
Get/Let
The TDF_x control flags

.Footer
Get/Let, Modify if open
The text appearing in the footer area

.hInst
Get/Let
hInstance; usually set by the class automatically, but if you want to change it to another app/dll, you can then specify an icon from that file. Do not set any icon related flags if you do this. The shell32/imageres feature handles this on its own.

.hWndCombo
Get
The handle of the combo control if present

.hWndComboEdit
Get
The handle of the combo control's textbox if present

.hWndDateTime
Get
The handle of the date/time control if present

.hWndDlg
Get
The main handle of the dialog

.hWndDUI
Get
The handle of the DirectUIHWND class that holds the controls; immediate parent of custom controls

.hWndInput
Get
The handle of the input textbox if present

.hWndSlider
Get
The handle of the slider control if present

.IconFooter
Get/Let, Modify if open
The ID or HICON for the footer icon

.IconMain
Get/Let, Modify if open
The ID or HICON for the main icon

.IconReplaceGradient
Get/Let, Modify if open
If TDF_KILL_SHIELD_ICON is present, you can optionally replace it instead of leaving it gone

.InputAlign
Get/Let
The position of the input textbox, content-buttons-footer

.InputAlignInFooter
Get/Let
If in the footer, left-right-center alignment

.InputCueBanner
Get/Let, Modify if open
The cue banner text of the input textbox

.InputIsPassword
Get/Let, Modify if open
Set/unset the password style that hides the text behind asterisks/dots

.InputOffsetX
Get/Let
Manual adjustment for position offset.

.InputText
Get/Let, Modify if open
The current text of the input textbox; can be used to set default text

.InputWidth
Get/Let
Override the automatic width value of the input textbox

.MainInstruction
Get/Let, Modify if open
The larger header text above the main message text

.ParenthWnd
Get/Let
Not mandatory, but if set the dialog will appear modally as a child of the given for (like Form.Show vbModal, Me)

.ResultComboIndex
Get
The final item selected when the dialog closes

.ResultDateTime
Get
The final date and/or time when the dialog closes

.ResultDateTimeChecked
Get
The final check status of date/time control(s) when the dialog closes

.ResultInput
Get
The final text of the input textbox when the dialog closes

.ResultMain
Get
The ID of the button that closed the dialog. TDCANCEL is also the result for the 'X' button closing the dialog or the Autoclose timeout being reached

.ResultRad
Get
The ID of the radio button selected when the dialog closes

.ResultVerify
Get
The status of the verification checkbox when the dialog closes

.SliderAlign
Get/Let
The position of the slider control, content-buttons-footer

.SliderAlignInFooter
Get/Let
If the slider is in the footer, left-right-center alignment

.SliderOffsetX
Get/Let
Manual adjustment for position offset.

.SliderTickStyle
Get/Let
Set if ticks appear on the top, bottom, or both

.SliderValue
Get/Let, Modify if open
The current value of the slider; can be used to set the default value

.SliderWidth
Get/Let
Override the automatic width value of the slider control

.Title
Get/Let
Main dialog title appearing in the control bar

.VerifyText
Get/Let
Text appearing next to the Verify checkbox

.Width
Get/Let
You can manually specify the width of the TaskDialog in Dialog Units (which are not pixels or twips). If not set, Windows automatically calculates an optimal value.



*Methods*

Method
Effects open dlg?
Description

.AddCustomButton(id, text [,hIcon])
No
Adds a custom button. These appear as command links if enabled, otherwise as standard buttons

.AddRadioButton(id, text)
No
Adds a radio button

.ClearCustomButtons
No
Clears the current set of custom buttons

.ClearRadioButtons
No
Clears the current set of radio buttons

.ClickButton(id)
.ClickRadioButton(id)
Yes
Click a button by id

.ClickVerification(state[,focused])
Yes
0/1 for checked, 1 to set keyboard focus on the checkbox

.CloseDialog
Yes
Closes an open dialog and returns TD_CLOSE as the result, even if that button is not present.

.ComboAddItem(text[,image][,overlay])
Yes
Adds an item to the combo control, if present

.ComboSetCurrentState(text[,image][,overlay])
Yes
Sets the text/image of the edit box of a combo control

.ComboSetInitialItem(idx)
No
Sets the default combo item

.ComboSetInitialState(text[,image][,overlay)
No
The default text/image of the combo edit box

.DateTimeSetInitial(date)
No
The default for the date and/or time control

.DateTimeSetRange(setmin,setmax,min,max)
Yes
Limit the dates/times allowed

.EnableButton(id, state)
.EnableRadioButton(id, state)
Yes
Enable/disable a button by id (0/1)

.GetCustomButtons(ids(),strs())
No
Get an array of the current custom buttons

.GetRadioButtons(ids(),strs())
No
Get an array of the current radio buttons

.Init
No
Resets all config settings to their default state

.NavigatePage(cTaskDialog)
Yes
Mutli-page navigation; opens a new taskdialog as a page of the current one

.ProcessCallback
.ProcessEnumCallback
.ProcessSubclass
These are public but for internal use; *DO NOT CALL*

.ProgressSetRange(min,max)
Yes
Sets the range of the progress bar

.ProgressSetState(state)
Yes
Progress state; green/normal, yellow/pause, red/error

.ProgressSetType(type)
Yes
Sets regular or marquee type progress bar

.ProgressSetValue(value)
Yes
Sets the current progress value

.ProgressStartMarquee(speed)
.ProgressStopMarquee
Yes
Start/stop a marquee-style progress bar

.ResetTimer
Yes
Resets the value sent by the Timer event.

.SetButtonElevated(buttonid,state)
Yes
Adds the security shield icon to a button to indicate elevated user permissions are required to perform that action. 1 to turn on, 0 to turn off the icon.

.SetButtonHold(buttonid)
.ReleaseButtonHold
Yes
Places a hold on a button: the dialog will not close when the button is clicked. The click event is still sent.

.SetCommonButtonIcon(tdbutton,hicon)
No
Add an HICON-based icon to a common button

.SetLogoImage(handle,type,pos[,offX][,offY])
No
Add a logo image in the type right or by the buttons

.SetSplitButton(id)
No
Add a dropdown arrow to a custom button

.SetWindowsButtonIconSize(size)
No
Set the size of button icons loaded from shell32 or imageres.

.ShowDialog
n/a
The main command to start the dialog, called after all initial setup

.SimpleDialog(text[,btns][,title][,maintxt][,icon][,hwnd][,hinst])
n/a
Shows a rudimentry task dialog with the plain TaskDialog() API

.SliderSetChangeValues(small,large)
Yes
The value of a small and large change in the slider control

.SliderSetRange(min,max[,tickfqr])
Yes
Range and tick frequency for the slider control


* - Date/Time checks values are as follows: If there only a single control, or both but only one has a checkbox, it's just 0/1 for unchecked/checked. For 2 controls both with checkboxes, 4=both are checked, 3=date checked, time unchecked, 2=date unchecked, time checked, 0=neither are checked

Recycling this post position for overrun from the lead posts; it was originally a comment reading:
Even if you used a newer RC, wouldn't you still have to insert the icon outside of the built-in resource editor?

Thanks for the tips, I'm incorporating them in now.

----------


## Bonnie West

> Even if you used a newer RC, wouldn't you still have to insert the icon outside of the built-in resource editor?


Unfortunately, yes. I tried replacing the older RC.EXE that came with VB6 with the one from the Win 7 SDK, but still, VB6's Resource Editor add-in complains that your icons are "Invalid Icon Files". I'm not aware of any replacement for the default VB6 Resource Editor add-in, so you'll have to manually create a resource file yourself using the latest RC.EXE if you want to properly embed modern icons in your compiled binaries. It's not too difficult anyway. You just have to write a resource script like this:



```
// td.rc

#define ICO_CLOCK 101
#define ICO_HEART 102

ICO_CLOCK ICON Clock.ico
ICO_HEART ICON Heart.ico

#define MANIFEST_RESOURCE_ID  1
#define RT_MANIFEST          24

MANIFEST_RESOURCE_ID RT_MANIFEST taskdialogindirect.exe.manifest
```

Then compile it via the command line or with a batch file, such as this:



```
:: CompileRC.cmd
@CD /D %~dp0
:: "%ProgramFiles%\Microsoft Visual Studio\VB98\Wizards\RC.EXE" td.rc
"%ProgramFiles%\Microsoft SDKs\Windows\v7.0\Bin\RC.Exe" /nologo td.rc
@PAUSE > NUL
```

Finally, manually add the resource file to your VB6 project. That's all.

----------


## fafalone

So, in other words, adding it as a custom resource and using the ResIconToHICON function is not only much easier, but the only way to go if you want to use it while in IDE?  :Embarrassment: 

But yeah editing the resource manually is still really useful... I do it to add custom version information and would really like to experiment with providing a dialog template for functions like GetOpenFileName.

----------


## Bonnie West

> So, in other words, adding it as a custom resource and using the ResIconToHICON function is not only much easier, but the only way to go if you want to use it while in IDE?


It's also possible to obtain a handle to an icon resource (even in the IDE) via LoadResPicture(IconID, vbResIcon).Handle. However, in cases where it refuses to load the newer icon formats, the following (IDE-friendly) alternative to ResIconTohIcon() should do the trick:



```
Option Explicit     'In a standard module

Private Declare Function CreateIconFromResourceEx Lib "user32.dll" ( _
              ByRef pbIconBits As Any, _
              ByVal cbIconBits As Long, _
     Optional ByVal fIcon As Long = -True, _
     Optional ByVal dwVersion As Long = &H30000, _
     Optional ByVal cxDesired As Long, _
     Optional ByVal cyDesired As Long, _
     Optional ByVal uFlags As Long _
) As Long
Private Declare Function LoadImageW Lib "user32.dll" ( _
              ByVal hInst As Long, _
              ByVal lpszName As Long, _
     Optional ByVal uType As Long, _
     Optional ByVal cxDesired As Long, _
     Optional ByVal cyDesired As Long, _
     Optional ByVal fuLoad As Long _
) As Long
Private Declare Function LookupIconIdFromDirectoryEx Lib "user32.dll" ( _
              ByRef presbits As Any, _
     Optional ByVal fIcon As Long = -True, _
     Optional ByVal cxDesired As Long, _
     Optional ByVal cyDesired As Long, _
     Optional ByVal Flags As Long _
) As Long

Public Declare Function DestroyIcon Lib "user32.dll" (ByVal hIcon As Long) As Long
```



```
'ID                - Integer, Long or String identifier of the icon resource. Any other Variant subtype will raise an error.
'Width             - The desired width, in pixels, of the icon. If not supplied or 0, the function uses the SM_CXICON system metric value.
'Height            - The desired height, in pixels, of the icon. If not supplied or 0, the function uses the SM_CYICON system metric value.
'GethIconFromResID - Returns a handle to the loaded/created icon if successful, 0 or an error otherwise. Don't forget to destroy the icon when done!

Public Function GethIconFromResID(ByRef ID As Variant, Optional ByVal Width As Long, Optional ByVal Height As Long) As Long
    Const IMAGE_ICON = 1&, LR_DEFAULTSIZE = &H40&, RT_GROUP_ICON = 14&, RT_ICON = 3&
    Dim Data() As Byte

    If App.LogMode Then
        GethIconFromResID = LoadImageW(App.hInstance, Choose(VarType(ID), , ID, ID, , , , , StrPtr(ID)), IMAGE_ICON, Width, Height, LR_DEFAULTSIZE)
    Else
        Data = LoadResData(ID, RT_GROUP_ICON)
        Data = LoadResData(LookupIconIdFromDirectoryEx(Data(0&), , Width, Height), RT_ICON)
        GethIconFromResID = CreateIconFromResourceEx(Data(0&), UBound(Data) + 1&, , , Width, Height)
    End If
End Function
```

----------


## LaVolpe

A couple of points

1) Nothing against the code Bonnie posted. But if this requires a typical user to manually use rc.exe, may not find many takers

2) Regarding this TaskDialog (your implementation or anyone's). Have you tested it with multiple forms open? Wondering if you get the same thing I do. We know a msgbox is application modal by nature. You open multiple forms & display a msgbox, you aren't going to access any of those forms until that msgbox is closed. Not so with the TaskDialog

P.S. this horizontal scrolling is annoying; maybe it's just my browser

----------


## fafalone

Are you saying that if you made it modal with Form1 as the parent, you wouldn't be able to access Form2? If so, that's not the case with my implementation. If .ParenthWnd is set, that form becomes inaccessible, but other forms respond:



With ParenthWnd=me.hwnd, i clicked the first dialog, then clicked form2 and the msgbox came up fine. If ParenthWnd=0, then all forms, including the calling form, are available.





> It's also possible to obtain a handle to an icon resource (even in the IDE) via LoadResPicture(IconID, vbResIcon).Handle


But if you could get it into the icon group, you'd just have to set hInst=App.hInstance and .IconMain=IconID anyway; no need to worry about an hIcon for it.




> P.S. this horizontal scrolling is annoying; maybe it's just my browser


Do you mean my new readme post format? I set the table width to 100% and not a fixed width so it should work for most resolutions... although it does look like the comments themselves are fixed-width.

----------


## LaVolpe

> Are you saying that if you made it modal with Form1 as the parent, you wouldn't be able to access Form2? If so, that's not the case with my implementation. If .ParenthWnd is set, that form becomes inaccessible, but other forms respond


The opposite, I was saying exactly what you said. Unlike the msgbox, the TaskDialog is NOT application-modal




> Do you mean my new readme post format?


No, I was talking about this thread. Gotta scroll way right/left to see read the entire posts

----------


## LaVolpe

FYI. I was playing around with trying to make the TaskDialog application-modal, or at least appear application modal.

Here's a method that appears to work (for most cases).

1) Locate the project owner window. In VB all forms appear to be owned by a hidden 'master' VB window, both in design and when compiled.
This master window will be the owner (GetWindow hWnd, GW_OWNER) for each form in the project, not already owned by an existing project window
This master window will not have an owner itself

2) Once the master is found, call EnableWindow on its handle to disable it, disables the owned windows too.

So, I locate the master window, disable it, call task dialog. None of the windows (other than the TaskDialog) are accessible. After TaskDialog closes, renable the master window.

For fun, one does not need to be passed a parent hWnd for the TaskDialog to make the above work. I have a routine that will 
1) Enumerate the desktop windows
2) Find the first visible window having the same thread ID as the project. This will be the parent window for TaskDialog
3) Locate the master from that window's hWnd
Now I can disable it & make the TaskDialog appear application-modal. Of course one can offer options: app-modality, no-modality, modality for calling form only

Oh, I forgot to mention a significant point with the above logic. While uncompiled, that master window owns the IDE too; i.e., IDE disabled  :Big Grin: 
Though with a msgbox, you can't access the code while it is displayed either... So, in another case, imitates msgbox modality well.
For debugging purposes, if this method is used, it is very important that ppl understand that just like the msgbox, this method would block RaiseEvent calls while uncompiled. So, making the TaskDailog imitate msgbox modaility, you get the downsides too. Of course, this all can be worked around too by simply not permitting application modality during IDE or providing a property that will enable/disable this functionality while in IDE. For the TaskDialog, blocked RaiseEvent calls only an issue if the TaskDialog is calling back to a class and the class uses RaiseEvent to inform the host what messages were received from the TaskDialog. 

Again, this modality is only an issue in IDE and in a specific scenario: need callbacks. And I can see this as being a major issue if the user needs to respond to adjust the progressbar, respond to URL clicks, respond to TaskDialog button clicks, etc. Being able to respond to these things make the TaskDialog more advantageous than the msgbox. So, how do we get IDE modality and prevent RaiseEvents from being blocked while in IDE?  Answer is quite simple. Use of an Implementation class as those are not blocked. This method resolves the issue and requires reformating a class that uses RaiseEvents.  The form/uc/propertypage/class that calls the TaskDialog class must use keyword Implements the new callback class. That callback class uses Public methods that replicate the RaiseEvent's Public Events. Instead TaskDialog class would need a property/parameter accepting the calling code as an instance of that callback class. Now instead of RaiseEvent, you would trigger the callback class's public methods. Short, incomplete sample follows:

Callback class: ITaskDialogCallback. 


```
Public Sub HyperlinkClicked(ByVal hWndTDialog As Long, ByVal URL As String)
End Sub
... other public methods as needed
```

The TaskDialog class: cTDialog. This is the class that has all the dialog properties & the ShowDialog method


```
Private m_Caller As ITaskDialogCallback  ' class level variable

Public Property Set Caller(theCaller As ITaskDialogCallback) ' method to set caller, optionally can be a parameter in the ShowDialog routine?
   Set m_Caller = theCaller
End Property

... within the method that receives the task dialog callback messages
If m_Caller Is Nothing Then Exit Function

Select Case uNotification
    Case TDN_HYPERLINK_CLICKED Then
        ' get the URL from the provided string pointer
        Call m_Caller.HyperlinkClicked(hWnd, strURL)
    ... other case statements
End Select

' reset m_Caller on the TDN_DESTROYED notification
```

The calling form:


```
Implements ITaskDialogCallback

' we'll say that an instance of the cTDialog class is named: clsTDialog 
Set clsTDialog.Caller = Me
clsTDialog.ShowDialog
....

' And the Implementation routine (added automatically by VB) looks like
Private Sub ITaskDialogCallback_HyperlinkClicked(ByVal hWndTDialog As Long, ByVal URL As String)
  ' send url to default browser
End Sub
```

Side note. Haven't tested this, modality-workaround, with MDI forms, but don't expect different behavior

----------


## Bonnie West

> Unlike the msgbox, the TaskDialog is NOT application-modal


Indeed. The MessageBox function has an *MB_TASKMODAL* flag whereas TaskDialogIndirect has no such equivalent.




> *MB_TASKMODAL*
> 0x00002000L
> Same as *MB_APPLMODAL* except that all the top-level windows belonging to the current thread are disabled if the _hWnd_ parameter is *NULL*. Use this flag when the calling application or library does not have a window handle available but still needs to prevent input to other windows in the calling thread without suspending other threads.
> 
> 
> 
> *Message Boxes*
> 
> A message box is a modal dialog box and the system creates it by using the same internal functions that *DialogBox* uses. If the application specifies an owner window when calling *MessageBox* or *MessageBoxEx*, the system disables the owner. An application can also direct the system to disable all top-level windows belonging to the current thread by specifying the *MB_TASKMODAL* value when creating the dialog box.





> I have a routine that will
> 1) Enumerate the desktop windows
> . . .


Here's another approach that doesn't require a callback procedure.

----------


## LaVolpe

> Here's[/URL] another approach that doesn't require a callback procedure.


Appreciated. I don't use EnumWindows API, just imitate it with: FindWindow, GetWindowThreadProcessId & GetNextWindow calls in a loop

But I see I can streamline it by using Forms collection - didn't even consider that!

Edited: 
However, after some testing, I'll keep my current method. As it exists, when a task dialog is displayed on top of another task dialog, the current method recognizes the previous dialog as top most in the thread even though it doesn't have a ThunderMain class.  But using the Forms collection would allow me to jump right to an hWnd in the thread vs starting with a random desktop window & navigating from there.  Worth tinkering with. Thanx

----------


## fafalone

Note- project has been updated- if you're using the multi-page feature, the new version has very important bugfixes. Otherwise updates not of particular importance. See details in first post.

----------


## Bonnie West

Bug report:



```
Public Property Let Width(Value As Long): uTDC.cxWidth = Width: End Property
```

Should be:



```
Public Property Let Width(Value As Long): uTDC.cxWidth = Value: End Property
```


Users should probably be reminded that the width of the task dialog's _client area_ is in *dialog units*, not pixels, twips or anything else.





> (v0.5.1)
> Added a routine to force the case of the Enum's for easier development.


You might be interested in this subroutine that automates redeclaring an Enum's members.  :Wink:

----------


## fafalone

Well that's embarrassing. Thanks... file updated.

Dialog units sure look like an unmitigated pain in the a**... MapDialogRect() and GetDialogBaseUnits() both require the hWnd of the dialog, and .width is set beforehand. And methods to calculate it without that seem a bit involved. For now I'll just note it in the readme; but I'd really like to just accept it in pixels and/or twips. Is there a simple way I'm not seeing?

----------


## Bonnie West

> Is there a simple way I'm not seeing?


Sorry, but I'm not aware of any. I believe, however, that it's better to use dialog units because it is a device-independent measurement. The task dialog will be able to retain its proportions regardless of the current screen DPI.

----------


## LaVolpe

> I believe, however, that it's better to use dialog units because it is a device-independent measurement...


I can see fafaone's issue. If offering dialog units as a measurement, you don't know what those units are beforehand as they are dependent on the dialog's font. If TaskDialog uses the system font, then it's easy enough with GetDialogBaseUnits API. However, per msdn, most dialogs define their own font & there is no guarantee that the same font will be used from O/S to O/S.

A simple idea if wanting to provide users a property for pixels & simple enough to test:

During the dialog's creation, a couple of notifications are received: tdn_created & tdn_dialog_constructed. 
When seeing one of those, maybe a simple call to SetWindowPos API would do the trick.
Of course the dialog structure's width member would need to be cached & not passed to TaskDialog API. The cached value used for SetWindowPos

Note. That the structure's width member is not only intended to be dialog units, but also client area only

----------


## LaVolpe

fafalone... just a heads up regarding my last post

Though SetWindowPos works well for sizing the window, the dialog messages come too late. The guts of the dialog is drawn to the size before SetWindowPos was called.

So, a new train of thought is required.

1) Probably can inform users that the Width property requires Dialog units, but for most users, might as well just not offer the property

2) If the width property is set, could potentially create a generic TaskDialog window, in the tdn_created event destroy the window, but before that:
-- call MapDialogRect to convert a 100x100 rectangle so you can get the unit-to-pixel ratio
-- cache that ratio & use it to convert user-supplied pixel value to dialog value for the TDialog structure's width member
-- Downside is that if caching this, then should user change themes while project is alive, the ratio will probably no longer work
:: fix for last statement: subclass for theme changes? perform step 2) above prior to displaying each TaskDialog?

```
' example
Private Function pvTaskDialogCallback(ByVal hWnd As Long, ByVal uNotification As Long, _
                            ByVal wParam As Long, ByVal lParam As Long, ByVal dwRefData As Long) As Long

' for my class, dwRefData is a reference to an Implementation class. 
' When -1, it is a flag I set for getting horizontal ratio

    Select Case uNotification
    Case TDN_CREATED
        If dwRefData = -1& Then
            Dim tRect As RECT
            tRect.Right = 100: tRect.Bottom = 100
            MapDialogRect hWnd, tRect
            m_HorzRatio = tRect.Right    ' cached class-level variable
            DestroyWindow hWnd
            ' user-set pixel width can now be calculated as: (Width * 100) \ m_HorzRatio
        Else
            ...
        End If

    .... other case statements

    End Select
End Function
```

3) Might be able to create a new window, based on the TaskDialog window class, get the font and calculate your own dialog units. Then destroy that window
-- measurement may not be exactly as windows calculates, but should be close



> http://msdn.microsoft.com/en-us/libr...=vs.85%29.aspx
> For a dialog box that does not use the system font, the base units are the average width and height, in pixels, of the characters in the dialog's font. You can use the GetTextMetrics and GetTextExtentPoint32 functions to calculate these values for a selected font. However, by using the MapDialogRect function, you can avoid errors that might result if your calculations differ from those performed by the system.
> 
> Each horizontal base unit is equal to 4 horizontal dialog template units; each vertical base unit is equal to 8 vertical dialog template units. to convert from pixels to dialog template units, use the following formulas:
> templateunitX = MulDiv(pixelX, 4, baseunitX)


*Edited*
But in hindsight, since the TaskDialog uses the standard Windows dialog class (#32770), would expect the system font to be used & therefore GetDialogBaseUnits API can be used.
Above being said, the conversion is as simple as the following:


```
dialogClientWidth = (desiredWidth * 4&) \ (GetDialogBaseUnits() And &HFFFF&)
```

In my tests, results of both sample routines returned the same calculated/converted values  :big yellow:

----------


## fafalone

So I guess the question is, are there any scenarios under which a task dialog wouldn't use the system font?

----------


## LaVolpe

I have not downloaded your project to play with -- still tweaking, working on my own version.

Here's one for you if it applies. In a multipage dialog, progressbars can be added, but in my case, they will not work (show progress) on any page other than the very first one. Same for you? I'm also fine tuning multipage dialogs & expect to find another oddity or two

*Edited*: Never mind. Figured it out. Just FYI if you run into same situation

Problem
1) I was initializing progressbars & setting control properties after the dialog constructed message received
-- this is not an issue for the 1st page of a dialog
2) There really isn't another message to use after the construction message (other than possibly the TDN_TIMER if it applies)
3) When any other page was later displayed, none of my initialization code seemed to work

Apparent Fix
1) After the constructed message received, set a short timer. I used a 30 ms timer
2) When timer event received, kill timer, then apply the initialization code

Works like a champ. My guess is that when the constructed message received for additional pages, it really isn't completely constructed?

----------


## fafalone

Yeah I ran into the same kinds of issues with having set things up in the initialization routines.

My solution was to use TDN_NAVIGATED for class-level initializations, then for initializing progress bars and such it has an event associated with it. TDN_NAVIGATED is essentially the same as TDN_CREATED for a new page.

----------


## LaVolpe

Much better solution -- seems to work just fine

----------


## LaVolpe

fafalone, here's something you may be interested in.  A way of extracting icons from the uncompiled resource file.

If res file has icon group, containing multiple versions of icon, then when compiled TaskDialog will extract the best match for its purpose. But in design-time, we can't do that because the res file isn't compiled. Our options are to use LoadResPicture which bites the big one here because it'll extract the best 32x32 icon 99% of the time and/or stretch it if necessary, regardless what size was requested (i.e., 16x16, 48x48, etc).

The code below is a 'rough draft' that I'll probably fine tune, but it works pretty well as-is.  Note: You'll need to destroy the hIcon at some point



```
' sample call
    Dim hIcon As Long
    hIcon = LoadResIconIDE("C:\Program Files\Microsoft Visual Studio\VB98\Projects\ico.RES", "FOXY", 16, 16)
    If hIcon Then
         MsgBox "Extracted icon. Handle = " & hIcon
         ' destroy hIcon when no longer needed
    End If
```

Now for the LoadResIconIDE method


```
Private Const RT_ICON As Long = 3
Private Const RT_GROUP_ICON As Long = 14

Private Declare Function LookupIconIdFromDirectoryEx Lib "user32.dll" (ByVal presbits As Long, ByVal fIcon As Long, ByVal cxDesired As Long, ByVal cyDesired As Long, ByVal Flags As Long) As Long
Private Declare Function CreateIconFromResourceEx Lib "user32.dll" (ByVal presbits As Long, ByVal dwResSize As Long, ByVal fIcon As Long, ByVal dwVer As Long, ByVal cxDesired As Long, ByVal cyDesired As Long, ByVal Flags As Long) As Long

Public Function LoadResIconIDE(FileName As String, ResID As Variant, Width As Long, Height As Long) As Long

    Dim fnr As Long, lNid As Long
    fnr = FreeFile()
    Open FileName For Binary Access Read As #fnr
    
    lNid = pvParseRESfile(fnr, RT_GROUP_ICON, ResID, Width, Height)
    If Not lNid = 0& Then
        LoadResIconIDE = pvParseRESfile(fnr, RT_ICON, lNid)
    End If
    Close #fnr


End Function


Private Function pvParseRESfile(FileHandle As Long, ResType As Long, ResID As Variant, Optional Cx As Long, Optional Cy As Long) As Long
    
    ' PROTOTYPE RESOURCEHEADER << must always start on a DWord boundary
    '    DataSize    AS DWORD ' bytes of resource data following header, not counting any trailing padding used for alignment
    '    HeaderSize  AS DWORD
    '    ResType(0)  AS WORD  ' variable length: numeric id or UnicodeZ string data
    '    ResName(0)  AS WORD  ' variable length: numeric id or UnicodeZ string data
    '    Padding AS WORD ????  ' 0-1 WORDs of padding to DWORD-align next item
    '    DataVersion AS DWORD
    '    MemoryFlags AS WORD
    '    LanguageId  AS WORD
    '    Version     AS DWORD
    '    Characteristics AS DWORD
    ' END PROTOTYPE
    ' Padding AS WORD ????  ' 0-1 WORDs of padding to DWORD-align data
    ' resource data (size of DataSize) follows & must always start on a DWord boundary
    
    Dim b() As Byte, iValue As Integer, lRead As Long
    Dim lDataSize As Long, lHdrSize As Long
    Dim bIsString As Boolean, bOk As Boolean, sID As String
    Const ICRESVER As Long = &H30000
    
    Seek #FileHandle, 1
    bIsString = (VarType(ResID) = vbString)
    Do Until EOF(FileHandle)
        Get #FileHandle, , lDataSize: Get #FileHandle, , lHdrSize
        If lHdrSize < 32& Or lDataSize < 0& Then Exit Do
        Get #FileHandle, , iValue: lRead = 10&
        If iValue = &HFFFF Then
            Get #FileHandle, , iValue: lRead = lRead + 2&
            If iValue = ResType Then
                Get #FileHandle, , iValue: lRead = lRead + 2&
                If bIsString Then
                    If Not iValue = &HFFFF Then
                        ReDim b(0 To lHdrSize - 31&)
                        Seek FileHandle, Seek(FileHandle) - 2&
                        Get #FileHandle, , b()
                        sID = b() ' << note: may contain extra 2 null bytes (DWord alignment) but StrComp ignores it....
                        lRead = lRead + LenB(sID) - 2&
                        If StrComp(sID, ResID, vbTextCompare) = 0 Then
                            bOk = True
                            Exit Do
                        End If
                    End If
                Else
                    Get #FileHandle, , iValue: lRead = lRead + 2&
                    If iValue = ResID Then
                        bOk = True
                        Exit Do
                    End If
                End If
            End If
        End If
        lRead = lHdrSize - lRead + (((lDataSize * 8&) + &H1F&) And Not &H1F&) \ &H8&
        If lRead > 0& Then Seek FileHandle, Seek(FileHandle) + lRead
    Loop
    
    If bOk Then
        lRead = lHdrSize - lRead
        If lRead > 0& Then Seek FileHandle, Seek(FileHandle) + lRead
        
        ReDim b(0 To lDataSize - 1&)
        Get #FileHandle, , b()
        If ResType = RT_ICON Then
            pvParseRESfile = CreateIconFromResourceEx(VarPtr(b(0)), lDataSize, True, ICRESVER, 0, 0, 0)
        ElseIf ResType = RT_GROUP_ICON Then
            For lRead = 7 To lDataSize - 1 Step 14
                If b(lRead + 1) = b(lRead) * 2 Then Debug.Print "likely malformated icon directory for Icon: "; ResID
            Next
            pvParseRESfile = LookupIconIdFromDirectoryEx(VarPtr(b(0)), True, Cx, Cy, 0&)
        End If
    End If

End Function
```

*Disclaimer*: This code should select the same icon that LoadImage selects based on desired width/height.
However, many icon headers are prepared incorrectly. The IconDirectory's 1-byte width & height are the physical size of the icon. Many applications will make the height value = height * 2. And that is incorrect per my interpretation. The BitmapInfo structure must report icon's height as double, but not the IconDirectory structure. When I loaded an icon group with multiple icons where the IconDirectory has the height as doubled, LookupIconIdFromDirectoryEx failed to pick the correct icon. When I fixed the height values, it picked it correctly. You will find documentation all over the web that says the IconDirectory height value is double actual height; however, I haven't found one MSDN article that specifies that. After reviewing Microsoft DLLs, they do not double the height. I'll let Microsoft set the example. RT_GROUPCUSOR are different.

So, what does this mean for people using your code and gripe about the wrong icon size being loaded for the TaskDialog? Education. Point them to this post. Alternatively, if you choose to use the code I posted, you can test for possible malformated IconDirectories quickly and Debug.Print or show MsgBox during IDE. If this sounds like a plan, suggest testing for that with something like the following:


```
...
        ElseIf ResType = RT_GROUP_ICON Then
            For lRead = 7 To lDataSize - 1 Step 14
                If b(lRead + 1) = b(lRead) * 2 Then Debug.Print "likely malformated icon directory for Icon: "; ResID
            Next
            pvParseRESfile = LookupIconIdFromDirectoryEx(VarPtr(b(0)), True, Cx, Cy, 0&)
        End If
...
```

----------


## fafalone

That seems to be pretty much the same thing my sample project has... it has the ResIconToHICON function that loads a full, multiple entry icon added as a custom resource (much easier than having to compile the resource manually, just so it's in the icon group instead), and chooses the size closest to the one requested in the call; which is 32x32/16x16 for header/footer in the project. 
Does your code just handle external .RES files, or could it do the same manual selection from a DLL with an icon resource group too?



```
Public Function ResIconToHICON(id As String, Optional cx As Long = 24, Optional cy As Long = 24) As Long
'returns an hIcon from an icon in the resource file
'Icons must be added as a custom resource

    Dim tIconHeader     As IconHeader
    Dim tIconEntry()    As IconEntry
    Dim MaxBitCount     As Long
    Dim MaxSize         As Long
    Dim Aproximate      As Long
    Dim IconID          As Long
    Dim hIcon           As Long
    Dim i               As Long
    Dim bytIcoData() As Byte
    
On Error GoTo e0

    bytIcoData = LoadResData(id, "CUSTOM")
    Call CopyMemory(tIconHeader, bytIcoData(0), Len(tIconHeader))

    If tIconHeader.ihCount >= 1 Then
    
        ReDim tIconEntry(tIconHeader.ihCount - 1)
        
        Call CopyMemory(tIconEntry(0), bytIcoData(Len(tIconHeader)), Len(tIconEntry(0)) * tIconHeader.ihCount)
        
        IconID = -1
           
        For i = 0 To tIconHeader.ihCount - 1
            If tIconEntry(i).ieBitCount > MaxBitCount Then MaxBitCount = tIconEntry(i).ieBitCount
        Next

       
        For i = 0 To tIconHeader.ihCount - 1
            If MaxBitCount = tIconEntry(i).ieBitCount Then
                MaxSize = CLng(tIconEntry(i).ieWidth) + CLng(tIconEntry(i).ieHeight)
                If MaxSize > Aproximate And MaxSize <= (cx + cy) Then
                    Aproximate = MaxSize
                    IconID = i
                End If
            End If
        Next
                   
        If IconID = -1 Then Exit Function
       
        With tIconEntry(IconID)
            hIcon = CreateIconFromResourceEx(bytIcoData(.ieImageOffset), .ieBytesInRes, 1, &H30000, cx, cy, &H0)
            If hIcon <> 0 Then
                ResIconTohIcon = hIcon
            End If
        End With
       
    End If
'Debug.Print "Res hIcon=" & hIcon

On Error GoTo 0
Exit Function

e0:
Debug.Print "modIcon.ResIconTohIcon.Error->" & Err.Description & " (" & Err.Number & ")"

End Function
```

----------


## LaVolpe

I do not plan on supporting external DLL icon selection, for the following reasons:
1) no guarantee DLL exists on all computers, in all O/S the code will be run on
2) believe option might be used by like 1% of 'educated' users. The other users might fall into above trap
3) If desire exists to use external icons, then code readily exists to do that and hIcon can be passed to the class instead
4) This is the big one: people that want icons for their own programs, include icons in their resource files to ensure portability

Regarding custom resource entries... I am not suggesting that users create custom resources, just for the IDE WYSIWYG workaround
1) When placed in the CUSTOM section, TaskDialog and other APIs won't be able to find them since they will not exist in the RT_GROUPICON section
2) Users cannot use code like: Set Me.Picture = LoadResPicture(...). APIs like LoadImage will not work for those icons
3) I do allow users to pass a CUSTOM resource, but do not require icons be placed there. 
Unless using rc.exe or a res file hack, understand that 32bit & Vista-PNG icons will probably reside there

My idea for parsing the uncompiled RES file follows this train of thought
1) Inform users of the code how to get WYSIWYG during IDE, uncompiled
2) In IDE only (class tests for running uncompiled). Use the RES path/file if user provided it
3) The call to pass the path/file can be encoded with #IF #ENDIF if desired

P.S. CUSTOM is default, but users can rename it and create as many as they'd like. 
For example, to place AVI files in a resource appropriately named AVI, these easy steps are used
1) Add AVI file to the CUSTOM resource
2) Double click on the newly added AVI item
3) Change the "Type" textbox value to read: AVI
4) Save changes

----------


## fafalone

The way I've set mine up supports either way; custom group icons can be used with an hIcon, but regular icon groups are supported too; you would just set .hInst = App.hInstance and then set the icon to the id. 

Custom resource icons are really the way to go IMO, the only exception being if you don't want to use icons not supported by the VB resource editor. I use PNG's from there too, with a RenderPNG function that can draw it onto an hdc. The only downside is needing gdiplus.. but still seems significantly less complicated than loading from a .rc file. I'm still going to add in support as you outlined... I'm helpless to project bloat; everything these days is being dumbed down and stripped of features. I believe in having as many features as imaginable. You're talking to the guy who 8 years ago started a simple file renaming program, that's now a 50,000 line behemoth with its own mini-language and regular scripting support, parsing beyond anything ever seen, TVDB/TMDB support, and every file-related extra you can imagine, even full support for libraries using IShellLibrary/IShellItem that comes from the dozen or so modern interfaces I added onto olelib.tlb, since I already used IShellFolder, IEnumIDList, IContentMenu, etc. Is it close to done? Hell no, I think of new things to add constantly.

----------


## LaVolpe

On Vista+ you don't need GDI+ for PNG, especially if the PNG is in an icon. CreateIconFromResourceEx will load a PNG-icon as hIcon. In fact, you can use DrawIconEx to render the PNG too.  And if one really wanted to get creative, you can load/render any PNG of 256x256 or less with those APIs if one were to create a icon structure for CreateIconFromResourceEx to use (I'm making an assumption that the API doesn't restrict png-icon to 256x256 only). I'd suspect that LoadImage would also, but on XP and lower, GDI+ or another option is needed.  And I'd like to stress this one more time, I don't feel custom resource is the way to go. VB, APIs and Windows itself will go to the app's resource for loading images. Nothing in the Custom resource section is usable to them

----------


## LaVolpe

> That seems to be pretty much the same thing my sample project has... it has the ResIconToHICON function that...



If you wish to use the API to determine best match (should guarantee same results as window's APIs), you can use/abuse the following code.
Basically, it takes LoadResData() results, converts the IconDirectory to dll format & uses API to choose best match

When I first posted this, no UDTs were used & was kinda hard to read unless one was really fluent with the icon file/dll structures. 
I revisited it, using your UDT structures so it could be followed easier. Hopefully no typos -- air code; I don't use those structures


```
' icoData() is the icon bytes returned from LoadResData() or read from ico file
' desiredSizeX is width: 16, 24, 32, etc
' desiredSizeY as height
    
    Dim lPtrSrc As Long, lPtrDst As Long, lID As Long
    Dim icDir() As Byte, LB As Long
    Dim tIconHeader As IconHeader
    Dim tIconEntry As IconEntry

    LB = LBound(icoData) ' just in case a non-zero LBound array passed
    ' convert 16 byte IconDir to 14 byte IconDir
    CopyMemory tIconHeader, icoData(LB), Len(tIconHeader)
    ReDim icDir(0 To tIconHeader.ihCount * Len(tIconEntry) + Len(tIconHeader) - 1&)
    CopyMemory icDir(0), tIconHeader, Len(tIconHeader)
    lPtrDst = Len(tIconHeader)
    lPtrSrc = LB + lPtrDst
    For lID = 1& To tIconHeader.ihCount
        CopyMemory tIconEntry, icoData(lPtrSrc), 12& ' size of standard tIconEntry less last 4 bytes
        tIconEntry.ieImageOffset = lID
        CopyMemory icDir(lPtrDst), tIconEntry, 14&     ' size of DLL tIconEntry
        lPtrDst = lPtrDst + 14&: lPtrSrc = lPtrSrc + Len(tIconEntry) 
    Next
    lID = LookupIconIdFromDirectoryEx(VarPtr(icDir(0)), True, desiredSizeX, desiredSizeY, 0&)
    Erase icDir()
    If lID > 0& Then
        CopyMemory tIconEntry, icoData(LB + (lID - 1&) * Len(tIconEntry) + Len(tIconHeader)), Len(tIconEntry)
        hIcon = CreateIconFromResource(VarPtr(icoData(LB + tIconEntry.ieImageOffset)), tIconEntry.ieBytesInRes, True, ICRESVER)
    End If
```

Here are my API declarations. Pay attention to how I used ByRef & ByVal


```
Private Declare Function CreateIconFromResource Lib "user32.dll" (ByVal presbits As Long, ByVal dwResSize As Long, ByVal fIcon As Long, ByVal dwVer As Long) As Long
Private Declare Function LookupIconIdFromDirectoryEx Lib "user32.dll" (ByVal presbits As Long, ByVal fIcon As Long, ByVal cxDesired As Long, ByVal cyDesired As Long, ByVal Flags As Long) As Long
Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
```

----------


## fafalone

How is ICRESVER set? Per MSDN I set it to &H30000 and the function seems to work for both LoadResData and an external .ico, but I'm wondering under what scenarios it would be different and how to determine that.

(also typo: CopyMemory icoDir instead of icDir)

PS- Since you're really familiar with icons and we're on the topic; any quick way to lookup actual ID from the sequential index when loading icons from a DLL file? I made an icon browser and quickly realized they don't match, and was going to try to avoid the rather complex way to load them 'properly' by reading the resources manually.

----------


## LaVolpe

Yes ICRESVER  is &H30000. As for difference between your current manual icon selection logic and how Windows does it, refer to this link & jump down to the section titled "Choosing an Icon"

Icons in a DLL really don't have a sequential index, per se. Icons in a DLL are grouped by RT_GROUP_ICON. These groups have names that are either integer values or string values. It is up to the resource compiler how they are named. For example, adding an icon to VB res file, VB typically names 1st one 101. That is the Group name. You can change it to anything you want. Within each Group, there will be 1 to several individual icons. These always have a numerical/integer ID and the Group's IconDirectory has, as the last member of each IconDirEntry, the ID.

So, within a DLL, unless one wants to manually parse it, ugh!, you would use EnumResourceNames API, looking for RT_GROUP_ICON entries. Within the callback procedure, you'd keep count of how many Groups there . Now, you could consider those sequential indexes. 1st Group found is 1, 2nd is 2, etc. During this enumeration, you could keep a reference of actual Group Name per index. 

Then if wanting a specific "index" and you have the actual RT_GROUP_ICON name:
1) Use LoadImage API to create icon
2) Use LoadResource (plus other APIs) to get ICONDIR, then LookupIconIdFromDirectoryEx for iconID, then LoadResource for iconID, & finally: CreateIconFromResourceEx

If you don't have the actual RT_GROUP_ICON name, then:
1) Call EnumResourceNames and on the nth iteration (equivalent to "index") within the callback routine, stop enumeration & cache the actual Group name 
2) With the Group name, previous option above applies

The individual icons are sequentially indexed in the DLL, whether some indexes may be skipped or not, I don't know (compiler decisions). But their index really has no direct relationship to the Group they belong in. Usually the 1st individual icon is indexed #1, 2nd is #2, etc. There is no guaranteed that #1 and #2 are from the same Group or if they are in the same Group, they are in the same order as in the Groups IconDirectory (usually they are).

So, using an entry in VB res file for example: User added 2 icons,  each contains 3 images: 16x16 32x32 48x48 and named the entries 101, MYICON repsectively
1st RT_GROUP_ICON name: 101
RT_GROUP_ICON directory will have 3 indexes: 1, 2, 3
RT_ICON with name of 1 will be 1st image in that group, 16x16
RT_ICON with name of 2 will be 2nd image in that group, 32x32
RT_ICON with name of 3 will be 3rd image in that group, 48x84
2nd RT_GROUP_ICON name: MYICON
RT_GROUP_ICON directory will have 3 indexes: 4,5,6
RT_ICON with name of 4 will be 1st image in that group, 16x16
RT_ICON with name of 5 will be 2nd image in that group, 32x32
RT_ICON with name of 6 will be 3rd image in that group, 48x84

*NOTE: The order of the RT_GROUP_ICONs above is a guess. Whether the compiler writes 101 before MYICON or vice versa is unknown & may depend on compiler criteria/choice

When that project is compiled, EnumResourceNames will return 2 RT_GROUP_ICON entries: 101 & MYICON. And EnumResourceNames will return 6 RT_ICON entries: 1-6

Unless you absolutely need to enumerate the DLL, it should be far simpler to use LoadImage if you know the RT_GROUP_ICON name. You'll most likely want to use LoadLibraryEx first (with the LOAD_LIBRARY_AS_DATAFILE constant), to retrieve the hMod parameter for LoadImage. LoadImage allows loading by Group ordinal (Integer name value) or by Group name as string (either text like "MYICON" or ordinal preceded by pound: "#101")

----------


## fafalone

Ah yes that's the really complicated way  :Smilie: 

I was using


```
Dim hr As Long, lCount As Long
lCount = ExtractIconEx(sCurIconFile, -1, 0, 0, 0)

ReDim glLargeIcons(lCount)
ReDim glSmallIcons(lCount)

For l = 0 To lCount - 1
    hr = ExtractIconEx(sCurIconFile, l, glLargeIcons(l), glSmallIcons(l), 1)
    Call ImageList_AddIcon(himl, glLargeIcons(l))
    Call ImageList_AddIcon(hIMLs, glSmallIcons(l))
    
    Call DestroyIcon(glLargeIcons(l))
    Call DestroyIcon(glSmallIcons(l))
Next l
```

Extracts all the icons and shows the 32x32/16x16 icons perfectly; but it goes sequentially.

----------


## LaVolpe

Yes, ExtractIconEx would be easier and at a small cost of not being able to dictate sizes other than small/large.

Since it is doing this sequentially & I validated that, I wouldn't be surprised if, internally, it doesn't do exactly how I'd do it:
1) EnumResourceNames using RT_GROUP_ICON
2) For each group: call LoadImage with the RT_GROUP_ICON name
Not really a lot of work, other than setting up the callback function. My alphaimage control uses a similar method

Oh, one drawback with using this method with the TaskDialog.

I know you are aware that when setting the header/footer icons, you can opt for preset icons (i.e., exclamation, shield, information, etc). But not sure you are also aware that you can use the RT_GROUP_ICON name of any icon within the imageres.dll; only stipulation is that dialog config's .hInstance is zero & of course dwFlags doesn't say to use hIcon. By using the actual group name, TaskDialog will extract the icon for you. If you choose to replicate that functionality manually, guess just a matter of customer education that they know to provide the sequential index of the imagres icon vs. the icon's name/ordinal

Edited: Actually that API can load icons by their ordinal within the dll. Want icons from RT_GROUP_ICON #5? Pass -5 as the icon to extract. Documentation doesn't indicate the API can extract string named groups; not that they are that common, but do exist. Vista's imageres.dll even has one.

Last but no least. If you google update or customize imageres.dll, you will find lots of hits regarding customizing that dll. If trying to provide icons from the dll by sequential indexing, no guarantee that the icon will be in the same spot on everyone's pc. Guess it is possible though that the original imagres Group names could be modified by someone recompiling that dll. I do plan on educating users that they can use that dll for selecting icons; however, it will come with 2 strong caveats:
1) As mentioned above -- no guarantee the icon they selected on their system will exist on some target system due to customization of the dll
2) Strongly suggest using icons from the Vista imageres.dll as newer O/S will likely not remove any, but append lots of new ones
I'm not at that point yet, but did want to do a comparison between those dlls on Vista & Win7 and see if my assumption is correct. If not correct, may not even offer that option

----------


## LaVolpe

Just FYI. I was playing with the idea of allowing icons to be added to buttons. 
TaskDialog allows a single icon, the UAC elevation icon. But thought it could be a nice touch to allow option for icon-only, text+icon, or text-only button captions. 

Downside: On Vista at least, cannot reliably cross-reference a TaskDialog button ID with the actual button window handle in order to use SendMessage bm_setimage as we need the button's hWnd. The dialog does not assign button/control IDs to the actual buttons it creates, they all have the same value as zero (i.e., GetWindowLong(GWL_ID)=0 ). There are ways to synchronize these, but are not 100% reliable. Obviously, dialog keeps an internal cross-reference between the Button ID assigned via the config structure & the button's actual window handle

1) EnumChildWindows of the dialog, looking for all "Button" class windows that are not option buttons. Get their text & compare that to the captions assigned during setup
-- Option buttons are "Button" class windows also, but their style can be validated via GetWindowLong() to distinguish them from push-button & command-link
-- logic flaws: 1) what if someone changes button text outside of your control 2) for comparison, how to know the captions for common buttons that may be using different languages
2) EnumChildWindows of the dialog, using ZOrder. Custom buttons displayed in order of first added, first displayed. Common buttons follow in a specific order
-- in other words, if 4 physical non-optionbuttons exist, then 0 or more are custom buttons, while the remaining, if any, are common buttons
-- logic flaws: 1) what if someone added/removed button outside of your control or tweaked the ZOrder? 2) What if later versions of dialog add more common buttons or changes display order?

Maybe Win7 or later assigns button IDs to the actual button windows and/or includes a custom message to be sent for assigning custom graphics. Until then...

----------


## fafalone

Hmm.. maybe a Vista-only limitation? 

On Win7 I took a look and Spy++ shows it's a Button class, gets the caption, and shows a unique non-zero handle for each button. I had briefly looked into this where others had asked, and the answer was a resounding 'no' and to use a very nice emulated version. Additionally it shows the same class and unique handles for CommandLinks... so maybe both are possible (!).

I'd definitely like to have that feature, let me see if I can get it to respond to BM_SETIMAGE.

----------


## LaVolpe

Well, heck yes then. Make it a Vista-only limitation in code & easy enough to check when GetWindowLong(GWL_ID)=0
Now the assumption is that the ControlID value can be related back to the assigned Button ID. Would think the common button control IDs would be equivalent to the windows constant, i.e., Ok = vbOk = IDOK

Glad I didn't destroy all my code relating to button captions  :Smilie: 

*Edited*: Maybe joyful too soon? Unique handle is expected as every window handle must be unique. What we should be looking for in Spy++ is the ControlID value, not the hWnd

----------


## LaVolpe

fafalone, did it & believe it is truly reliable regarding building cross-reference between dialog buttons' hWnds and the configuration structure's button IDs.
You may not want to wait around until I post my project, so I'll give you the outline & you can choose whether or not you want to pursue it also

The idea is kinda like a teacher with a student list but not know who the students are. Call out their name & wait for the answer back. The button's ID is the name & the subclassed WM_ENABLE message results in the answer back.

1) During TDN_CONSTRUCTED/NAVIGATED, I call EnumChildWindows to return each window on the dialog
-- For each child window on the dialog's hWnd, I filter out all but just Buttons, but also exclude option buttons (not supporting images on those)
-- For each button (normal or link), I cache it's original window procedure to the button's hWnd with SetProp, then subclass the window to my procedure
2) When EnumChildWindows returns, all buttons I care about are subclassed. At this point, dialog isn't shown yet
3) I loop thru each custom & common button and send a TDM_ENABLE_BUTTON message to disable the button (otherwise if enabled & enabling it, it is ignored)
-- when the button receives a WM_ENABLED message in my procedure, I update a class-level variable with the button's hWnd & within that procedure unsubclass the button
-- when the SendMessage call returns, if the hWnd value is not zero, then 
:: I store that hWnd to my cross-reference list for the button ID I just disabled & reset that variable for next button
:: I also call RemoveProp to undo what I did in step #1 above
-- at this point, I re-enable the button (unless tagged as disabled during setup) and set the icon (if one tagged during setup)
-- note that preset/common button IDs are set by system, but are system constants: Ok = IDOk, Yes = IDYes, etc
4) When the loop is complete, I call EnumChildWindows one more time. It is looking for the same buttons as the first time it was called
-- this time instead of subclassing buttons, it is calling GetProp to see if the property set in step #1 is still on the button
:: If property still exists, then didn't unsubclass, so do that now & remove the property
5) Clean up complete. Button ID-to-hWnd reference complete. Now I can use any BM_xxx messages on the buttons

P.S. After icon is set, disabling the button later on, does indeed render the icon disabled too. Cool

----------


## fafalone

Going to cogitate on a less intense way of matching the hWnds, but yeah I'm definitely going for it... might or might not limit support to custom buttons depending on how things go.

Also something neat to consider, if you're using the CommandLink style buttons, it will allow different sized icons and show them at their correct size. So you can pick the 32x32 size and it shows at that size on CL's, or you can use 16x16 and it shows at that size too. Larger icons will even show... but the bottoms get clipped off if you have multiple buttons-- you need to add lines. So I'm going to have to add a check to see if the given button is a commandlink, and select the bigger size- probably add a flag like TDF_CLICONSIZE_32 / 48 to allow the user to choose whether they want the bigger icon displayed. Can't just always pass a big icon; it will not scale down to 16x16 if it's on a normal button.



As you can see, it's not upscaled, it's using the full 48x48 icon.

Edit: So the method I'm trying out now; I'm going to operate under the scenario that the enumeration order matches the constructed order, which matches the button array order (common buttons are done in a particular order too). Will investigate scenarios where this might be false.. but for now I'm not keen on setting up a whole subclassing apparatus. It remains true with TDF_RTL_LAYOUT.

Edit2: Going good so far. Custom buttons are up and running. Radio buttons are always drawn first, so I added an offset if they exist.


Edit3: Took an hour, but got the logic of supporting the common buttons worked out. Was headache-inducing and would have probably been easier to subclass. But it's all working now. Even adding in an option to load it from shell32 or imageres, which is pretty simple since I'm already using them as a main/footer icon option. Simply added a flag to indicate the hIcon should be interpreted as a LoadImage call. Also added a SetWindowsButtonIconSize sub for bigger commandlink icons with this too.



Process used:
AddButton accepts hIcon, stored in separate array of same dimensions of total buttons
Enum on created/navigated populates m_hButton(), handily enum api has the lParam to pass an objptr to make this callback work identical to the main callback
SetButtonIcons loops through icon array setting non-zeros to the corresponding index in hwnds.

Behavior note: BM_SETIMAGE works on CommandLinks even if TDF_USE_COMMANDLINKS_NO_ICON is specified.

PS- How did you get the colored background without a shield icon?

PS2 - You mentioned 'BM_xxx'... sounds like you're thinking of going further... why not have dropdown buttons?

Oh boy.

----------


## LaVolpe

I originally thought going the same route: custom buttons displayed before common buttons & assume order is what is expected. Opted out simply because I didn't want the code to foul up if a different version of the API changed things about. The subclassing approach should be 100% since I am using the API to fire off a message by button id & getting the hWnd from the button that responds to it.

Note: normal buttons will take any size icon also, without scaling, but obviously anything > 16x16 won't display correctly on a normal size button. My code offers user-defined sizes for button icons also, as was already aware that they are not scaled by the dialog.

Regarding other BM messages: was thinking about split buttons, but not sure I am going that route... I do use SetWindowText to offer button caption changes at runtime

The colored header? Niffty but limited. A few undocumented preset icons (I don't have the code in front of me, but believe they are -6, -7, -8, -9 (may be off by 1). The colors are blue, red, green, yellow, gray. When one of those icons assigned, header only, header gets filled in & applies the associated icon. To replace the icon simply set the header icon to another icon or zero to remove it. But see TDM_UPDATE_ICON documentation for restrictions

----------


## fafalone

Oh ok. Thought there something I was missing to get it like that with normal setups. 

----

Project officially updated with icon button support, and native executing of links if you want the default behavior (event is still fired for customization).

----------


## fafalone

Forgot to ask..have you looked at the enumwindows and bm_setimage on vista? Are the button hwnds not returned by enumchildwindows?

----------


## LaVolpe

hWnds are always returned, regardless of the system. The only way a 'control' hWnd won't be returned is if the control is not an actual window but is owner-drawn on the DC, kinda like VB's Image & Label controls.

What would've made any workarounds completely unnecessary is if the dialog would've put the button IDs in the control's ControlID property, then we could've just used GetWindowLong(GWL_ID) within EnumChildWindows to return the hWnd for each button ID.

The screenshot I posted with the icon-only sample was on Vista.

----------


## LaVolpe

Curious, have you found a clean way of displaying the command links without icons & without the green arrows?

My workaround works, but feel it should be easier. Again, maybe just a Vista thing? But the flag TDF_USE_COMMAND_LINKS_NO_ICON doesn't work. 

I created a 1x1 4bit 100% transparent icon (B&W, 1 bit, didn't work for whatever reason). Code was modified a bit further to allow user-defined width so that if say a 32x32 icon was used on 1 or more command links, then a 32bit wide transparent icon could be used for command links with "no icons" so that all the link captions have the same left margin

----------


## fafalone

The flag works fine on my system (Win7 x64).. and MSDN makes no mention of it being new... so I'm not sure what the issue is for you.

----------


## LaVolpe

Never got it to work from day 1. I'll have to try on Win7 another time. Probably an early bug & not fixed until Win7? I've read other posts on the net of ppl having similar issues

*Edited*: Got it to work & found the problem.  When both that flag and the TDF_USE_COMMAND_LINKS were added, the latter overrode the former. Now that I see how these links captions don't line up when one has an icon and one doesn't, gotta admit, I like my 'transparent icon" solution to align them up

----------


## ChenLin

So the magic of the project, why not update it? This is the final version of it?

----------


## fafalone

All of the native features of task dialogs have been implemented, so anything else would be another customization. 

I'm definitely open to suggestions though if there's something you'd like to see the class be able to do natively. Or if you can suggest an easier way to implement some of the more complex features, I know some of it is a little tough. And of course I'll fix any bugs that are identified.

Let me know  :Thumb:

----------


## Tech99

Hi fafalone,

tested this in one windows 10 workstation. Generates an error
453 Can't find DLL entry point TaskDialogIndirect in comctl32.dll.

Question - any idea what might cause of this (possibly manifest error)?

Edit... Yes, that was the reason for the error.. Forgot to add manifest to project and then compiled exe didn't use comctl 6 version and was unable to find entry point.

----------


## fafalone

Yeah even direct API calls need a manifest the way they set it up for backwards compatibility.

---------------------------------------------
Unrelated, but in the main post is everyone else seeing tiny images (besides the header image)? When I first made it they appeared full sized, and the images themselves haven't changed.. but now they're tiny on both Firefox and Chrome. Is this a Firefox change or a VBForums change, and could it be fixed without giving up the table layout?
Edit: Nevermind, figured out I could fix it by using td="width: 75%"

----------


## fafalone

> [...]
> The colored header? Niffty but limited. A few undocumented preset icons (I don't have the code in front of me, but believe they are -6, -7, -8, -9 (may be off by 1). The colors are blue, red, green, yellow, gray. When one of those icons assigned, header only, header gets filled in & applies the associated icon. To replace the icon simply set the header icon to another icon or zero to remove it. But see TDM_UPDATE_ICON documentation for restrictions


Revisited this today; have you actually tried replacing it with another icon? It doesn't actually seem to work. Passing 0 to get no icon works fine, but passing another icon index doesn't; I tried a bunch of different values, making sure to pass through MAKEINTRESOURCE, and even tried without doing that. Just curious if it worked for you and I'm missing something?

Edit: Nevermind, was missing that I had been defaulting the hInstance to App.hInstance instead of 0. 
That leaves the big question; how to replace it with a custom icon via an hIcon? You can definitely get a custom icon in there by using App.hInstance since the gradient is part of the system resource it loads.. but given the pain involved in getting nice icons into VB resource files (as icons, specifically the icon group used here), maybe its easier.

Shield icon from gradient replaced with custom icon stored in application resource file:

----------


## putu.oka

Hi, how i can implement your td.res resource file in my existing project becuase i already have *.res file on my project and vb not allowed to add more than one *.res file.
Thanks

----------


## fafalone

If you're using La Volpe's manifest creator you can select an existing resource file instead of creating a new one; that will insert the manifest into your resource file, or you can do that manually with another resource editor.

----------


## fafalone

Ok, so I decided that no, I have not gone far enough with rarely used features. I want to move forward with making dropdown menu buttons work.
Ran into a problem though;
I'm not getting BCN_DROPDOWN in the WndProc after subclassing the taskdialog's hwnd to it.
If use SetParent to make sure that's the immediate parent, not only do I still not get BCN_DROPDOWN, but it breaks the button (left click doesn't close the dialog, and even if i did that manually it would be a bigger pain because i'd have to manage the return codes)
Heck I'm not even getting BCN_DROPDOWN when I subclass the button hwnd itsself.

Edit: Muahaha I got it. For some reason, while my normal split buttons fire a WM_NOTIFY with BCN_DROPDOWN, with the task dialog I don't get that but do get BCM_SETDROWNDOWNSTATE, which is all I need:

----------


## Steve Grant

Hi Fafalone, like many others, I am using this constantly now. However I really could use a Textbox to input a string as well. The Inputbox is so old fashioned and I have seen a textbox on a friends CodeJock version of the TaskDialog. Any chance?

Steve.

----------


## fafalone

There's no native option to have a textbox, so it's probably a custom control. Bolting a textbox onto the Windows task dialogs is certainly possible, but in a stable, reliable manner? Tricky, to say the least. But the even bigger problem would be positioning. Figuring out where the box could be placed given the vast array of options and changing number of lines based on text length? 
Really far better off just making a regular form, although I'll look into maybe doing something like replacing the content area with one? or better ideas?

*Edit:* 
I was able to work out adding one in a stable manner; it was a little tricky because it was locked and ugly when I tried to create it as a child of the main taskdialog window, but it worked perfectly when I tied it to the DirectUIHWND class (the class already has an EnumChildWindows call for other features),


Also set it up to return like the other results; i.e. .ResultMain .ResultVerify .ResultRadio and now .ResultInput too
Pressing Enter even works, returning with the current/default button.
So now the biggest challenge left is figuring out where to position it. How to set it to be in what location and present this as a user-friendly option.
Suggestions?



```
        hEditBox = CreateWindowEx(WS_EX_CLIENTEDGE, "Edit", "", _
                      WS_VISIBLE Or WS_CHILD Or WS_BORDER Or ES_LEFT, _
                      20, 100, 150, 22, _
                     hUI, IDD_EDIT1, App.hInstance, ByVal 0&)
        Dim hFont As Long
        hFont = GetStockObject(DEFAULT_GUI_FONT)
        If hFont Then
            SendMessageW hEditBox, WM_SETFONT, hFont, ByVal 0&
        End If
```


*Edit 2:*
I've got some good ideas going now, check this out:
Default placement, without a main instruction:

and with:

Only requiring the addition of a custom flag, .Flags = TDF_INPUT_BOX

Then check this bad boy out:


Made with


```
With TaskDialogPW
    .Init
    .MainInstruction = "Authorization Required"
    .Content = "The password is: password"
    .Flags = TDF_INPUT_BOX
    .InputIsPassword = True
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .SetButtonElevated TD_OK, 1
    .SetButtonHold TD_OK
    .Footer = "Enter your password then press OK to continue."
    .IconFooter = TD_INFORMATION_ICON
    .IconMain = TD_SHIELD_ERROR_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog


Private Sub TaskDialogPW_ButtonClick(ByVal ButtonID As Long)
If ButtonID = TD_OK Then
    If TaskDialogPW.InputText = "password" Then
        TaskDialogPW.CloseDialog
        'and do whatever
    Else
        TaskDialogPW.Footer = "Wrong password, please try again."
        TaskDialogPW.IconFooter = TD_ERROR_ICON
    End If
End If
End Sub
```

I still don't really know what to do about figuring out placement elsewhere, which can be really important if there's no footer area or common buttons to align it with (how it's being done now)
And even with this placement; it won't work with the Verify checkbox or the dropdown for more info control..

Ok so I think I've got it worked out... there's 3 alignment options.. the one above, aligned to the buttons

this one, aligned to the footer icon (or just plain bottom if there is no footer, but it's meant to be used with the icon)


and what I'm planning on making the default, aligned to be just above the common buttons and width-aligned with the dialog, content-align:

----------


## Steve Grant

I went round to see my mate just now to get him to show the Dialog with a Textbox for input and get it fresh in my mind. His textbox stays in the white area just below the Content Text. This leaves the space to the left of the buttons for the Checkbox. I tried putting a long string of text in the content and the textbox just moved down (always in the white though) as it should.

They say a picture is worth a thousand words so;

----------


## fafalone

Yup that's precisely how the default alignment option I've set works (I think I put in that edit with the 3-alignment-option pictures after your post, so re-read above too). It ensures there's always one line break to make sure the edit box isn't on top of the text; alignment like that would just be two. My textbox position is based on the buttons, so it doesn't matter how many lines the content text takes up; it will just always be there in the white space between the end of content and the gray area.

Here's how it would look as of right now:



The only real difference is the size of the gap (besides the theme elements; I'm on Win7, the buttons, check, and title will look like yours on Win8+)... but I can't adjust it any further upwards without a major complication to detect a double-crlf and adjust a whole bunch of alignment stuff accordingly.
So hopefully it's ok with that spacing.

*Edit* - ok ok I made the adjustment, now if it ends with a double-break it will align like this


Simple to call:


```
With TaskDialog1
    .Init
    .MainInstruction = "Duplicates"
    .Content = "If you want to exclude an Artists name from the search:" & vbCrLf & vbCrLf
    .Flags = TDF_INPUT_BOX Or TDF_VERIFICATION_FLAG_CHECKED
    .AddCustomButton 100, "Continue"
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .IconMain = TD_SHIELD_ICON
    .Title = "cTaskDialog Project"
    .InputText = "Enter Artist name here." 'Will be selected by default
    .VerifyText = "Exclude Jingles"
    .ParenthWnd = Me.hWnd
    .ShowDialog
End With
```

Should be able to post the update later today, just gotta clean things up, now that I'm done with a massive fall down the rabbit hole where I made the position automatically adjust to an expanded info click-- much much harder than it seemed going in, but now working.

----------


## fafalone

*cTaskDialog 0.7 Released!*
Two new features:
The first is just a minor option, a custom flag, TDF_KILL_SHIELD_ICON to remove the shield icon but keep the gradient background.

But the big one: INPUT BOXES!
Now you can take advantage of a wide range of TaskDialog options with a TextBox to retrieve user input, with the new custom flag TDF_INPUT_BOX

First, a basic input box with the default positioning:


```
With TaskDialog1
    .Init
    .MainInstruction = "Hello World"
    .Content = "Input Required"
    .Flags = TDF_INPUT_BOX
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .IconMain = TD_INFORMATION_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog

    Label5.Caption = .ResultInput
    If .ResultMain = TD_OK Then
        Label1.Caption = "Yes Yes Yes!"
    Else
        Label1.Caption = "Cancelled."
    End If
End With
```




We can also position it next to the buttons:


```
With TaskDialog1
    .Init
    .MainInstruction = "Input Required"
    .Content = "Tell me what I want to know!" & vbCrLf '& vbCrLf
    .Flags = TDF_INPUT_BOX
    .InputAlign = TDIBA_Buttons
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .IconMain = TD_INFORMATION_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog
```




The last placement option is to use it as footer input. It's designed to use the normal footer icon, but have a textbox instead of a label:


```
With TaskDialog1
    .Init
    .Content = "Something somesuch hows-it what-eva" '& vbCrLf
    .Flags = TDF_INPUT_BOX 'Or TDF_USE_SHELL32_ICONID
    .InputAlign = TDIBA_Footer
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .IconFooter = TD_INFORMATION_ICON
    .VerifyText = "Check here if you want to provide extra info below:"
    .Title = "cTaskDialog Project"
    .Footer = "$input"
    .ParenthWnd = Me.hWnd
    .ShowDialog
```




Here's a practical example showing it with default text (which is pre-selected automatically):


```
With TaskDialog1
    .Init
    .MainInstruction = "Duplicates"
    .Content = "If you want to exclude an Artists name from the search:" & vbCrLf & vbCrLf
    .Flags = TDF_INPUT_BOX Or TDF_VERIFICATION_FLAG_CHECKED 'Or TDF_USE_SHELL32_ICONID
    .AddCustomButton 100, "Continue"
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .IconMain = TD_SHIELD_ICON
    .Title = "cTaskDialog Project"
    .InputText = "Enter Artist name here."
    .VerifyText = "Exclude Jingles"
    .ParenthWnd = Me.hWnd
    .ShowDialog
```

Note the spacing; there's always a single line break like the first one (appended if not present), but using a double-break relaxes the spacing a bit.


If CommandLinks are being used, the default alignment (content) places the textbox between the content and the first commandlink:


```
With TaskDialog1
    .Init
    .MainInstruction = "You're about to do something stupid."
    .Content = "First, tell me why?"
    .IconMain = TD_INFORMATION_ICON
    .Title = "cTaskDialog Project"
    .Flags = TDF_USE_COMMAND_LINKS Or TDF_INPUT_BOX
    .AddCustomButton 101, "YeeHaw!" & vbLf & "Put some additional information about the command here."
    .AddCustomButton 102, "NEVER!!!"
    .AddCustomButton 103, "I dunno?"
    
    .ShowDialog
```

There's also compatibility with the expanded-info control in the default positioning. The position of the box is automatically adjusted when the expando control is clicked.


Finally here's a detailed practical application that shows some more features. The textbox can be set as a password box. We can then combine that with a button hold, so now when enter or ok is pressed, we check the input and only let OK execute if it's a match- while cancel still closes it right away.


```
Set TaskDialogPW = New cTaskDialog
With TaskDialogPW
    .Init
    .MainInstruction = "Authorization Required"
    .Content = "The password is: password"
    .Flags = TDF_INPUT_BOX
    .InputIsPassword = True
    .InputAlign = TDIBA_Buttons
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .SetButtonElevated TD_OK, 1
    .SetButtonHold TD_OK
    .Footer = "Enter your password then press OK to continue."
    .IconFooter = TD_INFORMATION_ICON
    .IconMain = TD_SHIELD_ERROR_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog
```

Then we need to handle the ok button click to check the password:


```
Private Sub TaskDialogPW_ButtonClick(ByVal ButtonID As Long)
If ButtonID = TD_OK Then
    If TaskDialogPW.InputText = "password" Then
        TaskDialogPW.CloseDialog
    Else
        TaskDialogPW.Footer = "Wrong password, please try again."
        TaskDialogPW.IconFooter = TD_ERROR_ICON
    End If
End If
End Sub
```






That about covers the main features. One last minor thing, the CueBanner can be set with .InputCueBanner, but isn't visible at first since focus is set on the textbox so the user can start typing right away without clicking first.

----------


## Steve Grant

OMG! You are amazing Faf! Talk about excellent service. That is exactly what the doctor ordered, with one tiny caveat. Can I be cheeky and ask for the textbox to have both its current 3D look and also like the picture I submitted which is just flat with a blue (I think) border.

Even if you choose to ignore my cheek  :Roll Eyes (Sarcastic): , very well done from the UK.

Steve.

----------


## fafalone

Well THAT level of detail I hope I can at least leave to you... especially since it's just a standard edit control that appears different in different versions of Windows. In the class' AddInputbox sub, the box is created with a CreateWindowEx call. Instead of WS_EX_CLIENTEDGE, try some other styles, 0 for completely flat.. WS_EX_STATICEDGE seems slightly thinner, maybe that's it? You can get all sorts of borders between that and the regular WS_ styles that go with WS_CHILD et al.
What OS are you on, I'd have to look on that.

And it was a great idea to have an input box... can't believe I hadn't thought of that when a few posts ago you saw me worrying about dropdown button menus (saving those for the next version!). As soon as you mentioned that I knew it must happen right away.

Edit:
Actually, it looks like just removing ES_BORDER should do the trick. That looks more like normal textboxes. I'm going to update the class like that...
Old: 
New:  Win8: 

Yeah the border on Win8 comes out slightly darker... I don't have a Win10 VM set up yet but if normal textboxes look like yours, then so should this.

----------


## Steve Grant

Win 10 Home 64. VB6 Pro SP6: That is perfect now thank you. TaskDialogINdirect, ITaskbar3, Dil's notify ctrl that pops out the Win 10 info bar. Who needs CodeJock!!!

Bless you mate - thank you.

Steve.

----------


## Steve Grant

Hi Faf, some of the things you said above made me think. I now wonder if a Combobox would not be better than a Textbox. You still get the benefits of a Textbox but if you need to present a list then you get the advantages of a Combo. What do you think? Here is a picture of a form I created to dummy it up.

----------


## fafalone

What do I think?

It must exist! Stand by. Will make it a ComboBoxEx with images too. At least one good point though is a lot of the code can be copied from the textbox.

*Edit*
So far so good...



```
himlSys = GetSystemImagelist(SHGFI_SMALLICON)
With TaskDialog1
    .Init
    .MainInstruction = "Duplicates"
    .Content = "If you want to exclude an Artists name from the search:" & vbCrLf & vbCrLf
    .Flags = TDF_VERIFICATION_FLAG_CHECKED Or TDF_COMBO_EDIT
    .AddCustomButton 100, "Continue"
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .IconMain = TD_SHIELD_ICON
    .Title = "cTaskDialog Project"
    .ComboCueBanner = "Cue Banner Text"
    .ComboImageList = himlSys
    .ComboAddItem "Item 1", 2
    .ComboAddItem "Item 2", 3
    .ComboAddItem "Item 3", 4
    .VerifyText = "Exclude Jingles"
    .ParenthWnd = Me.hWnd
    .ShowDialog
```

I've even set things up so that you can have both a combo and a textbox, since there's 3 different positions...

 or 

I think I need to add some sizing options...

Was thinking... i *could* probably add autocomplete too... but it would have to support custom lists... so that would be a large expansion and a dependency for oleexp. Think I'll leave off the main class, but I am going to expose properties for the hWnd's, so it could be attached if someone wanted to go down that road.

====
And I just thought of another idea.. still OTHER controls- was thinking an option for a calendar control would be really useful in particular. With the way I re-did the code for the combo, adding new ones in the same position configurations is standardized now.. a lot of work, but set up so it's fairly modular; can drop a new control in by just adding the flag, slightly changed create routine, and an additional set of variables/properties.

*Edit*
Yeah totally adding a datetime option too.


*Edit2*
Going crazy now... and now up to 2500 lines after adding support for (date or time) AND date + time,


and of course it's set up like the combo, something that can be added on:

----------


## Steve Grant

That looks great!! Here's the things I pick up in your screenshots (allowing for the fact this is just proof of concept).

The Combo dropdown needs to be width adjusted for long items. Code Below.
The control in the footer should be spaced away from the Icon, or, of course, we simply don't use the icon at this time.
Need to ensure the dialog doesn't bloat too much. I use this ALL the time now and, for a simple program, don't want it to increase the size of the program disproportionately.



```
Option Explicit
Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Private Sub AutoSizeDropDownWidth()
    Dim i As Integer, lTempWidth As Long, lWidth As Long
    Const CB_SETDROPPEDWIDTH = &H160
    
    If cboDialog.ListCount = 0 Then Exit Sub

    'Get the width of the largest item
    For i = 0 To cboDialog.ListCount - 1
        lTempWidth = cboDialog.Parent.TextWidth(cboDialog.List(i)) 'Ensure that form has the same font/size as combo
        If (lTempWidth > lWidth) Then lWidth = lTempWidth 'Store the longest length in lWidth
    Next
    
    lWidth = (lWidth \ Screen.TwipsPerPixelX) + 20 '+20 to allow for vertical scrollbar
   
    'don't allow drop-down width to exceed screen.width
    If lWidth > Screen.Width \ Screen.TwipsPerPixelX - 20 Then lWidth = Screen.Width \ Screen.TwipsPerPixelX - 20
    SendMessage cboDialog.hWnd, CB_SETDROPPEDWIDTH, lWidth, 0
End Sub
```

Many thanks - Yet again - Steve.

----------


## fafalone

The size is really a concern? The difference between 1000 lines and 10000 lines is a few KB, probably smaller than a single icon. (also, this size is par for the course for common controls... e.g. krool's usercontrol for a simple TextBox is 1900 lines, and 6700 for a ListView... so even if I hit 3000 i'd hope you wouldn't avoid the class for that)

What I am doing is keeping the usage simple, and each thing that's added uses the same simple setup and config... nothing new effects anything old, so if you're not using something you won't be inconvenienced by it. And the way it's designed, very little additional code is actually executed if one of these new features isn't being used.

 (edit: ok, I fixed the spacing. And a weird bug where if you used an edit control in the footer, but no footer icon, the bottom half would be cut off)


```
himlSys = GetSystemImagelist(SHGFI_SMALLICON)
Dim hIconF As Long
hIconF = IconToHICON(LoadResData("ICO_CLIP", "CUSTOM"), 16, 16)
With TaskDialog1
    .Init
    .MainInstruction = "Schedule Event"
    .Content = "Pick action to schedule. You can also set a name below." & vbCrLf & vbCrLf
    .Flags = TDF_DATETIME Or TDF_COMBO_LIST Or TDF_INPUT_BOX Or TDF_USE_HICON_FOOTER Or TDF_KILL_SHIELD_ICON
    .DateTimeType = dttDateTime
    .DateTimeAlign = TDIBA_Buttons
    .ComboSetInitialItem 0
    .ComboImageList = himlSys
    .ComboAddItem "Do One Thing", 3
    .ComboAddItem "Do Something Else", 4
    .ComboAddItem "Run and hide", 5
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .InputText = "New Event 1"
    .InputAlign = TDIBA_Footer
    .Footer = "$input"
    .IconMain = TD_SHIELD_GRADIENT_ICON
    .IconFooter = hIconF
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog
```

Edit:
I can do an autosize... but the combobox might not have the same font as the form, and TextWidth would create a dependecy ON a form. I'll have to use something like GetTextExtentPoint32.
Not sure if making it larger than the combo width is a good UI decision anyway tho.. and it can't be smaller. Might make it optional. Or since the hWnd is exposed the user could always do it themselves in the TaskDialog_Created event.

----------


## Steve Grant

> The size is really a concern?


You're right of course, consider me chastised.  :Blush:  Sadly I am of the age where when I started 'Lean & Mean' was the only to go and I guess it has stuck. Also there was no criticism of your coding style, as you say the Dialog is beautifully simple to use and an elegant addition to a program.

Have I missed where I can download this version from, or is it still being polished?

----------


## fafalone

Still being polished... wanted to get everything done; the combos, the date/time, and the most complicated, combining a date picker and time picker as a single option; and making sure all the options work. Will update it later this afternoon or tonight.

Edit
If it's ok, things are complex enough as it is so I'm going to leave things like drop width to the user; it's a single line anyway:


```
Private Sub TaskDialog3_DialogCreated(ByVal hWnd As Long)
Call SendMessage(TaskDialog3.hWndCombo, CB_SETDROPPEDWIDTH, 900&, ByVal 0&)
End Sub
```

Edit2: Ugh, you're going to have to hold out until tomorrow. Ran into quite a few complications handling the date/time ranges and checkboxes, the latter due to a bug that took forever to run down.
And this other TRULY bizarre bug...
So the Init function resets all the variables to their initial state, and part of that was clearing all the text fields:


```
    m_sContent = ""
    .pszContent = StrPtr(m_sContent)
    m_sFooter = ""
    .pszFooter = StrPtr(m_sFooter)
```

etc etc for every field...
Well, the TDF_KILL_SHIELD_ICON flag was causing a crash, but only when .Init was called (even if it was still new and hadn't been used yet), and I traced it down to another standard text clear:


```
    m_sExpandedInfo = ""
    .pszExpandedInformation = 0 'StrPtr(m_sExpandedInfo)
```

That variable is declared just like all the others, nothing had been assigned to it previously, yet for some reason this caused the dialog to appear as a tiny box no bigger than an icon, with an 'X' that led to a crash or no X and having to ctrl-alt-delete. For the life of me I can't figure out WHY, weirdest bug I've ever seen. Took hours to figure out that line was the cause. Setting it to 0 instead of StrPtr fixed the problem.

So yeah anyway, just minor bug fixes and final touchups now, but the screen shots and documentation posts are a major effort. Just too tired tonight. It's all working now though, so let the hype build!

After this release I'm planning some good things for 1.0. Going to add those dropdown buttons I was working on, and since that needs subclassing anyway that can then be used to create events for the new controls, like ComboItemChange, InputBoxChange, etc. Then I'm also going to add more controls... definitely a Slider and picture, will see what else.

----------


## Steve Grant

This is not life or death, so take your time and get it exactly as you want it. We both know it's gonna be awesome!

Steve.

----------


## fafalone

Will be worth the wait...
Check it, snuck one more new thing in for this release...
Now not only can you entirely kill the icon for the shield gradients like post #65, you can replace it with a resource icon id from your app, or set TDF_USE_SHELL32|IMAGERES_ICONID and use one from those (or a custom module you load yourself with LoadLibrary and set to .hInst):

----------


## Steve Grant

Considering how much stuff there is on that dialog, it still manages to look neat and tidy!!!  :Smilie:

----------


## fafalone

Ehh I couldn't resist doing it now... holding off on the major subclassing thing, but did want to add sliders:


It's a good thing I did too, because I didn't notice until I had to adjust it's coords, but there was a massive lack of proper alignment when the expando control came into play... I had only handled 1 scenario, there were about a dozen more to make sure things are always positioned correct (you can see this issue starting in the current release; if the inputbox is in the footer position, the position doesn't adjust, and if it's the expand into footer area style, it's in the entirely wrong position, from the get-go when expanded by default is on). There went 7 hours.

----------


## Steve Grant

Get some sleep man!!!

----------


## Tech99

Task dialog, with non editable combobox (TDF_COMBO_LIST) or editable (TDF_COMBO_LIST Or TDF_INPUT_BOX) styles, would be superb.

----------


## DEXWERX

and then you should package it into a nice DLL or OCX.  :Smilie:

----------


## fafalone

Just to update everyone, I ran into a large number of alignment issues. I really want the new controls to work with the existing options as much as possible, and that means individually lining up every control to each scenario of standard options.

Things like:
-Controls placed in the main area are counted by the backend API towards the dialog height, but not the button positions. So it creates a blank area at the bottom when the expando control resizes the dialog. So the dialog has to be manually adjusted on top of control alignment.
-Expando + verify checkbox alters alignment
-Radio buttons alter alignment slightly differently than command links
-Expando controls resizing things effect the alignments in all sorts of unpredicted ways (like not eliminating whitespace originally only existing because of adding two vbCrLf's, even after those are removed and the element updated). There's the principle alignment for each control combined with each native control (and more than one native control for the content area), and the alignment in response to expando controls altering the content or footer area.
-Some are minor adjustments; some are major and require adjustments beyond just changing x,y; like adding and removing line breaks to create whitespace, each in a different way for each of a dozen option scenarios.

So as the pictures indicate, the basic work is done, but I'd rather spend a bit more time getting everything to play together nicely than further restrict how the new controls can be used in combination with the existing control options.. It's not hard to do this; just time consuming. Still coming soon; just want to get it right.

Edit: 
@Tech99, yes both are available... the TDF_COMBO_BOX flag adds a combo control, and the type is determine by a .ComboStyle property to choose either dropdown list or edit list.

@DEXWERX, really?? a DLL or OCX instead of a class module?? I guess I could provide that option but what's the advantage good enough to be worth the loss of easy customization? All it requires right now is a single class module and a couple callback headers that can go in any standard module.

Edit 2: The major alignment adjustment process has been completed. Now just putting finishing things off and making demos. It took a while, but the good news is that you can use any combination of existing controls (command links, radio buttons, progress bar, expando button, and verify checkbox) and the new controls; I think I've covered every scenario...
Config                            InputBox         Combo      Date      DateTime     Slider
Content-Standard                    OK              OK         OK         OK           OK
Content-CommandLink                 OK              OK         OK         OK           OK
Content-Radio                       OK              OK         OK         OK           OK
Content-CmdLnk+Radio                OK              OK         OK         OK           OK
Content-Expando-Std                 OK              OK         OK         OK           OK
Content-Expando-CmdLnk              OK              OK         OK         OK           OK
Content-Expando-Radio               OK              OK         OK         OK           OK
Content-Expando-CmdLnk+Radio        OK              OK         OK         OK           OK
Content-ExpandoDef-ALL 4            OK              OK         OK         OK           OK
Content-ExpandoTF-ALL 4             OK              OK         OK         OK           OK
Content-Verify-Std                  OK              OK         OK         OK           OK
Content-Verify-CommandLink          OK              OK         OK         OK           OK
Content-Verify-Radio                OK              OK         OK         OK           OK
Content-Verify-CmdLnk+Radio         OK              OK         OK         OK           OK
Content-Exp+Ver-Std                 OK              OK         OK         OK           OK
Content-Exp+Ver-CommandLink         OK              OK         OK         OK           OK
Content-Exp+Ver-Radio               OK              OK         OK         OK           OK
Content-Exp+Ver-CmdLnk+Radio        OK              OK         OK         OK           OK
Content-Progress-Std                OK              OK         OK         OK           OK
Content-Progress-CmdLnk+Radio       OK              OK         OK         OK           OK
Content-Progress-Expando (ALL)      OK              OK         OK         OK           OK
(C-E-V w/Default)                   OK              OK         OK         OK           OK
(C-E-V w/Expand to Footer)          OK              OK         OK         OK           OK
Content-No Content Text (ALL)       OK              OK         OK         OK           OK
Content-No Main Instr (ALL)         OK              OK         OK         OK           OK
--------------------
Button-Std                          OK              OK         OK         OK           OK
--------------------
Footer-Std                          OK              OK         OK         OK           OK
Footer-Verify                       OK              OK         OK         OK           OK
Footer-Expando-Std                  OK              OK         OK         OK           OK
Footer-Expando+Verify               OK              OK         OK         OK           OK
Footer-Expando-ToFooter             OK              OK         OK         OK           OK
Footer-Expando+Verify-ToFooter      OK              OK         OK         OK           OK
(adjusting and testing every one of those is what took so long)

----------


## fafalone

*Update: cTaskDialog 0.8*
After the success of the Input Box in 0.7, version 0.8 of cTaskDialog adds even more useful controls added by custom flag. There's now 4 types of controls:
TDF_INPUT_BOX The textbox from 0.7
TDF_COMBO_BOX A combo box that can either be an editable one or a dropdown list. It's a ComboBoxEx (ImageCombo), so it accepts an imagelist in the form of an HIMAGELIST.
TDF_DATETIME Shows a datetime control; either a single one for date or time, or two controls for date AND time. 
TDF_SLIDER A standard slider control with detailed options for the range and ticks.

Following in the tradition of the existing control options, you can add one of each of these (up to 3) in the same 3 areas where the inputbox could be placed: in the main content area, next to the buttons (replaces the expando and/or verify controls if placed here), or as the footer (the footer icon shows and is properly aligned, but old footer text is covered). Thanks to some painstaking calculations and testing, all these controls are additional options: you can use any number and combination of the built-in controls along with the new custom ones, including the expando control, and a space for them is automatically created. The creation and usage of the new controls follows the exact same easy format of the rest of the class.

Here's some selected samples; the attached project contains many additional ones showing the different options.



```
himlSys = GetSystemImagelist(SHGFI_SMALLICON) 'any image list will do; make your own with ImageList_Create or IImageList
With TaskDialog3
    .Init
    .MainInstruction = "Duplicates"
    .Content = "If you want to exclude an Artists name from the search:"
    .Flags = TDF_VERIFICATION_FLAG_CHECKED Or TDF_COMBO_BOX
    .AddCustomButton 100, "Continue"
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .IconMain = TD_SHIELD_ICON
    .Title = "cTaskDialog Project"
    .ComboCueBanner = "Cue Banner Text"
    .ComboSetInitialState "", 5
'    .ComboSetInitialItem 1
    .ComboImageList = himlSys
    .ComboAddItem "Item 1", 6
    .ComboAddItem "Item 2", 7
    .ComboAddItem "Item 3", 8
    .VerifyText = "Exclude Jingles"
    .ParenthWnd = Me.hWnd
    .ShowDialog

    Label3.Caption = "Checked? " & .ResultVerify
    Label7.Caption = .ResultComboText
    Label9.Caption = .ResultComboIndex
    If .ResultMain = 100 Then
        Label1.Caption = "Continue!"
    Else
        Label1.Caption = "Cancelled."
    End If
End With
```




Here we add pre-selected users to our password demo. The partial code below shows how dual custom controls are used:

```
    .Flags = TDF_INPUT_BOX Or TDF_COMBO_BOX
    .ComboStyle = cbtDropdownList
    .InputIsPassword = True
    .InputAlign = TDIBA_Buttons
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .SetButtonElevated TD_OK, 1
    .SetButtonHold TD_OK
    .ComboAlign = TDIBA_Content
    .ComboSetInitialItem 0
    .ComboImageList = himlSys
    .ComboAddItem "User 1", 6
    .ComboAddItem "User 2", 7
    .ComboAddItem "User 3", 8
    .Footer = "Enter your password then press OK to continue."
    .IconFooter = TD_INFORMATION_ICON
    .IconMain = TD_SHIELD_ERROR_ICON
```




The most basic Date control. This can also be a time control.

```
    .Init
    .MainInstruction = "Hello World"
    .Content = "Pick a day, any day" & vbCrLf & vbCrLf
    .Flags = TDF_DATETIME
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .IconMain = TD_INFORMATION_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog

    Label11.Caption = .ResultDateTime
```




Many common options are supported. This example shows combining the new controls with the built-in ones, and setting a limited range in the date and time controls. When the two controls are used, both the chosen date and time are represented by .ResultDateTime.

```
Dim dTimeMin As Date, dTimeMax As Date

dTimeMin = DateSerial(Year(Now), Month(Now), Day(Now)) + TimeSerial(13, 0, 0)
dTimeMax = DateAdd("d", 7, dTimeMin)
dTimeMax = DateAdd("h", 4, dTimeMax)

With TaskDialog1
    .Init
    .MainInstruction = "Date Ranges"
    .Content = "Pick a time, limited to sometime in the next 7 days, between 1pm and 6pm" & vbCrLf & vbCrLf
    .Flags = TDF_DATETIME Or TDF_USE_COMMAND_LINKS
    .DateTimeType = dttDateTime
    .DateTimeAlign = TDIBA_Content
    .DateTimeSetRange True, True, dTimeMin, dTimeMax
    .DateTimeSetInitial dTimeMin
    .AddCustomButton 101, "Set Date" & vbLf & "Apply this date and time to whatever it is you're doing."
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .IconMain = TD_INFORMATION_ICON
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .ShowDialog

    Label11.Caption = .ResultDateTime
```




Here's a highly customized slider combined with showing the auto-positioning that allows the expando control to be used, even with built-in controls further upping the complexity.

```
    .Init
    .MainInstruction = "Sliding on down"
    .Content = "Pick a number"
    .Flags = TDF_SLIDER Or TDF_USE_COMMAND_LINKS
    .SliderSetRange 0, 100, 10
    .SliderSetChangeValues 10, 20
    .SliderTickStyle = SldTickStyleBoth
    .SliderValue = 50
    .SliderAlign = TDIBA_Content
    .ExpandedControlText = "ExpandMe"
    .ExpandedInfo = "Expanded"
    .AddCustomButton 100, "CommandLink"
    .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
    .IconMain = TD_INFORMATION_ICON
```




Finally, here's an example of using 3 of the new controls together in combination with two of the built-in controls, as well as the option to replace the shield icon with one from shell32.dll, while maintaining the gradient background.

```
himlSys = GetSystemImagelist(SHGFI_SMALLICON)
Dim hIconF As Long
hIconF = IconToHICON(LoadResData("ICO_CLIP", "CUSTOM"), 16, 16)
With TaskDialog1
    .Init
    .MainInstruction = "Schedule Event"
    .Content = "Pick action to schedule. Provide a date, and optionally a specific time. You can also set a name below."
    .Flags = TDF_DATETIME Or TDF_INPUT_BOX Or TDF_COMBO_BOX Or TDF_USE_HICON_FOOTER Or TDF_USE_SHELL32_ICONID Or TDF_KILL_SHIELD_ICON Or TDF_USE_COMMAND_LINKS
    .DateTimeType = dttDateTimeWithCheckTimeOnly
    .DateTimeAlign = TDIBA_Buttons
    .ComboAlign = TDIBA_Content
    .ComboStyle = cbtDropdownList
    .ComboSetInitialItem 1
    .ComboImageList = himlSys
    .ComboAddItem "Do Thing #1", 2
    .ComboAddItem "Do Thing #2", 7
    .ComboAddItem "Do Thing #3", 8
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .InputText = "New Event 1"
    .InputAlign = TDIBA_Footer
    .IconMain = TD_SHIELD_GRADIENT_ICON
    .IconFooter = hIconF
    .IconReplaceGradient = 276
    .Title = "cTaskDialog Project"
    .ParenthWnd = Me.hWnd
    .AddCustomButton 102, "Schedule" & vbLf & "Additional information here."
    .AddRadioButton 110, "Apply to this account only."
    .AddRadioButton 111, "Apply to all accounts."
    .ShowDialog

    Label2.Caption = "Radio: " & .ResultRad
    Label5.Caption = .ResultInput
    Label7.Caption = .ResultComboText
    Label9.Caption = .ResultComboIndex
    Label11.Caption = .ResultDateTime
    If .ResultDateTimeChecked = 0 Then
        Label13.Caption = "Time unchecked."
    Else
        Label13.Caption = "Time checked."
    End If
    If .ResultMain = 102 Then
        Label1.Caption = "Scheduled."
    Else
        Label1.Caption = "Cancelled."
    End If
End With
```






Full update notes from the .cls:


```
'NEW IN VERSION 0.8
'--Added ability to use comboxes (normal edit dropdown and dropdown list), date/time pickers,
'  and sliders like the inputbox controls.
'--There's still the 3 alignment positions from the inputbox, so you can have controls in each
'  of these places (however, only 1 of each type is allowed)
'--When TDF_KILL_SHIELD_ICON is used, there's now an option to replace it with a different icon:
'   Set .IconReplaceGradient...
'    1) To an ID in your app's resource file (only works when compiled)
'    2) Add the TDF_USE_SHELL32_ICONID or TDF_USE_IMAGERES_ICONID flag and use an ID from those
'    3) Set .hInst to a custom module and use an icon ID from that
'   Using an hIcon is not supported at this time.
'--Bug fix: Using TDF_KILL_SHIELD_ICON and calling .Init crashed the app
'--Bug fix: Using an InputBox at the footer position, the bottom half was cut off if no
'           footer icon was set.
'--Bug fix: Input box position adjusted for expando only if in the content position, now adjusts
'           if in footer position too. Prevented this bug in all other controls.
'--Bug fix: If input box was in footer position, and expanded info was expanded automatically
'           and into the footer area, the input box would be in the wrong position. Fixed and
'           prevented for new controls.
'KNOWN ISSUE: If the slider control is placed in the footer position and an expando control is
'             set to expand into the footer area, the transparent background on the slider is
'             lost (turns white) until it's clicked. It's a very unusual and specific situation,
'             so not going to delay this version while I come up with a fix.
'TODO:
'The next version will be 1.0. There's a couple major features planned, including images and more
'events (for the custom controls). Maybe more controls; like dropdown menus on the buttons.
'I'm very aware this class needs substantial cleanup, that's also being saved for 1.0
' So please go easy on the sloppiness!!! :)
```

----------


## Steve Grant

Excellent work from the UK! Much more than the doctor ordered.

Tiny problem, if you push the expando button you see some strange resizing going on. (Win 10 any way).

Steve.

----------


## fafalone

Yeah the expando button changes the built-in control positions and the size of the dialog, so the custom controls have to be moved too and the API doesn't do it on its own. The API actually counts the custom controls when calculating the dialog height, but forgets about it when the buttons are arranged. So the result was an incorrectly sized dialog with a giant blank space; so I resize to the correct dimensions. The reason for the delay is that it does some weird fading thing... if you don't delay the resize/reposition on a timer, the APIs that get the RECT's return the original value, and you need the new value to correctly position everything; and figuring it out in advance would be a huge undertaking: figuring out not only the RECT size based on the font and text length, with line breaks and everything, but also MS's word-wrapping and dialog-sizing algorithms.

I'm open to better solutions. There might be a way in the next version where the whole thing is going to be subclassed; but for now things have to be resized and repositioned like they are. The only alternatives are giant blank spots and/or things winding up on top of eachother; so it's that or simply disable the custom controls when the expando button is present.. the awkwardness seemed better.

*Edit*

_Coming Soon To A Dialog Near You...._

+Split button and logo image...


+Logo can also be placed top right... Edit: Got transparency working (to the background color).
+InputBoxes, ComboBoxes, and Sliders aligned next to the buttons scale in width to take up the available area (automatically adjusted based on furthest-left button)
+The footer position is still fixed by default, but if you specify a width of -1 it will scale
+InputBox/Combo/Slider have an added option to manually specify a fixed width
+Focus placed on the control in the content area (or button area then footer if none) so the inputbox isn't automatically focused if it's a secondary control

*Edit2*

+New alignment options fix widths in dialogs with no main icon, and footer right-align option (in addition to left and center) allow footer text to be shown too.

----------


## Tech99

Thansk fafalone.

Tested this 0.8 version briefly.

Taskdialog created with...



```
TaskDialog1
    .Init
    .ParenthWnd = frmMain.hWnd
    .MainInstruction = "Testing"
    .Content = "Does this work."
    .IconMain = IDI_QUESTION 
    .Title = App.EXEName   '"cTaskDialog Project"
    .flags = TDF_USE_COMMAND_LINKS 'TDF_USE_COMMAND_LINKS_NO_ICON
'etc...
```

do not work, error (TaskDialogIndirect ret=0x80070715) returns when IDI_QUESTION icon is defined. Version 0.6 did work, with IDI_QUESTION icon.

----------


## fafalone

Yeah there's been an issue with the IDI_QUESTION and IDI_APPLICATION icons... 

In order to display Icon resources from your compiled project's resource file, or from Shell32.dll or Imageres.dll, the hInstance must be set to App.hInstance or the handle of those DLLs. Changing to the class doing App.hInstance by default is what broke the IDI_'s. 

So if you want to use IDI_QUESTION or IDI_APPLICATION, add .hInst = 0 - but note you won't be able to use an icon from your .res file, shell32, or imageres in the other spot (header and footer).

If you want to permanently change the default, change uTDC.hInstance in Class_Initialize and Init to 0. On the whole I thought displaying resource icons was more important to have as the default, although I'm definitely open to feedback: The two possible scenarios are 
A) You can use an icon from your .res Icon resources just by using it's number, but then must set .hInst = 0 if you want to use IDI_ (0.8), or
B) You can use IDI_, but then must set .hInst = App.hInstance if you want to use a .res file Icon index (0.6)
So is (B) better?

PS- As suggested by the above, if the TDF_USE_SHELL32_ICONID or TDF_USE_IMAGERES_ICONID flags are set, you can't use IDI_ e.g. use a shell32 index for the main icon and IDI_QUESTION for the footer

----------


## TonyYang

I have tested this class on Win8.1, great work!

But this class will not work well when Windows display DPI rate is not 100%, say, 150%(Win10 Default for some new type ThinkPad laptops) or 200%(Microsoft Surface).

Some pixel size of extra elements(text/combo/datetime) are hard coded, say, always use 22 for TextBox height.  I tried to modify some code, but It's really not a easy work to just adjust by 15/screen.TwipsPerPixelx, on 150% DPI, it will get 33 and that's too much for a single line TextBox.

My suggestion is to make some adjustment on element size and position for high DPI monitor.

Pardon for my bad English.~~

Thanks!

----------


## fafalone

Yeah unfortunately a lot of stuff in VB6 isn't suited to high-DPI, and I don't have a high-DPI monitor myself to work with it. Will look into it.. I'm sure there's an article somewhere about to size controls correctly according to DPI. (but if you're using 150% DPI and have twips per pixel=15, that's the same as my standard dpi monitor)

----------


## TonyYang

Dear fafalone:

you can easily set high dpi on your current monitor, in fact all my daily work is done on a regular 1440*900 monitor, but to test vb6 programs on high dpi monitor,just set Control Panel\Appearance and Personalization\Display to 150% and set vb6.exe compatible properites to DPI awareness.





[QUOTE=fafalone;5141057]Yeah unfortunately a lot of stuff in VB6 isn't suited to high

----------


## Tech99

Thanks fafalone, setting .hInst = 0 sorted problem.

On the whole (usage scenarios), i would wote B, as it (IMHO) would be more logical.

----------


## fafalone

Ok so if I'm understanding MSDN's intro to DPI correctly, I should adjust x,y and cx,cy like
scaledX = GetDeviceCaps(GetDC(0), LOGPIXELSX) * originalX / 96
right?


Edit2: Well after playing around with what values got scaled, I've got everything looking correct except X is just a little too far, but not so much it's unacceptable. The biggest issue I'm worried about is everything says you're supposed to divide but I'm multiplying everything.



The X being so slightly off and the multiplying is making me very worried this isn't the correct way and is going to not look right on other DPI settings and resolutions.
What I'm doing to get the dialog above is cx*scale, cy*scale, but calculations for x,y only multiply the offsets by the scale and not the other things like the button positions.. e.g. lButtonY3 - (44 * m_ScaleY)

Edit: Yeah it's definitely wrong. If I scale the offset for the footer position the same exact way, it goes too far down.
Frankly I have no clue what to do since the official method doesn't work and I'm not at all familiar with this area.

----------


## TonyYang

Dim twipsX As Double
            twipsX = 1440 / GetDeviceCaps(hdcPrimaryMonitor, LOGPIXELSX)

that will get 96 on 100% dpi, or 120 on 125% dpi.

so scale rate is 120/96, same as 1.25

also you can try 15/screen.twipsPerpixelX(Y)m but that's not always accurate if windows DPI rate is NOT 125%, 150%.

On 200% DPI, screen.twipsPerpixelX(Y) is 7 (since it's an Integer value), but Actually it's 7.5. that will cause a lot of problems on ActiveX controls. only a few ActiveX Control have fix on this problem, an example of this can be found in this forum: http://www.vbforums.com/showthread.p...mmon-controls), Common.DPICorrectionFactor function 

Example: a textbox on vb6 form with 20*100 pixel based client rect will automatically resized to 25*125 on dpi 125%

----------


## fafalone

Do what with 1440 / gdc? Can't multiply or divide by it...

The x-offset on 100% scaling is 52 pixels. Manually aligning on 150% scaling got 69 pixels. That's a scale factor of 1.323.

And 1440 / GetDeviceCaps(hdcPrimaryMonitor, LOGPIXELSX) returns 10 on 150% on my computer, so I don't know where that's coming from. 

But I'm starting to think the problem is the task dialog size isn't a simple scale; something much more complex seems to be going on behind the scenes- given that control width/height is scaled appropriately.
So I went back and contemplated what was effecting the layout calculations the API was making behind the scenes, and struck upon an interesting hypothesis: the adjustment is caused exclusively by the icon. So instead of 52 * m_ScaleX, I instead set the offset to (32 * m_ScaleX) + 20... 32 being the unscaled width of the icon, and it WORKED! Perfect alignment. Footer left adjusted from 32 to (16 * m_ScaleX) + 16 also worked perfectly. There's quite a bit more work to confirm, test, and adjust things, but it looks promising as it reconciles quite a few issues.
It's going to be a nightmare to adjust Y-values on this principle though, because if the theory holds I have to figure out what part of the offset is the button height and adjust based on that, at least for the footer position where the button height would matter.

Edit: Y looks promising too. I got a button height of 23 unscaled, and instead of buttontop+(40*scale), buttontop + (23 * scale) + 17 put it 1 pixel off from the correct spot.

Edit2: I've got it down now. I've identified what does and does not need to be multiplied by the DPI scale factor for every aspect of alignment and sizing. Just need to revise all the other custom controls now. Version 1.0 will definitely be DPI sensitive. Thanks for pointing this out.

Edit3: Things are looking good.
If anyone is interested in testing things out while I'm doing final testing, I'm attaching a beta version of the big 1.0 release. This is really just a test version; there's a lot of commented debug code and notations, the samples are rearranged for testing with lots of extra lines to turn on and off, and there's a bunch of extra buttons for testing things.. aka not ready for public release- but I know I love getting beta versions of cool stuff, so thought I'd share since it's feature-complete and SHOULD be working 100% on normal DPI, and hopefully on other DPIs. It includes the dropdown buttons and logo images too.

(attachment removed; full version released)

----------


## fafalone

*cTaskDialog 1.0*
It's been a long road to this 1.0 release. I started off with a barebones implementation of Task Dialogs nearly 3 years ago, then a full implementation of TaskDialogIndirect. From there the customizations began. Now this class is extensively enhanced, turning the already excellent Task Dialog API into a powerhouse of customization without sacrificing ease of use.

While only adding 3 major features, dropdown buttons, logo images, and auto-close, 1.0 brings huge changes behind the scenes:


```
'NEW IN VERSION 1.0
'--You can now make a custom button into a Split Button (dropdown arrow). Use .SetSplitButton
'  with the ID of the custom button. This fires a DropdownButtonClicked event. Cannot be used
'  with Command Links. Note there's one more function prototype in mTaskDialogHelper you'll
'  need to copy if you're placing those in your own module (they've been condensed too so the
'  whole thing is only 3 lines now).
'--A logo type image can now be placed with .SetLogoImage. The current placement options are
'  the top right corner, or next to the buttons (only if no controls, either custom or the
'  expando/verify are present). The image is passed as an HBITMAP or HICON, so you can load
'  in whichever way you want. No copy is made, so you must destroy the image yourself, but
'  only after the dialog closes (or it will not appear).
'--Autoclose has been implemented. Set the .AutocloseTime property to a value in seconds; when
'  you get the property, it returns the current time remaining.
'--Events for custom controls: ComboItemChanged, ComboDropdown, InputBoxChange, DateTimeChange
'--Custom controls now have the option to manually set their width (and height for combo).
'--Custom controls in the Buttons position now have their width adjusted to fill the space.
'  Custom controls in the Footer position still use a standard size, however for InputBox,
'  ComboBox, and Slider if you specify a width of -1 the width will scale to the full width
'--For custom controls in the footer area, and the datetime control in the content area, there's
'  an additional alignment option to choose between left, center, and right. In the footer area
'  this could be used to show footer text and have the control off to the right.
'--Focus is no longer always on an InputBox: Focus is set on whatever custom control is in the
'  content area, if none button area, last footer area. Or, there's now a .DefaultCustomControl
'  option to manually specify which one, or not have focus set on any of them.
'--Bug fix: Several fixes relating to alignment and events for custom controls used on dialogs
'           that are loaded as a new page (NavigatePage/TDN_NAVIGATED)
'--Bug fix: .DefaultButton used TDBUTTONS instead of TDRESULT enum to identify IDs
'--Bug fix: .DefaultRadioButton was incorrectly associated with the TDBUTTONS enum
'--Alignment: -Adjusted Content area custom controls slightly higher when expanded
'              info is used.
'             -Custom controls in content area now have an appropriate X-position and width when
'              no icon appears, shifting everything to the left.
'             -Made sure that content text actually had a link before making the link adjustment,
'              since the flag would still be used if there's a link only in the footer area.
'             -When there's a link but no command links or radio buttons, content area controls
'              needed to be adjusted upwards a bit.
'--Alignment: -Custom controls size and position is now automatically adjust for the current DPI.
'             -You can also specify a scale factor manually with the DPIScaleX/DPIScaleY properties.
'KNOWN ISSUES: -If there's a custom control in the content area, when an expando control is expanded
'               and then collapsed, the excess whitespace isn't removed despite there not being any
'               extra vbCrLf's to account for it. No way to correct this has been discovered yet.
'              -If the dialog has to be resized in response to an expando control, the width is
'               reduced by several pixels for an unknown reason- but only on the first time. If the
'               numbers returned by getting the client RECT were wrong, you'd expect to lose more
'               pixels every time, but that's not the case. No solution has been discovered.
'              -Some alignment scenarios remain unsupported. If expando with the expand-footer w/
'               expand-default, have text with multiple lines, alignment will be off. This remains
'               a significant challenge to address as the DirectUIHWND reports incorrect information
'               to GetPixel, so figuring out where things are will require font height analysis and
'               reverse engineering line break length determination. Will work on in next version.
'              -Sliders with ticks on top and bottom simply don't fit on higher DPIs in the footer
'               position. Height available is too small no matter how it's positioned.
```


On to examples, first up is the very simple auto-close. 


When creating the dialog, you specify an auto-close time in seconds. When you retrieve the property while the dialog is active, it returns the time remaining, allowing you to do things like tie it into a progress bar of doom:


```
With TaskDialogAC
    .Init
    .MainInstruction = "Do you wish to do somethingsomesuch?"
    .Flags = TDF_CALLBACK_TIMER Or TDF_USE_COMMAND_LINKS Or TDF_SHOW_PROGRESS_BAR
    .Content = "Execute it then, otherwise I'm gonna peace out."
    .AddCustomButton 101, "Let's Go!" & vbLf & "Really, let's go."
    .CommonButtons = TDCBF_CLOSE_BUTTON
    .IconMain = IDI_QUESTION
    .IconFooter = TD_ERROR_ICON
    .Footer = "Closing in 15 seconds..."
    .Title = "cTaskDialog Project"
    .AutocloseTime = 15 'seconds
    .ParenthWnd = Me.hwnd
    .ShowDialog
End With

'Then:
Private Sub TaskDialogAC_DialogCreated(ByVal hwnd As Long)
TaskDialogAC.ProgressSetRange 0, 15
TaskDialogAC.ProgressSetState ePBST_ERROR
End Sub

Private Sub TaskDialogAC_Timer(ByVal TimerValue As Long)
On Error Resume Next
TaskDialogAC.Footer = "Closing in " & TaskDialogAC.AutocloseTime & " seconds..."
TaskDialogAC.ProgressSetValue 15 - TaskDialogAC.AutocloseTime
On Error GoTo 0
End Sub
```

When a dialog times out, it returns a TD_CANCEL main result (it also does this if you hit 'x').


Next up, logo images. Now, there's a very wide variety of image processing techniques. Rather than accept a file name and limit things to one method, the class module must simple be passed an HBITMAP, which you can acquire in a multitude of ways. The demo project uses GDI+, so a generic set of standard images is supported. The logo image can go in two places, in the buttons position (means no controls, custom or built-in, can be placed there):

```
'some initializing settings from the picture are omitted
    Dim hBmp As Long
    Dim sImg As String
    sImg = App.Path & "\vbf.jpg"
    Dim cx As Long, cy As Long
    hBmp = hBitmapFromFile(sImg, cx, cy)
With TaskDialog1
    .Init
    '[...]
    .SetLogoImage hBmp, LogoBitmap, LogoButtons
    .ShowDialog
End With
Call DeleteObject(hBmp) 'do not free image until the dialog is closed, otherwise it won't show.
```




Two more features of the logos are support for transparency, and support for custom offsets from the edges. Transparency, due to the way TaskDialogs work with that infernal DirectUIHWND, require some processing... the background color isn't correctly reported so it's transparent to the wrong color. This is resolved by custom-setting the background it's transparent to to the current RBG of the actual visible pixels. If we always just used the standard window background, it wouldn't work with the shield gradients. Don't worry, it's all handled for you. Just set the alignment and desired offsets:

```
    .SetLogoImage hBmp, LogoBitmap, LogoTopRight, 4, 4
```

(as suggested by 'LogoBitmap', there's also 'LogoIcon' that you need to use for .ico's)


Finally we get to drop down buttons. It's a pretty rare thing to require, but once subclassing had to be brought in for a host of other uses, why not add it? Note that the demo project prints a debug message when the dropdown arrow is clicked, I didn't include the large amount of code to then generate a menu at that spot. If you need help with doing that, let me know in this thread or by PM, as I do have that code written.
At this time, it's only possible to make a split button out of a custom button; which has to be a normal button-- so command links aren't supported. Turning a button into a split button is done by ID (and only 1 button can be made into a split one at this time):


```
    .AddCustomButton 123, " SuperButton  " 
    .SetSplitButton 123
```

Note the extra space padding-- this is required for the button to look right. I'll look into automating the addition of spaces in the future but for now it's gotta be manually done.
Icons work but aren't perfect:

You might want to consider customizing the icon by having some blank pixels on the left.





A final major feature, that took by far the longest to implement, was making the dialog play nice with different DPIs. It was a significant challenge that took quite a few hours, but I believe I have made the position and size calculations correct for arbitrary high-DPI. I've done quite a bit of testing at 125% and 150% DPI, and everything is looking good. You don't need to do anything to handle this, the class detects DPI and adjusts automatically. However, there is an option to manually set the scaling factor that is used (or see the one currently in use by the class): DPIScaleX and DPIScaleY. 
It's calculated currently as follows:


```
hDC = GetDC(0&)
m_ScaleX = GetDeviceCaps(hDC, LOGPIXELSX) / 96
m_ScaleY = GetDeviceCaps(hDC, LOGPIXELSY) / 96
```


As always, please report any bugs you encounter or features you'd like to see. Enjoy  :wave:

----------


## fafalone

**Plans for new features**

I'm going to continuously update this post with new features as I add them for the next version.

The first new feature is extending the left/right/center alignment options for custom controls. This combined with fixed width means that the block on multiple controls in the same place will be removed since if you're careful it's possible to have multiples like this:



In the first image, the date/time has a right-align set, and the textbox has a center-align set.
In the second image, the input textbox uses a fixed-width, which can be DPI sensitive by .InputWidth = 140 * .DPIScaleX. I'm also going to add a manual offset option.

----------


## dreammanor

Hi, Fafalone, I encountered the following two questions when I tested your TaskDialog on Win10:

1. When I click any of the buttons on the main window, the following prompt appears:

----------


## dreammanor

2. When I compile the project, the following prompt appears:

----------


## fafalone

For 1: Your project, and the IDE if running from there, needs to be manifested for Common Controls 6.0..



> *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.


The linked thread also covers manifesting the IDE.

For 2:
You can delete that whole function.
Sorry about that, it's just test code for a future version I was playing around with. The download has been updated with the whole thing commented out too.

----------


## dreammanor

Ok, I'll try again, thank you very much.

----------


## Cube8

Typo: Event _DialogConstucted_ -> _DialogConstructed_

----------


## fafalone

Well that's embarrassing, that typo has survived since the very first release  :Blush: 

It'll be fixed in the next major release; not going to push it as a 'can't wait' issue. Thanks for pointing it out.

----------


## loquat

can this be used in office vba?

----------


## fafalone

I don't see why not.

----------


## loquat

> I don't see why not.


seems need manifests as well as in vb6 ide?

----------


## fafalone

Good point; I just checked and VBA does support the 6.0 API, so it *can* be called, no manifest needed, but like everything else it has to be converted to VBA's PtrSafe garbage where Long is different from LongPtr.
I did a test calling the simple API, which shows it works:


```
Private Declare PtrSafe Function TaskDialog Lib "comctl32.dll" _
                                                    (ByVal hwndParent As Long, _
                                                     ByVal hInstance As Long, _
                                                     ByVal pszWindowTitle As LongPtr, _
                                                     ByVal pszMainInstruction As LongPtr, _
                                                     ByVal pszContent As LongPtr, _
                                                     ByVal dwCommonButtons As Long, _
                                                     ByVal pszIcon As Long, _
                                                     pnButton As Long) As Long


Private Sub CommandButton1_Click()
   Dim dwIcon As Long
   Dim pnButton As Long
   Dim Success As Long
    Dim sTitle As String
    Dim sMainText As String
    Dim sMessage As String
    sMainText = "test"
    sMessage = "msg"
    
Dim pszTitle As LongPtr
Dim pszMain As LongPtr
Dim pszContent As LongPtr

If sTitle = "" Then
    sTitle = "titlist"
End If
pszTitle = StrPtr(sTitle)
If sMainText <> "" Then pszMain = StrPtr(sMainText)
If sMessage <> "" Then pszContent = StrPtr(sMessage)
Dim dwIco As Long
dwIco = -1
If dwIco Then
   dwIcon = tdMakeIntResource(dwIco)
End If
  Call TaskDialog(hWndOwner, _
                        hinst, _
                        pszTitle, _
                        pszMain, _
                        pszContent, _
                        1, _
                        dwIcon, _
                        pnButton)

    
End Sub
Private Function tdMakeIntResource(ByVal dwVal As Long) As Long
   tdMakeIntResource = &HFFFF& And dwVal
End Function
```

So given the extensive use of pointers in the class, even if you used the older version before my customizations, it would still be quite the time consumer to convert, but the API can indeed be called.

----------


## loquat

But it seems in my VBA IDE mode, it still shows "cannot find entrypoint taskdialog balabala"
office 2010x86 win7x64

----------


## loquat

I tried ur demo on #99

----------


## fafalone

Perhaps it could be only 64bit VBA supports comctl32. I know it's not the versions that are the problem; I'm also using Office 2010 on Win7x64, except I've got 64bit office. 

You could also try providing a manifest for the office exe. Check if it has one (if you don't know how I can explain), if not provide it...
I looked at my 64-bit Excel 2010 executable, and it has this manifest:


```
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
	<noInherit/>
	<assemblyIdentity version="14.0.4756.1000" processorArchitecture="AMD64" name="excel" type="win32"/>
	<description>Microsoft Excel</description>
	<dependency optional="yes">
		<dependentAssembly>
			<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.1.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
		</dependentAssembly>
	</dependency>
</assembly>
```

So that's why it works for 64-bit, but I don't have a copy of 32-bit Office2010 handy to see if it does the same thing.

----------


## loquat

how can i check the res file of my Excel.exe, which is just the same version of urs except mine is 32bits

----------


## fafalone

I checked with Resource Hacker... open excel.exe and check if there's a 'Manifest' category (the category name might be '24' in other resource editors).

----------


## loquat

Yes the 'Manifest' here it is.
why can't i use it...


```
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
	<noInherit/>
	<assemblyIdentity version="14.0.4756.1000" processorArchitecture="X86" name="excel" type="win32"/>
	<description>Microsoft Excel</description>
	<dependency optional="yes">
		<dependentAssembly>
			<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.1.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
		</dependentAssembly>
	</dependency>
</assembly>
```

----------


## fafalone

So I went searching on Google and ran across this: https://gist.github.com/kumatti1/1714700

It declares TaskDialog like this:


```
Declare Function TaskDialog& Lib "C:\Windows\winsxs\x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.7601.17514_none_41e6975e2bd6f2b2\comctl32.dll" (ByVal hWndParent&, ByVal hInstance&, ByVal pszWindowTitle&, ByVal pszMainInstruction&, ByVal pszContent&, ByVal dwCommonButtons&, ByVal pszIcon&, ByVal pnButton&)
```

See how it's pointing to an x86 sxs reference instead of just the DLL? Try using that declare (or just that code).

(I'm not familiar with how all that works; but I'm guessing a similar path should exist if that one doesn't?)

----------


## loquat

this seems good, wonderful...

----------


## Tech99

> So that's why it works for 64-bit, but I don't have a copy of 32-bit Office2010 handy to see if it does the same thing.


32-bit Excel 2010 has this manifest (file excel.exe.manifest), at installation folder, typically in C:\Program Files (x86)\Microsoft Office\Office14



```
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"><noInherit></noInherit><assemblyIdentity version="11.0.0.0" processorArchitecture="*" name="excel" type="win32"></assemblyIdentity><description>Microsoft Excel</description><dependency><dependentAssembly><assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.30729.1" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity></dependentAssembly></dependency><trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel></requestedPrivileges></security></trustInfo><compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> 
		<application>
			
			<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"></supportedOS>
		</application> 
	</compatibility><asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
		<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
			<dpiAware>true</dpiAware>
		</asmv3:windowsSettings>
	</asmv3:application></assembly>
```

----------


## fafalone

That one doesn't mention comctl 6... could the 'prefer external manifest' setting have been flipped on? Or Tech99 does your excel.exe not also have an internal manifest?

Do you guys think there's really enough demand that I should re-write the class for VBA? The newer versions with all the customizations aren't going to happen (wayyy too much work) but I'm considering v0.6- the version that implemented all the built-in functionality. Just curious if there's a good number of people interested in something like that.

----------


## Tech99

> That one doesn't mention comctl 6... could the 'prefer external manifest' setting have been flipped on? Or Tech99 does your excel.exe not also have an internal manifest?
> 
> Do you guys think there's really enough demand that I should re-write the class for VBA? The newer versions with all the customizations aren't going to happen (wayyy too much work) but I'm considering v0.6- the version that implemented all the built-in functionality. Just curious if there's a good number of people interested in something like that.


Sorry for the fuss, yes there is an internal manifest. 

I think IMHO that there is not much of a need, to make an VBA version of your class. Demand might not be worth of re-write it.



```
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<noInherit/>
<assemblyIdentity version="14.0.7170.5000" processorArchitecture="X86" name="excel" type="win32"/>
<description>Microsoft Excel</description>
<dependency optional="yes">
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.1.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
</assembly>
```

----------


## loquat

can we change excel.exe.manifest to use normal declare of comctl32.dll?

----------


## Tech99

> can we change excel.exe.manifest to use normal declare of comctl32.dll?


What you mean by normal declare? Sure you can generate/modify common control v6 manifest to suit your application.

See the MSDN article.
https://msdn.microsoft.com/en-us/lib...(v=vs.85).aspx

----------


## loquat

what i mean can we change excel.exe.manifest to use this declare in vba


```
Private Declare Function TaskDialog Lib "comctl32.dll" _
                                                    (ByVal hwndParent As Long, _
                                                     ByVal hInstance As Long, _
                                                     ByVal pszWindowTitle As Long, _
                                                     ByVal pszMainInstruction As Long, _
                                                     ByVal pszContent As Long, _
                                                     ByVal dwCommonButtons As Long, _
                                                     ByVal pszIcon As Long, _
                                                     pnButton As Long) As Long
```

but no need to use comctl32.dll in winsxs folder like this


```
Declare Function TaskDialog& Lib "C:\Windows\winsxs\x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.7601.17514_none_41e6975e2bd6f2b2\comctl32.dll" (ByVal hWndParent&, ByVal hInstance&, ByVal pszWindowTitle&, ByVal pszMainInstruction&, ByVal pszContent&, ByVal dwCommonButtons&, ByVal pszIcon&, ByVal pnButton&)
```

by the way, in my winsxs folder there are 3 version of comctl32.dll up to version 6.0.7601.17514
they are 6.0.7601.18837, 6.0.7601.23039 and 6.0.7601.23403

----------


## loquat

can we call the comctl32.dll api without declare it just like
paul caton do
http://www.planetsourcecode.com/vb/s...70195&lngWId=1
or Lavolpe do
http://www.vbforums.com/showthread.php?781595

----------


## Inside

> ...P.S. this horizontal scrolling is annoying; maybe it's just my browser


The "poll" on the right side is gone  :Big Grin:   that's why you have to scroll...When the new poll coming you'll see better again.

----------


## Tech99

> what i mean can we change excel.exe.manifest to use this declare in vba
> 
> 
> ```
> Private Declare Function TaskDialog Lib "comctl32.dll" _
>                                                     (ByVal hwndParent As Long, _
>                                                      ByVal hInstance As Long, _
>                                                      ByVal pszWindowTitle As Long, _
>                                                      ByVal pszMainInstruction As Long, _
> ...


Sure you can declare library directly, pointing to 32-bit DLL, but nowadays it is better to use PtrSafe type declaration - which is compatible 32 and 64 bit office versions.

https://msdn.microsoft.com/en-us/lib...ffice.14).aspx




> To resolve this, VBA now contains a true pointer data type: LongPtr. This new data type enables you to write the original Declare statement correctly as:
> 
> VBA
> 
> 
> ```
> Declare PtrSafe Function RegOpenKeyA Lib "advapire32.dll" (ByVal hKey as LongPtr, ByVal lpSubKey As String, phkResult As LongPtr) As Long
> ```
> 
> ...

----------


## loquat

can we make a dynamic call of TaskDialog api in comctl32.dll, so that we need no manifest?
this maybe a solution of the problem need manifest in ide mode such as office vba

----------


## loquat

I found that copy C:\Windows\winsxs\x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.7601.17514_none_41e6975e2bd6f2b2\comctl32.dll to any folder is will be good
such as "c:\comctl32.dll"
then this will be ok in ide mode, vb6ide as well as office vba


```
Private Declare Function TaskDialog Lib "c:\comctl32.dll" _
                                                    (ByVal hwndParent As Long, _
                                                     ByVal hInstance As Long, _
                                                     ByVal pszWindowTitle As Long, _
                                                     ByVal pszMainInstruction As Long, _
                                                     ByVal pszContent As Long, _
                                                     ByVal dwCommonButtons As Long, _
                                                     ByVal pszIcon As Long, _
                                                     pnButton As Long) As Long
'WINCOMMCTRLAPI HRESULT WINAPI TaskDialogIndirect(const TASKDIALOGCONFIG *pTaskConfig, __out_opt int *pnButton, __out_opt int *pnRadioButton, __out_opt BOOL *pfVerificationFlagChecked);
Private Declare Function TaskDialogIndirect Lib "c:\comctl32.dll" _
                                                    (pTaskConfig As TASKDIALOGCONFIG, _
                                                    pnButton As Long, _
                                                    pnRadioButton As Long, _
                                                    pfVerificationFlagChecked As Long) As Long
```

----------


## loquat

found a problem within "Misc - Auto-close"
if i set the AutoCloseTime to 20 seconds, it seems that the former 5 seconds shows no progress
    .Footer = "Closing in 20 seconds..."
    .Title = "cTaskDialog Project"
    .AutoCloseTime = 20 'seconds
and if i set the AutoCloseTime to 5 seconds, the 1st seconds will show progress to 10/15 of the progressbar


how to use the progressbar just like vb6 progressbar control

----------


## fafalone

To do the progress bar in the demo, I had hard coded the range...


```
Private Sub TaskDialogAC_DialogCreated(ByVal hwnd As Long)
TaskDialogAC.ProgressSetRange 0, 15
TaskDialogAC.ProgressSetState ePBST_ERROR
End Sub

Private Sub TaskDialogAC_Timer(ByVal TimerValue As Long)
On Error Resume Next
TaskDialogAC.Footer = "Closing in " & TaskDialogAC.AutocloseTime & " seconds..."
TaskDialogAC.ProgressSetValue 15 - TaskDialogAC.AutocloseTime
On Error GoTo 0
End Sub
```

The 15 is those functions represents the 15 seconds in the demo dialog, so it would instead be 20 or 5.
You'll likely want to set up a module-level variable with your time and use that instead....


```
Private lTimeLimit As Long

Private Sub TaskDialogAC_DialogCreated(ByVal hwnd As Long)
TaskDialogAC.ProgressSetRange 0, lTimeLimit
TaskDialogAC.ProgressSetState ePBST_ERROR
End Sub

Private Sub TaskDialogAC_Timer(ByVal TimerValue As Long)
On Error Resume Next
TaskDialogAC.Footer = "Closing in " & TaskDialogAC.AutocloseTime & " seconds..."
TaskDialogAC.ProgressSetValue lTimeLimit - TaskDialogAC.AutocloseTime
On Error GoTo 0
End Sub

'[...]
lTimeLimit = 20
.Footer = "Closing in " & lTimeLimit & " seconds..."
.Title = "cTaskDialog Project"
.AutoCloseTime = lTimeLimit
'[...]
```

It's controlled manually like that in general, there's no automatic progressbar for autoclose, just the normal one that's synced to the time as it counts down; it's got all the features of a standard progress bar... see the end of post #2 in this thread for an example and documentation of all the calls.

----------


## loquat

thanks fafalone, it works now

----------


## loquat

found that, it works fine in vba now...

----------


## AAraya

fafalone - thanks so much for making this available.  I'll put it to good use modernizing my message boxes!  

I am seeing a problem however which is a bit unsettling.  When I run the demo for a while, one of two problems will happen - MZTools will crash or the VB6 IDE will lock up.  I'm concerned that this may point to some instability in the code somewhere - memory leaks, unreleased handles, subclassing which is unsafe in the IDE, etc...  Have you or anyone else seen instability issues running this code?

----------


## Cube8

I think MZTools is a bit unstable. I have used it in the past (before even fafalone released this class) and I also had some issues.

----------


## AAraya

> I think MZTools is a bit unstable. I have used it in the past (before even fafalone released this class) and I also had some issues.



I have not seen this issue before with MZTools.  It happens only when running this demo.  Looking at the code I see that it uses subclassing.  I've always found subclassing to be a bit wonky and dangerous when run from the IDE.  I've added code which checks if it's running in the IDE and if so, don't execute the subclassing related code.  I'll monitor if this resolves the issue - so far it has.

----------


## fafalone

Its possible theres some issue with subclassing in the IDE... it's hard to track though when its something that would be tough to reproduce. I've been meaning to swap out the subclassing methods on this project to eliminate the requirement for a extra module anyway, sounds like a good excuse for me to just do that, as the self-subclass technique seems much more stable in the IDE in any case.

----------


## AAraya

I discovered two problems in your custom flag values - they both conflict with already defined values.

Custom flag TDF_DATETIME conflicts with the existing TDF_SIZE_TO_CONTENT (&H1000000)
Custom flag TDF_USE_SHELL32_ICONID conflicts with the existing  TDF_NO_SET_FOREGROUND (&H10000)

The first one is the one which caused me to discover the problem.  When setting the dialog to size to content, a datetime control was appearing on the dialog.  

I've changed the values for these two custom flags to the next available values:
TDF_USE_SHELL32_ICONID = &H4000000
TDF_DATETIME = &H8000000 

And all works fine on my end.

----------


## fafalone

It seems I omitted TDF_NO_SET_FOREGROUND because it's new to Win8 and I must have copied the Win7 SDK, 

But it any case, it seems I completely forgot about the nearly finished next version, in which I had already corrected the conflicts  :EEK!:

----------


## fafalone

*cTaskDialog 1.1 Released!*
The flag issues mentioned by AAraya have been fixed along with several other improvements, including now being self-contained without a .bas needed.



```
'NEW IN VERSION 1.1
'--The cTaskDialog class is now entirely self-contained- using Paul Caton/LaVolpe's self-sub
'  self-callback routines to eliminate the need for a module or presence of the TaskDialog__Procs.
'--Additional left-right-center alignment for custom controls has been added. For the additions,
'  they are only relevent if the control is being used with a fixed width. Given this, automatic
'  prevention of only allowing 1 control per location has been disabled-- however, it is the on
'  the caller to ensure there's no overlap.
'--To further aid in this, you can now specify a manual offset with .(control)OffsetX that is
'  added to the default offset after alignment calculation. Note that for right alignment, to get
'  the control farther to the left, you'd want to make this a negative number.
'--ComboText now also a let that sets the text. This does not add an item and cannot set an image
'  if an imagelist is in use; for those features use ComboSetCurrentState
'--Added Get/Let for Combo dropdown width (.ComboDropWidth). Returns 0 unless previously set. Can
'  modify an open dialog.
'--Bug fix: TDF_NO_SET_FOREGROUND flag, which was added in Windows 8, was missing.
'--Bug fix: Alignment issues with certain flags with the DateTime control when placed in the footer.
'--Bug fix: Flag conflicts with built in ones and omitted flags. Fixed.
```

----------


## Mith

VB: VB6 sp6
OS: Win7 / Win10




> *cTaskDialog 1.1 Released!*


i found a bug using the Flag TDF_INPUT_BOX:

i specifed a file name for the _InputText_ that is larger than the width of the showed input textbox and i cannot scroll to the end of the file name:



currently the file name is truncated inside the textbox.

any idea how to scroll to the end of the file name inside the textbox?

BTW: this problem also occurs with v1.0

----------


## Mith

i solved the problem myself:

add the new line to the class header:



```
Private Const ES_AUTOHSCROLL As Long = &H80&
```

at the Sub AddInputBox() alter the line:



```
dwStyle = WS_VISIBLE Or WS_CHILD Or WS_TABSTOP Or ES_LEFT
```

to:



```
dwStyle = WS_VISIBLE Or WS_CHILD Or WS_TABSTOP Or ES_LEFT Or ES_AUTOHSCROLL
```

----------


## fafalone

Thanks for letting me know, fix will be included in the next release.

----------


## gilman

I have download the project but it has fail to load in the IDE because the file mIcon.bas isn't in the zip

----------


## fafalone

Wow I have no idea how that version got the names mixed up. The mTDSample.bas included in the zip is the only module you need; you can just add that or re-download it, I updated it.

In the vbp it had Module=mTDSample; mIcon.bas, so showed as mTDSample on my screen... but I don't know how the project was even loading on my end given that I deleted the old module... that's an odd one.

Sorry about that.

----------


## Steve Grant

Hi faf, I am still using 0.7 as that provides all I need except for two things. The most important: how do I pre-check the verification checkbox? Secondly the default button has no effect. I've tried 1.1 and I still can't pre-check. I've been trying clickverification 1.

Cheers to you.

----------


## fafalone

The easy way to pre-check that button is to use TDF_VERIFICATION_FLAG_CHECKED as one of the .Flags


ClickVerification has a bug I just noticed anyway; it's using TDM_CLICK_BUTTON, which is a regular button click that will close the dialog, where it should have TDM_CLICK_VERIFICATION. If you still wanted to use that method instead just switch the variable name, and it has to be called while the dialog is active, so in the _DialogCreated event or somewhere else.

==

And for the default button, are you using .CommonButtons or custom buttons? If custom, it should just be the straightforward button id. But for CommonButtons, there was a bug in 0.7 that was subsequently fixed as of v1.0; in 0.7 the Enum that pops up is TDBUTTONS, but to set a different default for common buttons, you need to specify the button by its TDRESULT value. So for the 'No' button you'd use TD_NO instead of TDCBF_NO_BUTTON.

----------


## Steve Grant

Thanks faf, works like a charm.

----------


## fafalone

*cTaskDialog 1.2 Released!*

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.



```
'Version 1.2 Release 2 (R2)
'--Bug fix: Quick bug fix for footer icons not redrawing when changed during runtime.
'NEW IN VERSION 1.2
'--Since it turns out, when you navigate to a new page, it doesn't activate the new TaskDialog
'  object, it just takes the settings and applies them to the original object. So the outcome
'  of this was that only the settings that are part of the native TaskDialog would be applied,
'  and see also the bugfix related to sending messages to active dialogs. Any of the custom
'  features wouldn't get applied. All settings are transferred now.
'  NOTE: Since this has to be done by overwriting the current configuration set, if you move
'    back to an earlier page, you'll need to reset the settings first. All of them, not just
'    customizations.
'--Added InputBoxTextAlign property to set left/right/center alignment.
'--Bug fix: Flag for DateTime and Slider were assigned same value.
'--Bug fix: Input box should have had ES_AUTOHSCROLL set.
'--Bug fix: In ClickVerification, it used the message for a button click, which led to either
'           closing the dialog or doing nothing, instead of TDM_CLICK_VERIFICATION, the correct
'           message to send for modifying the checkbox through code.
'--Bug fix: Navigating to a new page zero'd the hWnd on the expectation it would change, but
'           this expectation was incorrect. It shouldn't be zero'd and any action applied should
'           be sent to the main TaskDialog object, the API messages will apply to the current
'           page. E.g. TaskDialog1 is the first page, which loads a second page with object
'           TaskDialog2. When navigating to TaskDialog2, TaskDialog1 receives a Navigated() event,
'           and if TaskDialog2 has a marquee progress bar, call TaskDialog1.ProgressStartMarquee.
```


In the Demo project, there's a new 'Advanced Multi Page Dialog' button that has a setup with 3 screens:

This shows a number of ways of doing things with multiple pages, including interacting with custom controls. When using multiple pages, be sure to set the .PageIndex property, because every event is handled in the main dialog, so you'll want to check which page is sending the button click. Also shown is going back to an earlier page; note that this requires resetting the configuration of the dialog again, because your initial settings for the page were overwritten when it navigated, which loads the new page _into_ the original-- it doesn't actually load a new dialog. 
It uses a login dialog borrowed from another example, showing how you can use the pageindex to still handle updating the current dialog. Then finally, it shows taking data from that login page (or the first page skipping the log in), and using it to set the text of the last page; and how to start the marquee on the last page.

While it's a little complicated to get the hang of, you can get some really fancy multi-dialog systems set up to accomplish a wide variety of tasks, especially now with the new handling methods that allowing use of the customizations and really anything more than dead simple things. 
I'll definitely make some better examples in the future, building up from the base like with the original stuff.

----------


## loquat

i would like to add manifest into vb6.exe to avoid declare changing in IDE



> <dependency>
> 		<dependentAssembly>
> 			<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
> 		</dependentAssembly>
> 	</dependency>

----------


## AAraya

I use the TDF_ENABLE_HYPERLINKS and TDF_EXEC_HYPERLINKS flags to create hyperlinks in my dialogs which take the user to a web page.  This works great.  
Is there a way to have a click on one of these hyperlinks to fire a routine in my own code? If not, is there some other way to do so with this control?

----------


## AAraya

> I use the TDF_ENABLE_HYPERLINKS and TDF_EXEC_HYPERLINKS flags to create hyperlinks in my dialogs which take the user to a web page.  This works great.  
> Is there a way to have a click on one of these hyperlinks to fire a routine in my own code? If not, is there some other way to do so with this control?


I decided to create a custom button for this.  Works great.

----------


## fafalone

When you click a hyperlink, the TaskDialog_HyperlinkClick fires; you can do whatever you want there, you don't need to call ShellExecute. Make sure you're _not_ using the TDF_EXEC_HYPERLINKS flag, because that sets the dialog to call ShellExecute on its own.

----------


## AAraya

> When you click a hyperlink, the TaskDialog_HyperlinkClick fires; you can do whatever you want there, you don't need to call ShellExecute. Make sure you're _not_ using the TDF_EXEC_HYPERLINKS flag, because that sets the dialog to call ShellExecute on its own.


Thanks!  Did not know that. I'm happy with my custom button in this case but this might come in handy down the road.

I don't see that in my version - you must have added that after version 1?

----------


## fafalone

Don't see what?

The HyperlinkClick event has been there since the very beginning; the TDF_EXEC_HYPERLINKS flag was added in Version 0.5.1. Although it does seem I neglected to ever mention that in the changelog.

----------


## zeilo

If the Input Text control is left empty by the user, the *Get InputText Property* returns the default value of InputText (saved into module variable sEditText) when the dialog was initiated.

*Original*


```
Public Property Get InputText() As String
    If hEditBox Then
            Dim lLen As Long, sText As String
            lLen = SendMessageW(hEditBox, WM_GETTEXTLENGTH, 0, ByVal 0&) * 2
            If lLen Then
                sText = Space$(lLen)
                Call SendMessageW(hEditBox, WM_GETTEXT, lLen, ByVal sText)
                sEditText = StrConv(sText, vbFromUnicode)
            End If
    End If
    InputText = sEditText
End Property
```

*Proposed*


```
Public Property Get InputText() As String
    If hEditBox Then
            Dim lLen As Long, sText As String
            lLen = SendMessageW(hEditBox, WM_GETTEXTLENGTH, 0, ByVal 0&) * 2
            If lLen Then
                sText = Space$(lLen)
                Call SendMessageW(hEditBox, WM_GETTEXT, lLen, ByVal sText)
                sEditText = StrConv(sText, vbFromUnicode)
            Else
                sEditText = vbNullString
            End If
    End If
    InputText = sEditText
End Property
```

Best Regards!

----------


## Semke

is it possible to have multiple TDF_INPUT_BOX or multiple  TDF_DATETIME

----------


## fafalone

It's certainly possible to do, but the control as is isn't set up for that.

----------


## jedifuk

hi guys, i know this is stupid and newbie question. I manage to embeded manifest vb6.exe.manifest to my IDE vb6, and apply this taskdialog during design time
But after I compile and run time..Error 453, Unable to point entry to comctl32.dll

Can someone guide me, how can i manage to show this taskdialog in runtime

thanks

UPDATE : never mind, i figured it out, never play with manifest exe before.... :Smilie:

----------


## Steve Grant

You also need to manifest your compiled .exe.

----------


## jedifuk

> You also need to manifest your compiled .exe.


thanks, i figured it out by applying .res file into my project IDE, and compile it. after that another appname.exe.res in same folder
all things work well...on WINDOWS 7...but when I tried to use it on Windows 10... some controls error. I checked it and raised error "invalid property value" on VKUSERCONTROLS (free controls which apply xp style and support unicode)..

But why no error on Windows 7, still confuse

----------


## Steve Grant

Be careful with VKuserControls they only partially support Unicode. The textboxes for example do not. I have also found that those controls are not as stable as I would like on Win10

----------


## fafalone

If your project already has a manifest, you can use something like Krool's Common Controls replacement; they'll apply the newer visual styles in a manifested app and all support Unicode.



Also, your manifest for your exe goes in the .res file, then you add that to your project in the IDE, it gets compiled in, and that's it. You don't need to keep the .res file with your compiled .exe, or an external manifest file, once there's one embedded in the exe.

----------


## jedifuk

> Be careful with VKuserControls they only partially support Unicode. The textboxes for example do not. I have also found that those controls are not as stable as I would like on Win10


that is something for my next task. I used vkusercontrols on all my forms to gain xp styles..think I need to switch to standard controls




> If your project already has a manifest, you can use something like Krool's Common Controls replacement; they'll apply the newer visual styles in a manifested app and all support Unicode.


thank you..this is what I intend to do

----------


## jedifuk

Also, your manifest for your exe goes in the .res file, then you add that to your project in the IDE, it gets compiled in, and that's it. You don't need to keep the .res file with your compiled .exe, or an external manifest file, once there's one embedded in the exe.[/QUOTE]

I am really newbie in manifest things..guess everything always require first step..I will try it..

Out of thread : I don't know if someone can do something with VB6 language, maybe upgrade to support 64bit. I still saw a lot of potential with VB, I am looking at Twinbasic, hopefully every VB programmer wishes will come into this TwinBasic

----------


## Little John

hi fafalone,

for a special purpose I would like to run the dialog vbModeless.

Is that possible somehow?

----------


## Cube8

> hi fafalone,
> 
> for a special purpose I would like to run the dialog vbModeless.
> 
> Is that possible somehow?


Just don't assign anything to hWndParent property.

----------


## 7edm

Just found this incredible project some time ago and been playing with it, getting more and more advanced. One of the most useful CodeBank items I would say. One thing I'm missing though is the ability to have an "ItemData" property for the ComboBox as it's not always useful or convenient to identify items in a sequential numeric order like in a ListIndex. I don't know if it's possible to extend the combo itself in that way but something like an ID as with .AddCustomButton would do. The use case for this is typically if you read combo items from a DB.

----------


## fafalone

Definitely possible, just a few small changes. Not sure when I'll be ready with a new version, but the way you'd go about it is to add an lParam: Add the bolded code in the functions below:



```
Private Type ctdComboItem
    sText As String
    iImage As Long
    iOverlay As Long
    lpData As Long
End Type


Public Sub ComboAddItem(sText As String, Optional iImage As Long = -1, Optional iOverlay As Long = -1, Optional lParam As Long = 0)
If aComboItems(0).sText = "" Then
    aComboItems(0).sText = sText
    aComboItems(0).iImage = iImage
    aComboItems(0).iOverlay = iOverlay
    aComboItems(0).lpData = lParam
    Exit Sub
End If
ReDim Preserve aComboItems(UBound(aComboItems) + 1)
aComboItems(UBound(aComboItems)).sText = sText
aComboItems(UBound(aComboItems)).iImage = iImage
aComboItems(UBound(aComboItems)).iOverlay = iOverlay
aComboItems(UBound(aComboItems)).lpData = lParam
If hCombo Then
    CBX_InsertItem hCombo, sText, iImage, iOverlay, lParam
End If
End Sub

Public Property Get ResultComboData() As Long: ResultComboData = aComboItems(nComboIdx).lpData: End Property
```

I'll have to test it later but that's about the extent of what you'd need, just an extra field to store a long that's either the data itself or a pointer/key for where to get it. I added it to the combo itself just in case there's a need, but storing it in the local structure that holds the items should be enough.

----------


## 7edm

Thanks, I will test this code in implementation with my program. In my use, ItemData as used with ordinary VBCombo is typically a primary key ID for an item read from a database in code and not a value that a user normally would supply/submit. So when reading the items captions from the DB it may be a partial set, or sorted alphabetically (or on other parameter), so identification by 'listindex' would not be very efficient or even possible. For now I have worked around it by adding an external dynamic array storing the data in listindex order but of course it's much better to have all this logic inside the class and control. Nothing fancy really, just the ability to store a long with each combo item.

----------


## 7edm

Looking at the code, I wonder if also these changes aren't needed?


```
Public Sub zzComboGetItemData(lMax As Long, sText() As String, iImage() As Long, iOverlay() As Long, lParam() As Long)
Attribute zzComboGetItemData.VB_MemberFlags = "40"
Dim nct As Long
nct = UBound(aComboItems)
lMax = nct
ReDim sText(nct)
ReDim iImage(nct)
ReDim iOverlay(nct)
ReDim lParam(nct)
Dim i As Long
For i = 0 To UBound(aComboItems)
    sText(i) = aComboItems(i).sText
    iImage(i) = aComboItems(i).iImage
    iOverlay(i) = aComboItems(i).iOverlay
    lParam(i) = aComboItems(i).lpData
Next i
End Sub
```


and


```
    Dim cbT() As String, cbI() As Long, cbO() As Long, cbD() As Long, nct As Long
    cTD.zzComboGetItemData nct, cbT, cbI, cbO, cbD
    ReDim aComboItems(nct)
    For i = 0 To nct
        aComboItems(i).sText = cbT(i)
        aComboItems(i).iImage = cbI(i)
        aComboItems(i).iOverlay = cbO(i)
        aComboItems(i).lpData = cbD(i)
    Next i
```

Then also the use of this variable need to be updated as it's uses the same Type structure?


```
Private tComboInit As ctdComboItem
```

----------


## 7edm

I think this addition also needs to be made


```
Public Sub Init()
'resets the entire module
...
tComboInit.iOverlay = 0
tComboInit.lpData = 0
```

I'm also experimenting with adding a 'NewIndex" property as that can be useful in some cases. For that I have added to the above code


```
tComboInit.iOverlay = 0
tComboInit.lpData = 0
nComboNewIndex = 0
```

and this in declaration


```
Private nComboIdx As Long
Private nComboNewIndex As Long
```

property


```
Public Property Get ComboHeight() As Long: ComboHeight = cyCombo: End Property

Public Property Get ComboNewIndex() As Long: ComboNewIndex = nComboNewIndex: End Property

Public Property Let SliderAlign(nAlign As TDInputBoxAlign): nSliderAlign = nAlign: End Property
```

and finally I made these changes to ComboAddItem and at the same time cut down on the use of UBound() to make the code a tad more efficient


```
Public Sub ComboAddItem(sText As String, Optional iImage As Long = -1, Optional iOverlay As Long = -1, Optional lParam As Long = 0)
If aComboItems(0).sText = "" Then
    aComboItems(0).sText = sText
    aComboItems(0).iImage = iImage
    aComboItems(0).iOverlay = iOverlay
     aComboItems(0).lpData = lParam
    Exit Sub
End If
nComboNewIndex = UBound(aComboItems) + 1
ReDim Preserve aComboItems(nComboNewIndex)
aComboItems(nComboNewIndex).sText = sText
aComboItems(nComboNewIndex).iImage = iImage
aComboItems(nComboNewIndex).iOverlay = iOverlay
aComboItems(nComboNewIndex).lpData = lParam
If hCombo Then
    CBX_InsertItem hCombo, sText, iImage, iOverlay, lParam
End If
End Sub
```

Maybe you prefer the repeated calls to UBound as your coding style, I just try to avoid unnecessary calls :-)

----------


## 7edm

Hi again, I think I have found something at least I concider to be a bug.
In the ProcessCallback function, if the combostyle is editable and the combo text field is left empty when submitting the dialog (in this case clicking a custom button)


```
    Case TDN_DESTROYED
        If hEditBox Then
            Dim lLen As Long, sText As String
            lLen = SendMessageW(hEditBox, WM_GETTEXTLENGTH, 0, ByVal 0&) * 2
            If lLen Then
                sText = Space$(lLen)
                Call SendMessageW(hEditBox, WM_GETTEXT, lLen, ByVal sText)
                sEditText = StrConv(sText, vbFromUnicode)
            End If
        End If
        If hCombo Then
            sComboText = GetComboTextW(hEditCombo)
```

the sComboText variable will get assigned a Chr$(0) so you can never make the test 


```
If len(Trim$(.ResultComboText)) > 0 Then
```

 or 


```
If Trim$(.ResultComboText) = "" Then
```

so I think the returned string somehow needs to be tested for Chr$(0) and removed if exists.

----------


## fafalone

ah good call, I'd tend to fix it at the source, GetComboText was adding a 1 to the length even if it was 0. Can't recall if the extra 1 is needed for anything or not, but in case:


```
Private Function GetComboTextW(hEdit As Long) As String
Dim bytS() As Byte
Dim ch As Long
ch = SendMessageW(hEdit, WM_GETTEXTLENGTH, 0, ByVal 0&) * 2
If ch = 0& Then Exit Function
ch = ch + 1
ReDim Preserve bytS(ch)
ch = SendMessageW(hEdit, WM_GETTEXT, ch, ByVal VarPtr(bytS(0))) * 2 - 1
If ch >= 0 Then
    ReDim Preserve bytS(ch)
End If
GetComboTextW = CStr(bytS)

End Function
```

The return on that len=0 for empty boxes.

Will also add the other parts of the extra data you found, thanks.

----------


## 7edm

Hi, I'm doing some quality control of my code as I'm setting up unit testing, and wonder *is this a bug?*


```
Public Property Get DefaultRadioButton() As Long: DefaultButton = uTDC.nDefaultRadioButton: End Property
```

shouldn't it be



```
Public Property Get DefaultRadioButton() As Long: DefaultRadioButton = uTDC.nDefaultRadioButton: End Property
```

?
in cTaskDialog

EDIT/ADDING:
and same with this


```
Public Property Get DateTimeAlign() As TDInputBoxAlign: ComboAlign = nDateTimeAlign: End Property
```

to be


```
Public Property Get DateTimeAlign() As TDInputBoxAlign: DateTimeAlign = nDateTimeAlign: End Property
```

and this


```
Public Property Get DateTimeAlignInButtons() As TDControlAlign: DateTimeAlignInFooter = nDTButtonAlign: End Property
```

to be


```
Public Property Get DateTimeAlignInButtons() As TDControlAlign: DateTimeAlignInButtons = nDTButtonAlign: End Property
```

----------


## 7edm

ooou, another one maybe, at least the code doesn't make sense to me.


```
Public Sub EnableButton(ButtonID As Long, lEnable As Long)
'lEnable=0 disable; <>0 enable
...
    Else
        Dim lnew() As Long
        ReDim lnew(0)
        Dim i As Long, k As Long
        For i = 0 To UBound(lBtnDis)
            If lBtnDis(i) <> ButtonID Then
                ReDim Preserve lnew(k)
                lnew(k) = lBtnDis(i)
            End If
        Next
        lBtnDis = lnew
    End If
     ...
End Sub
```

doesn't variable k need to be incremented here? and is the first ReDim of lnew() really necessary?

----------


## fafalone

Good catches, I'll update the project when I have a chance.

1st redim is probably not neccessary but I've gotten burned by trying to refer to ubound(array) only to be told it's out of bounds so many times I've gotten in the habit of immediately putting redim(0) after declaring an array since it will usually help and doesn't hurt.

----------


## 7edm

Here is another one, SimpleDialog shouldn't it be 'As TDRESULT' rather than 'As TDBUTTONS' ? As it is now the intellisense gives wrong suggestions, which doesn't match with what's returned.

----------


## Mith

cTaskDialog v1.2 R2 

i have a string conversion problem when using the class on a chinese windows system.

The function StrConv converts the characters from a string wrong:

BEFORE:


AFTER:


Everyone should avoid to use the function StrConv if your app will run on a windows system with unicode language like chinese, russian etc.

Do you have a replacement for the StrConv-function?
Maybe a self-written function to convert the wide-unicode-string to a VB-String?

The StrConv is used here in your code:



```
Public Property Get InputText() As String
    If hEditBox Then
            Dim lLen As Long, sText As String
            lLen = SendMessageW(hEditBox, WM_GETTEXTLENGTH, 0, ByVal 0&) * 2
            If lLen Then
                sText = Space$(lLen)
                Call SendMessageW(hEditBox, WM_GETTEXT, lLen, ByVal sText)
                sEditText = StrConv(sText, vbFromUnicode)
            Else
               sEditText = vbNullString
            End If
    End If
    InputText = sEditText
End Property
```



```
Case TDN_DESTROYED
        If hEditBox Then
            Dim lLen As Long, sText As String
            lLen = SendMessageW(hEditBox, WM_GETTEXTLENGTH, 0, ByVal 0&) * 2
            If lLen Then
                sText = Space$(lLen)
                Call SendMessageW(hEditBox, WM_GETTEXT, lLen, ByVal sText)
                sEditText = StrConv(sText, vbFromUnicode)
            End If
        End If
```

----------


## Mith

I fixed the problem with the wrong text conversion!

Now the TaskDialog returns the correct unicode string!

Can you add the fix to your next release?


I added this API declaration:



```
Private Declare Function SendMessageW2 Lib "user32" Alias "SendMessageW" (ByVal hWnd As Long, _
                                                   ByVal wMsg As Long, _
                                                   ByVal wParam As Long, _
                                                   ByVal lParam As Long) As Long
```

And changed the code from:



```
Case TDN_DESTROYED
        If hEditBox Then
            Dim lLen As Long, sText As String
            lLen = SendMessageW(hEditBox, WM_GETTEXTLENGTH, 0, ByVal 0&) * 2
            If lLen Then
                sText = Space$(lLen)
                Call SendMessageW(hEditBox, WM_GETTEXT, lLen, ByVal sText)
                sEditText = StrConv(sText, vbFromUnicode)
            End If
        End If
```

to



```
Case TDN_DESTROYED
        If hEditBox Then
            Dim lLen As Long, sText As String
            lLen = SendMessageW(hEditBox, WM_GETTEXTLENGTH, 0, ByVal 0&) * 2
            If lLen Then
                sText = Space$(lLen)
                Call SendMessageW(hEditBox, WM_GETTEXT, lLen, StrPtr(sText))
                sEditText = Left$(sText, InStr(sText, Chr$(0)) - 1)
            End If
        End If
```

and i changed this code:



```
Public Property Get InputText() As String
    If hEditBox Then
            Dim lLen As Long, sText As String
            lLen = SendMessageW(hEditBox, WM_GETTEXTLENGTH, 0, ByVal 0&) * 2
            If lLen Then
                sText = Space$(lLen)
                Call SendMessageW(hEditBox, WM_GETTEXT, lLen, ByVal sText)
                sEditText = StrConv(sText, vbFromUnicode)
            Else
               sEditText = vbNullString
            End If
    End If
    InputText = sEditText
End Property
```

to



```
Public Property Get InputText() As String
    If hEditBox Then
            Dim lLen As Long, sText As String
            lLen = SendMessageW(hEditBox, WM_GETTEXTLENGTH, 0, ByVal 0&) * 2
            If lLen Then
                sText = Space$(lLen)
                Call SendMessageW2(hEditBox, WM_GETTEXT, lLen, StrPtr(sText))
                sEditText = Left$(sText, InStr(sText, Chr$(0)) - 1)
            Else
               sEditText = vbNullString
            End If
    End If
    InputText = sEditText
End Property
```

----------


## Visualman

If I set the .ComboStyle = cbtDropdownList property, then the .ResultComboText property always returns empty.

.ResultComboIndex returns correctly.



```
With TaskDialog1
    .Init
    .MainInstruction = "Duplicates"
    .Content = "If you want to exclude an Artists name from the search:"
    .Flags = TDF_COMBO_BOX
    .AddCustomButton 100, "Continue"
    .CommonButtons = TDCBF_CANCEL_BUTTON
    .ComboStyle = cbtDropdownList 'cbtDropdownEdit
    .IconMain = IDI_QUESTION
    .Title = "cTaskDialog Project"
    .ComboSetInitialState "", 5
    .ComboSetInitialItem 1
    .ComboAddItem "Item 1"
    .ComboAddItem "Item 2"
    .ComboAddItem "Item 3"
    .ParenthWnd = Me.hwnd
    .ShowDialog

    Label1.Caption = .ResultComboText
    Labe2.Caption = .ResultComboIndex

End With
```

----------


## fafalone

@Mith, thanks, will add to next release.

@Visualman: This is expected behavior. .ResultComboText is only meant to return the text of a regular combobox, not the item text for the DropdownList style. If you need to refer back to the item text for a dropdownlist item, you can store the entries in a module-level variable before adding them to the class, then access that variable based on the returned index.

----------


## nav786te

Hi
Kindly some one help with setting up Progressbar for Taskdialog.
I cannot figureout how to interact with progress to update it after it shows in my Sub.
You cannot open it as non-blocking so you can update progress manulayy whenever I need as required during my code is running.

----------


## fafalone

There's an example of that in the set of examples in the first couple posts:





> 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.




```
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
```

----------


## nav786te

Hi
Thanks for your quick response. This is what I have already explored and workin gok for examples for progressbars we have.
But my issue is I have subs in a Module (one example is attached for Microsoft Access). If I run the Taskdialog before the actual tasks in my subs, I cannot go back to update the progress as Taskdialog does not let even if I don't bind hwnd to nothing.

Secondly If I take all my sub into Taskdialog timer event, it's all gets crazy.
Is there no way to just open the dialog and then within my sub I just update the progress wheneve something is completed.
I'm using Statusbar progress monitor in my sub but I wan t to replace it with Taskdialog as I repaced all other messagebox with it.


Regards

----------


## fafalone

Just like the MsgBox, the TaskDialog isn't asynchronous so the only option that doesn't involve doing it through the timer callback would be running it in a different thread. Which is very difficult in VB6 and I don't know if it can even be done in VBA; perhaps this kind of progress dialog is more appropriate for the task?

----------


## fafalone

This project now has a Universal version that works in twinBASIC x86/x64 and VBA7 x84/x64, in addition to VB6 and VBA6.

For now I'm leaving the version in this thread up, but the next feature update will use the universal codebase (you could drop in cTaskDialog.cls and modTDHelper from the universal codebase into the VB6 version in this thread too).

[twinBASIC/VB6/VBA7] TaskDialogIndirect: cTaskDialog universal implementation x86/x64 

There's a neat workaround for the fact alignment issues make it difficult to call in VBA7x64  :Smilie:

----------

