cTaskDialog is the sequel to my previous TaskDialogIndirect project, mTaskDialog. This version adds support for all TaskDialogIndirect features, including the progress bar, timer feedback, updating the text and icons while the dialog is open, and events for all the notifications. It's also much easier to use. VB6 can use many modern Wndows features, and the TaskDialog is one of the most useful.
**Recently Added Features**
Project Update - 15 Jun 2024
Version 1.5 Universal adds request features for Combo Item lParam access, and no longer requires a manifest. While it's still recommended you use one, if it detects comctl6 isn't present, it will now use the SxS Activation Context API to briefly activate it when calling TaskDialogIndirect so you no longer get an entry point not found error. This is primarily for 32bit Excel, which for some reason doesn't have comctl6 enabled like other 32bit Office apps and 64bit Excel; again, other sources should use a manifest if one isn't present.
Project Update - 17 Jan 2024
Additional undocumented Common Buttons added (abort, retry, ignore, and continue).
Project Update - 08 Oct 2023
Updating this post to Version 1.3 Universal. The features are the same with some bug fixes, but this version universally supports: VB6, VBA6, VBA7 32bit, VBA7 64bit, twinBASIC 32bit, and twinBASIC 64bit.. IMPORTANT: For compatibility, this version no longer uses self-subclassing, and like earlier versions, once again requires mTDHelper.bas in all projects. (mTDSample.bas is still only for the demo form).
Project Update - 21 March 2020
This version has some important improvements to functionality, including a very significant expansion of multi-page functionality, and some bug fixes, one of them critical. R2: A quick same-night update was made to fix an issue where the footer icon would go blank if it was changed during runtime if it was part of a mutli-page dialog. Full Details
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. The sample project includes Sub Main(), and see LaVolpe's Manifest Creator to create the manifests.
Setting Up cTaskDialog
Your project must include cTaskDialog.cls and mTDHelper.bas
mTDSample.bas is used only for the sample project.
Once you've got a project using the modern common controls, cTaskDialog is similar in use to a lot of other class modules, like the other common controls.
To initialize the class, put the following at the start of a form and in the Form_Load code:
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:
Code:
Dim td As TDBUTTONS
td = TaskDialog1.SimpleDialog("Is TaskDialogIndirect going to be better than this?", TDCBF_YES_BUTTON, App.Title, "This is regular old TaskDialog", TD_SHIELD_GRAY_ICON, Me.hWnd, App.hInstance)
Label1.Caption = "ID of button clicked: " & bt
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.
Code:
With TaskDialog1
.MainInstruction = "This is a simple dialog."
.CommonButtons = TDCBF_YES_BUTTON Or TDCBF_NO_BUTTON
.IconMain = TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.ShowDialog
If .ResultMain = TD_YES Then
Label1.Caption = "Yes Yes Yes!"
ElseIf .ResultMain = TD_NO Then
Label1.Caption = "Nope. No. Non. Nein."
Else
Label1.Caption = "Cancelled."
End If
End With
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:
Code:
.Init
.MainInstruction = "You're about to do something stupid."
.Content = "Are you absolutely sure you want to continue with this really bad idea?"
.CommonButtons = TDCBF_YES_BUTTON Or TDCBF_NO_BUTTON
.IconMain = TD_SHIELD_WARNING_ICON 'TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.ShowDialog
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.
Code:
With TaskDialog1
.Init
.MainInstruction = "You're about to do something stupid."
.Content = "Are you absolutely sure you want to continue with this really bad idea?"
.IconMain = TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.AddCustomButton 101, "YeeHaw!"
.AddCustomButton 102, "NEVER!!!"
.AddCustomButton 103, "I dunno?"
.ShowDialog
Label1.Caption = "ID of button clicked: " & .ResultMain
End With
Note that we have removed the .CommonButtons. If you specify buttons there as well, they will appear in addition to your custom buttons. 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.
Code:
.AddRadioButton 110, "Let's do item 1"
.AddRadioButton 111, "Or maybe 2"
.AddRadioButton 112, "super secret option"
.ShowDialog
Label1.Caption = "ID of button clicked: " & .ResultMain
Label2.Caption = "ID of radio button selected: " & .ResultRad
One of the other biggies are Hyperlinks. These require a few additional steps. First, you need to include TDF_ENABLE_HYPERLINKS in the .Flags property. Then, you add in the hyperlink as normal html, but the url needs to be in quotes. Then you'll need to use one of the events for the class. The most common thing to do is just execute the link, so that's what's shown here, but you could also get the URL back from the pointer and do something else with it. You must use ShellExecuteW, not ShellExecuteA (which is normally what just plain ShellExecute points to). The declare is included in the sample project.
Code:
With TaskDialog1
.Init
.MainInstruction = "Let's see some hyperlinking!"
.Content = "Where else to link to but <a href=""http://www.microsoft.com"">Microsoft.com</a>"
.IconMain = TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.CommonButtons = TDCBF_CLOSE_BUTTON
.Flags = TDF_ENABLE_HYPERLINKS
.ShowDialog
End With
Private Sub TaskDialog1_HyperlinkClick(ByVal lPtr As Long)
Call ShellExecuteW(0, 0, lPtr, 0, 0, SW_SHOWNORMAL)
End Sub
Let's talk about custom icons. You can have a custom icon for both the main icon and the footer icon.
Thanks to the brilliant idea of Schmidt over here, the option to specify an icon id from either shell32.dll or imageres.dll, the two main Windows icon libraries, has been added. This expands the icons you can show without using an hIcon. TDF_USE_SHELL32_ICONID, TDF_USE_IMAGERES_ICONID
Simply specify if you're going to use one of those sources by adding the above to the flags, and set the icon to the index you want. Only one can be used, so both header and footer index will come from the chosen DLL. Standard icons (the TDICONS group) work regardless of the source of other icons. NOTE: These do not run from 0-# of icons, so if your icon browser is telling you that, you need to use a different one, otherwise the dialog may display the wrong icon, or not show at all if the id doesn't exist.
Code:
With TaskDialog1
.Init
.MainInstruction = "Show me the icons!"
.Content = "Yeah, that's the stuff."
.Footer = "Got some footer icon action here too."
.Flags = TDF_USE_SHELL32_ICONID
.IconMain = 18
.IconFooter = 35
.Title = "cTaskDialog Project"
.CommonButtons = TDCBF_CLOSE_BUTTON
.ShowDialog
End With
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.
Code:
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.
The last basic feature is the verification checkbox. Here's an example with that, and all the other text fields.
Code:
With TaskDialog1
.Init
.MainInstruction = "Let's see all the basic fields."
.Content = "We can really fit in a lot of organized information now."
.Title = "cTaskDialog Project"
.Footer = "Have some footer text."
.CollapsedControlText = "Click here for some more info."
.ExpandedControlText = "Click again to hide that extra info."
.ExpandedInfo = "Here's a whole bunch more information you probably don't need."
.VerifyText = "Never ever show me this dialog again!"
.IconMain = TD_INFORMATION_ICON
.IconFooter = TD_ERROR_ICON
.ShowDialog
Label1.Caption = "ID of button clicked: " & .ResultMain
End With
One of the major stylistic differences are the CommandLink buttons. When using the Command Link style, the first line is considered the main text, and lines are made into sub-text. Note that the line is broken with vbLf only; not vbCrLf. vbCrLf results in the text not being smaller on Win7 x64, I haven't tested other systems but it should be the same.
With the custom button sample from above, these changes are made:
Code:
.Flags = TDF_USE_COMMAND_LINKS
.AddCustomButton 101, "YeeHaw!" & vbLf & "Put some additional information about the command here."
Advanced Features
The TaskDialog supports having a progress bar, both regular and marquee. To enable it, include the TDF_SHOW_PROGRESS_BAR or the TDF_SHOW_MARQUEE_PROGRESS_BAR flag (you can switch back and forth between them while the dialog is open if you want). Getting it to show up is the easy part, linking it to actual events in your program is where it gets a little tricky. There's some events that are provided that will help out...
TaskDialog_DialogCreated is triggered when the dialog is displayed, then all the buttons, the radio buttons, the expando button, the checkbox, and hyperlinks all have events when the user clicks them. In addition to that, TaskDialog_Timer is sent approximately every 200ms and includes a variable telling you how many ms has elapsed since the dialog appeared, or since it was reset with the .ResetTimer() call. The example shows a basic counter, but you can go further and enable/disable buttons and use hyperlinks to control things too.
Code:
Private bRunProgress As Boolean
Private lSecs As Long
With TaskDialog1
.Init
.MainInstruction = "You're about to do something stupid."
.Content = "Are you absolutely sure you want to continue with this really bad idea? I'll give you a minute to think about it."
.IconMain = TD_INFORMATION_ICON
.Title = "cTaskDialog Project"
.Footer = "Really, think about it."
.Flags = TDF_USE_COMMAND_LINKS Or TDF_SHOW_PROGRESS_BAR Or TDF_CALLBACK_TIMER
.AddCustomButton 101, "YeeHaw!" & vbLf & "Put some additional information about the command here."
.AddCustomButton 102, "NEVER!!!"
.AddCustomButton 103, "I dunno?"
.VerifyText = "Hold up!"
bRunProgress = True
.ShowDialog
End With
Private Sub TaskDialog1_DialogCreated(ByVal hWnd As Long)
If bRunProgress Then
Timer1.Interval = 1000
Timer1.Enabled = True
TaskDialog1.ProgressSetRange 0, 60
End If
End Sub
Private Sub TaskDialog1_Timer(ByVal TimerValue As Long)
If lSecs > 60 Then
Timer1.Enabled = False
bRunProgress = False
Else
TaskDialog1.ProgressSetValue lSecs
TaskDialog1.Footer = "You've been thinking for " & lSecs & " seconds now..."
End If
End Sub
Private Sub TaskDialog1_VerificationClicked(ByVal Value As Long)
If Value = 1 Then
Timer1.Enabled = False
bRunProgress = False
Else
bRunProgress = True
Timer1.Enabled = True
End If
End Sub
Private Sub Timer1_Timer()
lSecs = lSecs + 1
End Sub
Microsoft didn't document it very well, but it did provide a mechanism to have multiple pages. With multiple pages, you can have a command that takes the user forward to the next dialog in the sequence, or back to the previous one. Setting this up is a little complicated, but not too bad.
1) Create a new cTaskDialog and initialize the same as any other dialog.
2) Fill out all the configuration info you need before calling the first dialog.
3) When you add a button to a dialog to go forward or back, you must also use .SetButtonHold, otherwise the dialog closes when the button is clicked.
4) Add code to call .NavigatePage in the .ButtonClick event.
5) The new page will send a .Navigated event; this is instead of .DialogCreated. Also note that only the dialog that exits and returns a final result will send the .DialogDestroyed event.
Code:
Private WithEvents TaskDialog2 As cTaskDialog
Set TaskDialog2 = New cTaskDialog
With TaskDialog2
.Init
.Content = "Here's a whole new dialog with all the options."
.CommonButtons = TDCBF_OK_BUTTON
.IconMain = TD_SHIELD_OK_ICON
.Title = "cTaskDialog Project - Page 2"
End With
With TaskDialog1
.Init
.MainInstruction = "You can now have multiple pages."
.Content = "Click Next Page to continue."
.Flags = TDF_USE_COMMAND_LINKS
.AddCustomButton 200, "Next Page" & vbLf & "Click here to continue to the next TaskDialog"
.CommonButtons = TDCBF_YES_BUTTON Or TDCBF_NO_BUTTON
.IconMain = TD_SHIELD_WARNING_ICON
.SetButtonHold 200
.Title = "cTaskDialog Project - Page 1"
.ShowDialog
End With
Private Sub TaskDialog1_ButtonClick(ByVal ButtonID As Long)
If ButtonID = 200 Then
TaskDialog1.NavigatePage TaskDialog2
End If
End Sub
Here are the progress bar calls:
.ProgressSetRange(Min,Max)
Sets the range
.ProgressSetState(state)
Sets the progress bar state; ePBST_NORMAL for a green bar, ePBST_PAUSED for yellow, ePBST_ERROR for red.
.ProgressSetValue(value)
Sets the value of the progress bar.
.ProgressSetType(value)
Allows switching between a regular bar and marquee style bar. 0 for regular, 1 for marquee.
.ProgressStartMarquee([speed])
Starts the marquee; the bar must be set to marquee style, either originally, or changed to it, in order for this to work. If speed is not specified, the default of 30 is used.
.ProgressStopMarquee()
Stops the marquee.
Here's the code for the everything box at the top; note how the use of the cancel button adds the X and icon to the title bar (those can appear without a cancel button using the TDF_ALLOW_DIALOG_CANCELLATION flag).
Code:
Private bRunMarquee As Boolean
Dim hIconM As Long, hIconF As Long
hIconM = ResIconTohIcon("ICO_CLOCK", 32, 32)
hIconF = ResIconTohIcon("ICO_HEART", 16, 16)
With TaskDialog1
.Init
.MainInstruction = "Let's see it all!"
.Content = "Lots and lots of features are possible, thanks <a href=" & Chr(34) & "http://www.microsoft.com" & Chr(34) & ">Microsoft</a>"
.IconMain = hIconM
.IconFooter = hIconF
.Flags = TDF_USE_HICON_MAIN Or TDF_USE_HICON_FOOTER Or TDF_ENABLE_HYPERLINKS Or TDF_USE_COMMAND_LINKS Or TDF_SHOW_MARQUEE_PROGRESS_BAR Or TDF_CAN_BE_MINIMIZED
.Title = "cTaskDialog Project"
.Footer = "Have some footer text."
.CollapsedControlText = "Click here for some more info."
.ExpandedControlText = "Click again to hide that extra info."
.ExpandedInfo = "Here's a whole bunch more information you probably don't need."
.VerifyText = "Never ever show me this dialog again!"
.CommonButtons = TDCBF_RETRY_BUTTON Or TDCBF_CANCEL_BUTTON Or TDCBF_CLOSE_BUTTON Or TDCBF_YES_BUTTON
.AddCustomButton 101, "YeeHaw!" & vbLf & "Some more information describing YeeHaw"
.AddCustomButton 102, "NEVER!!!"
.AddCustomButton 103, "I dunno?" & vbLf & "Or do i?"
.AddRadioButton 110, "Let's do item 1"
.AddRadioButton 111, "Or maybe 2"
.AddRadioButton 112, "super secret option"
.EnableRadioButton 112, 0
.EnableButton 102, 0
.SetButtonElevated TD_RETRY, 1
bRunMarquee = True
.ShowDialog
bRunMarquee = False
End With
Private Sub TaskDialog1_DialogCreated(ByVal hWnd As Long)
If bRunProgress Then
Timer1.Enabled = True
TaskDialog1.ProgressSetRange 0, 60
End If
If bRunMarquee Then
TaskDialog1.ProgressStartMarquee
End If
End Sub
That's the basic feature set. The class allows an infinite number of customizations to take place from here.
IMPORTANT: For compatibility, this version no longer uses self-subclassing, and like earlier versions, once again requires mTDHelper.bas in all projects. (mTDSample.bas is still only for the demo form).
Last edited by fafalone; Jun 15th, 2024 at 05:06 AM.
Reason: Title update
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
(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:
Code:
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:
Code:
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:
Code:
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):
Code:
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:
Code:
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.
Code:
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:
Code:
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.
Code:
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:
Code:
.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.
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.
Code:
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.
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.
Code:
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:
Code:
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):
Code:
'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:
(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):
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.
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.
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
Originally Posted by fafalone
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:
Code:
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).
Originally Posted by fafalone
... 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:
Code:
.Content = "Where else to link to but <a href=""http://www.microsoft.com"">Microsoft.com</a>"
Originally Posted by fafalone
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.
Originally Posted by fafalone
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!
.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.
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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
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
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.
Last edited by fafalone; Jun 25th, 2020 at 06:24 PM.
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
Originally Posted by fafalone
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:
Finally, manually add the resource file to your VB6 project. That's all.
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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?
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.
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
Originally Posted by 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?
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:
Code:
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
Code:
'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
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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.
Last edited by fafalone; Sep 28th, 2014 at 12:23 AM.
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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
Last edited by LaVolpe; Sep 28th, 2014 at 08:45 AM.
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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
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.
Code:
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
Code:
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:
Code:
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
Last edited by LaVolpe; Sep 28th, 2014 at 10:58 AM.
Insomnia is just a byproduct of, "It can't be done"
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.
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.
Originally Posted by LaVolpe
I have a routine that will
1) Enumerate the desktop windows
. . .
Here's another approach that doesn't require a callback procedure.
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
Originally Posted by Bonnie West
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
Last edited by LaVolpe; Sep 28th, 2014 at 12:14 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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.
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
Bug report:
Code:
Public Property Let Width(Value As Long): uTDC.cxWidth = Width: End Property
Should be:
Code:
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 dialogunits, not pixels, twips or anything else.
Originally Posted by fafalone
(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.
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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?
Last edited by fafalone; Oct 9th, 2014 at 08:05 PM.
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
Originally Posted by fafalone
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.
On Local Error Resume Next: If Not Empty Is Nothing Then Do While Null: ReDim i(True To False) As Currency: Loop: Else Debug.Assert CCur(CLng(CInt(CBool(False Imp True Xor False Eqv True)))): Stop: On Local Error GoTo 0
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
Originally Posted by Bonnie West
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
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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?
Code:
' 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
Originally Posted by msdn
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:
Code:
dialogClientWidth = (desiredWidth * 4&) \ (GetDialogBaseUnits() And &HFFFF&)
In my tests, results of both sample routines returned the same calculated/converted values
Last edited by LaVolpe; Oct 11th, 2014 at 02:44 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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?
Last edited by LaVolpe; Oct 13th, 2014 at 05:34 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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.
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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
Code:
' 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
Code:
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:
Code:
...
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
...
Last edited by LaVolpe; Oct 14th, 2014 at 09:59 PM.
Reason: optimized/tweaked code
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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?
Code:
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
Last edited by fafalone; Oct 15th, 2014 at 07:42 AM.
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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
Last edited by LaVolpe; Oct 15th, 2014 at 02:51 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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.
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
Originally Posted by fafalone
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
Code:
' 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
Code:
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)
Last edited by LaVolpe; Oct 21st, 2014 at 07:47 AM.
Reason: reformatted code to use UDT
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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.
Last edited by fafalone; Oct 20th, 2014 at 10:59 PM.
Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs
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")
Last edited by LaVolpe; Oct 21st, 2014 at 10:20 AM.
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs
Ah yes that's the really complicated way
I was using
Code:
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.
Re: [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs
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
Last edited by LaVolpe; Oct 21st, 2014 at 07:41 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs
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...
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs
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.
Last edited by fafalone; Oct 25th, 2014 at 12:36 PM.
Re: [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs
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
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
Last edited by LaVolpe; Oct 25th, 2014 at 12:56 PM.
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs
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
Last edited by LaVolpe; Oct 27th, 2014 at 12:28 AM.
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs
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.
Last edited by fafalone; Oct 28th, 2014 at 07:20 AM.
Re: [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs
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
Insomnia is just a byproduct of, "It can't be done"
Re: [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs
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).