dcsimg
Page 1 of 4 1234 LastLast
Results 1 to 40 of 129

Thread: [VB6] TaskDialogIndirect - Complete class implementation of Vista+ Task Dialogs

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    [VB6] TaskDialogIndirect - Complete Task Dialog wrapper and extensive enhancements

    cTaskDialog 1.1
    Say goodbye to unsightly, primitive messageboxes forever with the new enhanced TaskDialog.
    Updated released 30 August 2019


    Download Attachment: cTaskDialog11.zip

    cTaskDialog is the sequel to my previous TaskDialogIndirect project, mTaskDialog. This version adds support for all TaskDialogIndirect features, including the progress bar, timer feedback, updating the text and icons while the dialog is open, and events for all the notifications. It's also much easier to use. VB6 can use many modern windows features, and the TaskDialog is one of the most useful

    **Recently Added Features**
    Project Update - 30 August 2019
    Class is now self-contained, no longer requires a separate .bas file (though the sample still has one for resources used by the sample project-- but it's not for redistribution).
    Several bug fixes including missing flag and flag conflicts.
    Left-right-center alignment for controls.

    Major Update - 01 March 2017
    Logo images, dropdown buttons, autoclose, DPI sensitivity, and plenty of improvements have brought cTaskDialog to its 1.0 release.
    See this post for more details, more pictures, and sample code!

    The prior months cTaskDialog 0.8 added the large number of non-built in controls you're seeing, if you're not familiar with those already, you'll also want to check out the release post for cTaskDialog 0.8.
    What is TaskDialog?

    TaskDialog, introduced in Windows Vista, is a massive upgrade to the MessageBox. While not as simple, it offers a huge array of features... custom icons, custom button text, command link style buttons, expanded info button, a footer, a checkbox for 'i read this' or 'don't show this again' type messages, radio buttons, hyperlinks, and more.

    This project can be used to create a simple messagebox like the older ones, to an extremely complex dialog with all the features mentioned, even all at once!

    Before using cTaskDialog

    The TaskDialog was introduced with version 6.0 of the common controls, with Windows Vista. That means a manifest is required for your compiled application, and for VB6.exe in order to use it from the IDE. In addition, your project must start from Sub Main() and initialize the common controls before any forms are loaded. The sample project includes Sub Main(), and see LaVolpe's Manifest Creator to create the manifests.

    Setting Up cTaskDialog

    Your project must include cTaskDialog.cls. mTaskDialogHelper.bas contains 3 required lines- you can include that module, or place the following 3 lines in an existing .bas module:
    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: TaskDialogCallbackProc = lpRefData.ProcessCallback(hwnd, uNotification, wParam, lParam): End Function
    Public Function TaskDialogEnumChildProc(ByVal hwnd As Long, ByVal lParam As cTaskDialog) As Long: TaskDialogEnumChildProc = lParam.ProcessEnumCallback(hwnd): End Function
    Public Function TaskDialogSubclassProc(ByVal hwnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long, ByVal uIdSubclass As Long, ByVal dwRefData As cTaskDialog) As Long: TaskDialogSubclassProc = dwRefData.ProcessSubclass(hwnd, uMsg, wParam, lParam, uIdSubclass): End Function
    mTaskDialogSample.bas, mIcon, and the form are only required 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.
    Code:
    hIcon1 = ResIconToHICON("ICO_CLOCK", 16, 16)
        .Flags = TDF_USE_SHELL32_ICONID_BUTTON Or TDF_USE_COMMAND_LINKS
        .CommonButtons = TDCBF_CLOSE_BUTTON Or TDCBF_NO_BUTTON
        .AddCustomButton 103, "Button 1"
        .AddCustomButton 102, "Button 2", hIcon2
        .SetWindowsButtonIconSize ICO_32
        .SetCommonButtonIcon TDCBF_NO_BUTTON, hIcon1


    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.


    (continued in next post)
    Download Attachment: cTaskDialog11.zip
    Last edited by fafalone; Aug 30th, 2019 at 04:03 PM. Reason: New Version!

  2. #2

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.
    Code:
        .Init
        .MainInstruction = "Hello World"
        .Content = "Pick a day, any day" & vbCrLf & vbCrLf
        .Flags = TDF_DATETIME
        .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
        .IconMain = TD_INFORMATION_ICON
        .Title = "cTaskDialog Project"
        .ParenthWnd = Me.hWnd
        .ShowDialog
    
        Label11.Caption = .ResultDateTime

    Many common options are supported. This example shows combining the new controls with the built-in ones, and setting a limited range in the date and time controls. When the two controls are used, both the chosen date and time are represented by .ResultDateTime.
    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.
    Code:
        .Init
        .MainInstruction = "Sliding on down"
        .Content = "Pick a number"
        .Flags = TDF_SLIDER Or TDF_USE_COMMAND_LINKS
        .SliderSetRange 0, 100, 10
        .SliderSetChangeValues 10, 20
        .SliderTickStyle = SldTickStyleBoth
        .SliderValue = 50
        .SliderAlign = TDIBA_Content
        .ExpandedControlText = "ExpandMe"
        .ExpandedInfo = "Expanded"
        .AddCustomButton 100, "CommandLink"
        .CommonButtons = TDCBF_OK_BUTTON Or TDCBF_CANCEL_BUTTON
        .IconMain = TD_INFORMATION_ICON

    Finally, here's an example of using 3 of the new controls together in combination with two of the built-in controls, as well as the option to replace the shield icon with one from shell32.dll, while maintaining the gradient background.
    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:
    Code:
        .SetLogoImage hBmp, LogoBitmap, LogoTopRight, 4, 4
    (as suggested by 'LogoBitmap', there's also 'LogoIcon' that you need to use for .ico's)

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

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

    See post #4 for events, properties, and methods

    Thanks

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


    Download Attachment: cTaskDialog11.zip

    ---
    Last updated 3/1/2017

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

    All bug reports, comments, criticisms, and ideas on how to improve the project are absolutely welcome!
    Last edited by fafalone; Aug 30th, 2019 at 04:03 PM. Reason: Updated documentation

  3. #3
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,057

    Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs

    Quote Originally Posted by fafalone View Post
    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).

    Quote Originally Posted by fafalone View Post
    ... 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>"
    Quote Originally Posted by fafalone View Post
    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.

    Quote Originally Posted by fafalone View Post
    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!

    Quote Originally Posted by MSDN
    You should call DestroyIcon for icons created with CreateIconFromResourceEx.
    Quote Originally Posted by fafalone View Post
    .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
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  4. #4

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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 [td]Width of the dropdown list.
    [tr][td].ComboHeight
    Get/Let The height of the dropdown list of the combo control. Default is 115px (DPI scaled)
    .ComboImageList Get/Let Associates the combo control with an HIMAGELIST (API/COM imagelists)
    .ComboIndex Get/Let, Modify if open The currently selected combo item
    .ComboOffsetX Get/Let Manual adjustment for position offset.
    .ComboStyle Get/Let Normal dropdown (editable) or dropdown list
    .ComboText Get The current text of the combo box.
    .ComboWidth Get/Let Override the automatic width value
    .CommonButtons Get/Let For the standard OK, Cancel, Yes, No, etc buttons
    .Content Get/Let, Modify if open The primary message text
    .DateTimeAlign Get/Let Date/Time control position (content, buttons, footer)
    .DateTimeAlignInButtons Get/Let If in content area, left-right-center alignment
    .DateTimeAlignInContent Get/Let If in content area, left-right-center alignment
    .DateTimeAlignInFooter Get/Let If in footer area, left-right-center alignment
    .DateTimeChecked Get/Let, Modify if open A value* representing the current check state; can be used to set default state
    .DateTimeOffsetX Get/Let Manual adjustment for position offset.
    .DateTimeType Get/Let Date, Time, or Date+Time with checkbox options
    .DateTimeValue Get/Let, Modify if open The current date and/or time, as a Date type
    .DefaultButton Get/Let Specifies the default button by ID; can be either a common button or custom button ID
    .DefaultCustomControl Get/Let If multiple custom controls are present, override the automatic calculation of which one gets focus
    .DefaultRadioButton Get/Let The ID of the default radio button
    .DPIScaleX Get/Let The current X scaling factor; will be 1 for 100% DPI
    .DPIScaleY Get/Let The current Y scaling factor; will be 1 for 100% DPI
    .ExpandedControlText Get/Let The text next to the round expando button when expanded
    .ExpandedInfo Get/Let, Modify if open The extra information shown/hidden by the expando control
    .Flags Get/Let The TDF_x control flags
    .Footer Get/Let, Modify if open The text appearing in the footer area
    .hInst Get/Let hInstance; usually set by the class automatically, but if you want to change it to another app/dll, you can then specify an icon from that file. Do not set any icon related flags if you do this. The shell32/imageres feature handles this on its own.
    .hWndCombo Get The handle of the combo control if present
    .hWndComboEdit Get The handle of the combo control's textbox if present
    .hWndDateTime Get The handle of the date/time control if present
    .hWndDlg Get The main handle of the dialog
    .hWndDUI Get The handle of the DirectUIHWND class that holds the controls; immediate parent of custom controls
    .hWndInput Get The handle of the input textbox if present
    .hWndSlider Get The handle of the slider control if present
    .IconFooter Get/Let, Modify if open The ID or HICON for the footer icon
    .IconMain Get/Let, Modify if open The ID or HICON for the main icon
    .IconReplaceGradient Get/Let, Modify if open If TDF_KILL_SHIELD_ICON is present, you can optionally replace it instead of leaving it gone
    .InputAlign Get/Let The position of the input textbox, content-buttons-footer
    .InputAlignInFooter Get/Let If in the footer, left-right-center alignment
    .InputCueBanner Get/Let, Modify if open The cue banner text of the input textbox
    .InputIsPassword Get/Let, Modify if open Set/unset the password style that hides the text behind asterisks/dots
    .InputOffsetX Get/Let Manual adjustment for position offset.
    .InputText Get/Let, Modify if open The current text of the input textbox; can be used to set default text
    .InputWidth Get/Let Override the automatic width value of the input textbox
    .MainInstruction Get/Let, Modify if open The larger header text above the main message text
    .ParenthWnd Get/Let Not mandatory, but if set the dialog will appear modally as a child of the given for (like Form.Show vbModal, Me)
    .ResultComboIndex Get The final item selected when the dialog closes
    .ResultDateTime Get The final date and/or time when the dialog closes
    .ResultDateTimeChecked Get The final check status of date/time control(s) when the dialog closes
    .ResultInput Get The final text of the input textbox when the dialog closes
    .ResultMain Get The ID of the button that closed the dialog. TDCANCEL is also the result for the 'X' button closing the dialog or the Autoclose timeout being reached
    .ResultRad Get The ID of the radio button selected when the dialog closes
    .ResultVerify Get The status of the verification checkbox when the dialog closes
    .SliderAlign Get/Let The position of the slider control, content-buttons-footer
    .SliderAlignInFooter Get/Let If the slider is in the footer, left-right-center alignment
    .SliderOffsetX Get/Let Manual adjustment for position offset.
    .SliderTickStyle Get/Let Set if ticks appear on the top, bottom, or both
    .SliderValue Get/Let, Modify if open The current value of the slider; can be used to set the default value
    .SliderWidth Get/Let Override the automatic width value of the slider control
    .Title Get/Let Main dialog title appearing in the control bar
    .VerifyText Get/Let Text appearing next to the Verify checkbox
    .Width Get/Let You can manually specify the width of the TaskDialog in Dialog Units (which are not pixels or twips). If not set, Windows automatically calculates an optimal value.

    Methods

    Method Effects open dlg? Description
    .AddCustomButton(id, text [,hIcon]) No Adds a custom button. These appear as command links if enabled, otherwise as standard buttons
    .AddRadioButton(id, text) No Adds a radio button
    .ClearCustomButtons No Clears the current set of custom buttons
    .ClearRadioButtons No Clears the current set of radio buttons
    .ClickButton(id)
    .ClickRadioButton(id)
    Yes Click a button by id
    .ClickVerification(state[,focused]) Yes 0/1 for checked, 1 to set keyboard focus on the checkbox
    .CloseDialog Yes Closes an open dialog and returns TD_CLOSE as the result, even if that button is not present.
    .ComboAddItem(text[,image][,overlay]) Yes Adds an item to the combo control, if present
    .ComboSetCurrentState(text[,image][,overlay]) Yes Sets the text/image of the edit box of a combo control
    .ComboSetInitialItem(idx) No Sets the default combo item
    .ComboSetInitialState(text[,image][,overlay) No The default text/image of the combo edit box
    .DateTimeSetInitial(date) No The default for the date and/or time control
    .DateTimeSetRange(setmin,setmax,min,max) Yes Limit the dates/times allowed
    .EnableButton(id, state)
    .EnableRadioButton(id, state)
    Yes Enable/disable a button by id (0/1)
    .GetCustomButtons(ids(),strs()) No Get an array of the current custom buttons
    .GetRadioButtons(ids(),strs()) No Get an array of the current radio buttons
    .Init No Resets all config settings to their default state
    .NavigatePage(cTaskDialog) Yes Mutli-page navigation; opens a new taskdialog as a page of the current one
    .ProcessCallback
    .ProcessEnumCallback
    .ProcessSubclass
    These are public but for internal use; DO NOT CALL
    .ProgressSetRange(min,max) Yes Sets the range of the progress bar
    .ProgressSetState(state) Yes Progress state; green/normal, yellow/pause, red/error
    .ProgressSetType(type) Yes Sets regular or marquee type progress bar
    .ProgressSetValue(value) Yes Sets the current progress value
    .ProgressStartMarquee(speed)
    .ProgressStopMarquee
    Yes Start/stop a marquee-style progress bar
    .ResetTimer Yes Resets the value sent by the Timer event.
    .SetButtonElevated(buttonid,state) Yes Adds the security shield icon to a button to indicate elevated user permissions are required to perform that action. 1 to turn on, 0 to turn off the icon.
    .SetButtonHold(buttonid)
    .ReleaseButtonHold
    Yes Places a hold on a button: the dialog will not close when the button is clicked. The click event is still sent.
    .SetCommonButtonIcon(tdbutton,hicon) No Add an HICON-based icon to a common button
    .SetLogoImage(handle,type,pos[,offX][,offY]) No Add a logo image in the type right or by the buttons
    .SetSplitButton(id) No Add a dropdown arrow to a custom button
    .SetWindowsButtonIconSize(size) No Set the size of button icons loaded from shell32 or imageres.
    .ShowDialog n/a The main command to start the dialog, called after all initial setup
    .SimpleDialog(text[,btns][,title][,maintxt][,icon][,hwnd][,hinst]) n/a Shows a rudimentry task dialog with the plain TaskDialog() API
    .SliderSetChangeValues(small,large) Yes The value of a small and large change in the slider control
    .SliderSetRange(min,max[,tickfqr]) Yes Range and tick frequency for the slider control
    * - Date/Time checks values are as follows: If there only a single control, or both but only one has a checkbox, it's just 0/1 for unchecked/checked. For 2 controls both with checkboxes, 4=both are checked, 3=date checked, time unchecked, 2=date unchecked, time checked, 0=neither are checked

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

    Thanks for the tips, I'm incorporating them in now.
    Last edited by fafalone; Aug 30th, 2019 at 05:03 PM.

  5. #5
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,057

    Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs

    Quote Originally Posted by fafalone View Post
    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:

    Code:
    // td.rc
    
    #define ICO_CLOCK 101
    #define ICO_HEART 102
    
    ICO_CLOCK ICON Clock.ico
    ICO_HEART ICON Heart.ico
    
    #define MANIFEST_RESOURCE_ID  1
    #define RT_MANIFEST          24
    
    MANIFEST_RESOURCE_ID RT_MANIFEST taskdialogindirect.exe.manifest
    Then compile it via the command line or with a batch file, such as this:

    Code:
    :: CompileRC.cmd
    @CD /D %~dp0
    :: "%ProgramFiles%\Microsoft Visual Studio\VB98\Wizards\RC.EXE" td.rc
    "%ProgramFiles%\Microsoft SDKs\Windows\v7.0\Bin\RC.Exe" /nologo td.rc
    @PAUSE > NUL
    Finally, manually add the resource file to your VB6 project. That's all.
    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
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  6. #6

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  7. #7
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,057

    Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs

    Quote Originally Posted by fafalone View Post
    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
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  8. #8
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  9. #9

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  10. #10
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  11. #11
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  12. #12
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,057

    Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs

    Quote Originally Posted by LaVolpe View Post
    Unlike the msgbox, the TaskDialog is NOT application-modal
    Indeed. The MessageBox function has an MB_TASKMODAL flag whereas TaskDialogIndirect has no such equivalent.

    Quote Originally Posted by MSDN
    MB_TASKMODAL
    0x00002000L
    Same as MB_APPLMODAL except that all the top-level windows belonging to the current thread are disabled if the hWnd parameter is NULL. Use this flag when the calling application or library does not have a window handle available but still needs to prevent input to other windows in the calling thread without suspending other threads.

    Message Boxes

    A message box is a modal dialog box and the system creates it by using the same internal functions that DialogBox uses. If the application specifies an owner window when calling MessageBox or MessageBoxEx, the system disables the owner. An application can also direct the system to disable all top-level windows belonging to the current thread by specifying the MB_TASKMODAL value when creating the dialog box.
    Quote Originally Posted by LaVolpe View Post
    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
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  13. #13
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs

    Quote Originally Posted by Bonnie West View Post
    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  14. #14

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  15. #15
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,057

    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 dialog units, not pixels, twips or anything else.


    Quote Originally Posted by fafalone View Post
    (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
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  16. #16

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  17. #17
    Default Member Bonnie West's Avatar
    Join Date
    Jun 2012
    Location
    InIDE
    Posts
    4,057

    Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs

    Quote Originally Posted by fafalone View Post
    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
    Declare Sub CrashVB Lib "msvbvm60" (Optional DontPassMe As Any)

  18. #18
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs

    Quote Originally Posted by Bonnie West View Post
    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  19. #19
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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
    Quote 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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  20. #20

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs

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

  21. #21
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  22. #22

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  23. #23
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs

    Much better solution -- seems to work just fine
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  24. #24
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  25. #25

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  26. #26
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  27. #27

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  28. #28
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  29. #29
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    Re: [VB6] TaskDialogIndirect: Complete class implementation of Vista+ Task Dialogs

    Quote Originally Posted by fafalone View Post
    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  30. #30

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  31. #31
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  32. #32

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  33. #33
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  34. #34
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  35. #35

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  36. #36
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  37. #37
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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
    Attached Images Attached Images  
    Last edited by LaVolpe; Oct 27th, 2014 at 12:28 AM.
    Insomnia is just a byproduct of, "It can't be done"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  38. #38

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

    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.

  39. #39
    VB-aholic & Lovin' It LaVolpe's Avatar
    Join Date
    Oct 2007
    Location
    Beside Waldo
    Posts
    18,271

    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"

    Classics Enthusiast? Here's my 1969 Mustang Mach I Fastback. Her sister '67 Coupe has been adopted

    Newbie? Novice? Bored? Spend a few minutes browsing the FAQ section of the forum.
    Read the HitchHiker's Guide to Getting Help on the Forums.
    Here is the list of TAGs you can use to format your posts
    Here are VB6 Help Files online


    {Alpha Image Control} {Memory Leak FAQ} {Unicode Open/Save Dialog} {Resource Image Viewer/Extractor}
    {VB and DPI Tutorial} {Manifest Creator} {UserControl Button Template} {stdPicture Render Usage}

  40. #40

    Thread Starter
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,289

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

Page 1 of 4 1234 LastLast

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width