Results 1 to 13 of 13

Thread: CommonDialog: update file extension automatically when user changes file type

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Feb 2017
    Posts
    2,964

    Post CommonDialog: update file extension automatically when user changes file type

    This is not a question, but a code snippet that I want to share.
    I didn't post it in the codebank because it is not a whole project, but something that can be added to the ShowSave method of a CommonDialog class.

    Code:
        ' code part before the call to GetOpenFileName
        Dim iDialog As T_OPENFILENAME
        
        ' code
        tDialog.lpstrFilter = StrPtr(FilterString)
        tDialog.lCustData = LenB(FilterString)
        ' code
        If (tDialog.Flags And cdlOFNAllowMultiselect) = 0 Then
            If InStr(FilterString, "|") > 0 Then
                tDialog.Flags = tDialog.Flags Or OFN_ENABLEHOOK Or OFN_ENABLESIZING Or cdlOFNExplorer
                tDialog.lpfnHook = GetAddressofFunction(AddressOf SaveDialogCallback)
            End If
        End If
        ' code
    Code:
    Private Function GetAddressOfFunction(Add As Long) As Long
        GetAddressOfFunction = Add
    End Function
    In a bas module the hook procedure:

    Code:
    Public Function SaveDialogCallback(ByVal hDlg As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
        Const WM_INITDIALOG As Long = &H110
        Const WM_NOTIFY = &H4E
        Const H_MAX As Long = &HFFFF + 1
        Const CDN_FIRST = (H_MAX - 601)
        Const CDN_TYPECHANGE = (CDN_FIRST - &H6)
        Const IDFILENAEDIT = &H480&
        Const CDM_FIRST = (WM_USER + 100)
        Const CDM_SETCONTROLTEXT = (CDM_FIRST + &H4)
        Const CDM_GETCONTROLTEXT = CDM_FIRST
        Static sStructurePtr As Long
        Dim iDialog As T_OPENFILENAME
        Dim iFileName As String
        Dim iBuff As String
        Dim iFilter As String
        Dim iFilterElements() As String
        Dim iHwndDlg As Long
        Dim tNMH As NMHDR
        Dim iPos As Long
        Dim iExt As String
        Dim iPos2 As Long
        
        If wMsg = WM_INITDIALOG Then
            sStructurePtr = lParam
        ElseIf wMsg = WM_NOTIFY Then
            CopyMemory tNMH, ByVal lParam, Len(tNMH)
            Select Case tNMH.code
                Case CDN_TYPECHANGE
                    CopyMemory iDialog, ByVal sStructurePtr, Len(iDialog)
                    iHwndDlg = GetParent(hDlg)
                    iBuff = Space$(255)
                    SendMessage iHwndDlg, CDM_GETCONTROLTEXT, IDFILENAEDIT, ByVal StrPtr(iBuff)
                    iPos = InStr(iBuff, Chr(0))
                    If iPos > 1 Then
                        iFileName = Left$(iBuff, iPos - 1)
                    Else
                        iFileName = iBuff
                    End If
                    iPos = InStr(iFileName, ".")
                    If iPos > 0 Then
                        iFilter = Space$(iDialog.lCustData)
                        CopyMemory ByVal StrPtr(iFilter), ByVal iDialog.lpstrFilter, iDialog.lCustData
                        iFilterElements = Split(Left$(iFilter, Len(iFilter) - 2), Chr(0))
                        iExt = iFilterElements((iDialog.nFilterIndex - 1) * 2 + 1)
                        iPos2 = InStrRev(iExt, ".")
                        If iPos2 > 0 Then
                            iExt = Mid$(iExt, iPos2 + 1)
                        End If
                        iFileName = Left$(iFileName, iPos - 1) & "." & iExt
                        SendMessage iHwndDlg, CDM_SETCONTROLTEXT, IDFILENAEDIT, ByVal StrPtr(iFileName)
                    End If
            End Select
        End If
    End Function
    I'm using lCustData to carry the len of the filter string, but if you are already using that for something else, you could use any other method, including a global variable or SetProp.
    The dialog is intended to work modally, only one dialog at the time. Anyway the code can be changed to handle several instances if needed. The main point here is to show what messages to use.

  2. #2
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,667

    Re: CommonDialog: update file extension automatically when user changes file type

    Nice work.

    Can't resist adding, if you're using IFileDialog, you can accomplish this as follows:

    1) Your file types structure should be module level where the dialog is called from,
    2) Set up a public method to return the type based on index,
    Code:
    Option Explicit
    Private tFileSpec As COMDLG_FILTERSPEC
    
    Public Function GetTypeForFS(idx As Long) As String
    GetTypeForFS = tFileSpec(idx).pszSpec
    End Function
    3) Then you set up your normal dialog with the IFileDialogEvents class, which contains IFileDialogEvents_OnTypeChange, then you can do it with this code:
    Code:
    Dim sFN As String
    Dim lp As Long
    Dim fti As Long
    Dim sSpec As String
    pdf.GetFileName lp
    sFN = LPWSTRtoStr(lp)
    If sFN <> "" Then
        If InStr(sFN, ".") Then
            sFN = Left$(sFN, Len(sFN) - InStrRev(sFN, ".") + 1)
        End If
        pdf.GetFileTypeIndex fti
        sSpec = Form1.GetTypeForFS(fti - 1)
        If InStr(sSpec, ";") Then
            sSpec = Left$(sSpec, InStr(sSpec, ";") - 1)
        End If
        sFN = Replace$(sSpec, "*", sFN)
        pdf.SetFileName sFN
    End If
    Note to substitute Form1 for whichever form you're calling from.

    (Sample for calling the dialog:
    Code:
        Dim PDLG As FileSaveDialog
        Dim pcb2 As cFileDialogEvents
        Set PDLG = New FileSaveDialog
        Set pcb2 = New cFileDialogEvents
        Dim lck As Long
        ReDim tFileSpec(2)
        tFileSpec(0).pszName = "PNG"
        tFileSpec(0).pszSpec = "*.png"
        tFileSpec(1).pszName = "GIF"
        tFileSpec(1).pszSpec = "*.gif"
        tFileSpec(2).pszName = "BMP"
        tFileSpec(2).pszSpec = "*.bmp"
        
        With PDLG
            .Advise pcb2, lck
            .SetTitle "Extension Auto-Change Test"
            .SetFileTypes 3&, VarPtr(tFileSpec(0).pszName)
            .Show Me.hWnd
            .UnAdvise lck
        End With
        Set pcb2 = Nothing
        Set PDLG = Nothing

  3. #3

    Thread Starter
    PowerPoster
    Join Date
    Feb 2017
    Posts
    2,964

    Re: CommonDialog: update file extension automatically when user changes file type

    Hello fafalone. Thanks for sharing it.
    I never used IFileDialog to date, but I think that could be the next move, if I ever do another modernizing.

    BTW: I'm not very familiar with the advantages of the IFileDialog, other than cosmetic is there any for example for a normal dialog that is used for saving Excel and PDF files?

  4. #4
    Hyperactive Member
    Join Date
    Aug 2016
    Posts
    497

    Re: CommonDialog: update file extension automatically when user changes file type

    Quote Originally Posted by Eduardo- View Post
    This is not a question, but a code snippet that I want to share.
    I didn't post it in the codebank because it is not a whole project, but something that can be added to the ShowSave method of a CommonDialog class.

    Code:
        ' code part before the call to GetOpenFileName
        Dim iDialog As T_OPENFILENAME
        
        ' code
        tDialog.lpstrFilter = StrPtr(FilterString)
        tDialog.lCustData = LenB(FilterString)
        ' code
        If (tDialog.Flags And cdlOFNAllowMultiselect) = 0 Then
            If InStr(FilterString, "|") > 0 Then
                tDialog.Flags = tDialog.Flags Or OFN_ENABLEHOOK Or OFN_ENABLESIZING Or cdlOFNExplorer
                tDialog.lpfnHook = GetAddressofFunction(AddressOf SaveDialogCallback)
            End If
        End If
        ' code
    Code:
    Private Function GetAddressOfFunction(Add As Long) As Long
        GetAddressOfFunction = Add
    End Function
    In a bas module the hook procedure:

    Code:
    Public Function SaveDialogCallback(ByVal hDlg As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
        Const WM_INITDIALOG As Long = &H110
        Const WM_NOTIFY = &H4E
        Const H_MAX As Long = &HFFFF + 1
        Const CDN_FIRST = (H_MAX - 601)
        Const CDN_TYPECHANGE = (CDN_FIRST - &H6)
        Const IDFILENAEDIT = &H480&
        Const CDM_FIRST = (WM_USER + 100)
        Const CDM_SETCONTROLTEXT = (CDM_FIRST + &H4)
        Const CDM_GETCONTROLTEXT = CDM_FIRST
        Static sStructurePtr As Long
        Dim iDialog As T_OPENFILENAME
        Dim iFileName As String
        Dim iBuff As String
        Dim iFilter As String
        Dim iFilterElements() As String
        Dim iHwndDlg As Long
        Dim tNMH As NMHDR
        Dim iPos As Long
        Dim iExt As String
        Dim iPos2 As Long
        
        If wMsg = WM_INITDIALOG Then
            sStructurePtr = lParam
        ElseIf wMsg = WM_NOTIFY Then
            CopyMemory tNMH, ByVal lParam, Len(tNMH)
            Select Case tNMH.code
                Case CDN_TYPECHANGE
                    CopyMemory iDialog, ByVal sStructurePtr, Len(iDialog)
                    iHwndDlg = GetParent(hDlg)
                    iBuff = Space$(255)
                    SendMessage iHwndDlg, CDM_GETCONTROLTEXT, IDFILENAEDIT, ByVal StrPtr(iBuff)
                    iPos = InStr(iBuff, Chr(0))
                    If iPos > 1 Then
                        iFileName = Left$(iBuff, iPos - 1)
                    Else
                        iFileName = iBuff
                    End If
                    iPos = InStr(iFileName, ".")
                    If iPos > 0 Then
                        iFilter = Space$(iDialog.lCustData)
                        CopyMemory ByVal StrPtr(iFilter), ByVal iDialog.lpstrFilter, iDialog.lCustData
                        iFilterElements = Split(Left$(iFilter, Len(iFilter) - 2), Chr(0))
                        iExt = iFilterElements((iDialog.nFilterIndex - 1) * 2 + 1)
                        iPos2 = InStrRev(iExt, ".")
                        If iPos2 > 0 Then
                            iExt = Mid$(iExt, iPos2 + 1)
                        End If
                        iFileName = Left$(iFileName, iPos - 1) & "." & iExt
                        SendMessage iHwndDlg, CDM_SETCONTROLTEXT, IDFILENAEDIT, ByVal StrPtr(iFileName)
                    End If
            End Select
        End If
    End Function
    I'm using lCustData to carry the len of the filter string, but if you are already using that for something else, you could use any other method, including a global variable or SetProp.
    The dialog is intended to work modally, only one dialog at the time. Anyway the code can be changed to handle several instances if needed. The main point here is to show what messages to use.
    If you have time, can you provide the complete code? It looks interesting.

  5. #5

    Thread Starter
    PowerPoster
    Join Date
    Feb 2017
    Posts
    2,964

    Re: CommonDialog: update file extension automatically when user changes file type

    Quote Originally Posted by xxdoc123 View Post
    If you have time, can you provide the complete code? It looks interesting.
    There are several CommonDialog classes out there that can be more complete than the one I'm using. Most remarkable one may be the one in Krool controls. May be Krool is interested on adding this feature to his. If not, I could take it and post an updated version with the changes here.

  6. #6
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Posts
    2,700

    Re: CommonDialog: update file extension automatically when user changes file type

    Btw, using OFN_ENABLEHOOK used to render old-style OFN dialog so I don't use if on Vista and newer. IFileDialog is the way to go for receiving notifications with new OFN style.

    Btw, a caveat I see in my sources concerns this

    CopyMemory iDialog, ByVal sStructurePtr, Len(iDialog)

    The point is that OPENFILENAME struct is *shorter* before Win2000 -- there is no FlagsEx member and two reserved dwords before it -- 12 bytes less.

    cheers,
    </wqw>

  7. #7

    Thread Starter
    PowerPoster
    Join Date
    Feb 2017
    Posts
    2,964

    Re: CommonDialog: update file extension automatically when user changes file type

    Quote Originally Posted by wqweto View Post
    Btw, using OFN_ENABLEHOOK used to render old-style OFN dialog
    Not if you add the cdlOFNExplorer flag too.

    Quote Originally Posted by wqweto View Post
    so I don't use if on Vista and newer. IFileDialog is the way to go for receiving notifications with new OFN style.

    Btw, a caveat I see in my sources concerns this

    CopyMemory iDialog, ByVal sStructurePtr, Len(iDialog)

    The point is that OPENFILENAME struct is *shorter* before Win2000 -- there is no FlagsEx member and two reserved dwords before it -- 12 bytes less.

    cheers,
    </wqw>
    Hum, anyway I'm not supporting Windows 98/Me anymore.

  8. #8
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Posts
    2,700

    Re: CommonDialog: update file extension automatically when user changes file type

    Quote Originally Posted by Eduardo- View Post
    Not if you add the cdlOFNExplorer flag too.
    Is this OFN_EXPLORER flag?

    No, it doesn't help. There is a huge difference between OS provided open file dialog with OFN_ENABLEHOOK and without it on Windows 10 and everything since Vista.

    Quote Originally Posted by Eduardo- View Post
    Hum, anyway I'm not supporting Windows 98/Me anymore.
    Sure, no one does. I am still testing on NT 4.0 for no apparent reason.

    cheers,
    </wqw>

  9. #9

    Thread Starter
    PowerPoster
    Join Date
    Feb 2017
    Posts
    2,964

    Re: CommonDialog: update file extension automatically when user changes file type

    Quote Originally Posted by wqweto View Post
    Is this OFN_EXPLORER flag?
    Must be.

    Quote Originally Posted by wqweto View Post
    No, it doesn't help. There is a huge difference between OS provided open file dialog with OFN_ENABLEHOOK and without it on Windows 10 and everything since Vista.
    </wqw>
    Well, test it.

  10. #10
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Posts
    2,700

    Re: CommonDialog: update file extension automatically when user changes file type

    Quote Originally Posted by Eduardo- View Post
    Well, test it.
    I did before previous post.

    Do you want me to post some screenshots? (I would capture output from your code with and without the flag if possible.)

    Edit: Here it is "native" OFN on Win10



    This is with OFN_ENABLEHOOK flag set



    cheers,
    </wqw>

  11. #11

    Thread Starter
    PowerPoster
    Join Date
    Feb 2017
    Posts
    2,964

    Re: CommonDialog: update file extension automatically when user changes file type

    I see what you mean. I even didn't notice that difference.

    What I get if I add the OFN_ENABLEHOOK flag but not the OFN_EXPLORER one is this:

    Name:  NoExplorerFlag.png
Views: 31
Size:  10.4 KB

    I thought you were talking about that.

  12. #12
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    2,667

    Re: CommonDialog: update file extension automatically when user changes file type

    Quote Originally Posted by Eduardo- View Post
    Hello fafalone. Thanks for sharing it.
    I never used IFileDialog to date, but I think that could be the next move, if I ever do another modernizing.

    BTW: I'm not very familiar with the advantages of the IFileDialog, other than cosmetic is there any for example for a normal dialog that is used for saving Excel and PDF files?
    The main advantage is if you wanted to add customizations to the dialog; it's much easier to add additional controls with IFileDialog.

    Also really helpful the event callback class makes handling events a lot easier than all the callbacks/API and subclassing required to do the same with the API dialogs.

    If you're doing something that heavily interfaces with the shell, you might want to be handling files by working with ITEMIDLISTs and IShellItem interfaces, which IFD handles naturally.

    If you're loading from portable devices, depending on what you're passing the returned path to, there be trouble resolving the bizarre paths it returns. It might require manual correction or not work at all, while using pidls and shell interfaces that IFileDialog gets you works better.


    So for the simplest uses, not much advantage, but the more complex stuff you're doing with the dialog, the more you'll want to look at IFileDialog instead.

  13. #13

    Thread Starter
    PowerPoster
    Join Date
    Feb 2017
    Posts
    2,964

    Re: CommonDialog: update file extension automatically when user changes file type

    In my specific current case, the user only needs to save files, with two options for file type (in the previous version of this program there was only one, so there was no trouble).
    I thought it wasn't nice to leave the wrong extension when she changed type.... sometimes simple details like this one take lot of work on MS technology.

    I didn't find the right information about how to do this on internet, so I had to research by testing. Then I decided to share it so someone else do not have to do the same.

    But I didn't realize of that "subtle" change in the dialog.

    Next stop is IFileDialog... but I don't feel like to do it now (I'm still working on other enhancements on the program).

Posting Permissions

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



Click Here to Expand Forum to Full Width