-
Jan 26th, 2021, 05:06 PM
#1
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.
-
Jan 26th, 2021, 06:49 PM
#2
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
-
Jan 26th, 2021, 07:49 PM
#3
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?
-
Jan 26th, 2021, 08:13 PM
#4
Hyperactive Member
Re: CommonDialog: update file extension automatically when user changes file type
 Originally Posted by Eduardo-
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.
-
Jan 26th, 2021, 08:41 PM
#5
Re: CommonDialog: update file extension automatically when user changes file type
 Originally Posted by xxdoc123
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.
-
Jan 27th, 2021, 02:07 AM
#6
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>
-
Jan 27th, 2021, 03:06 AM
#7
Re: CommonDialog: update file extension automatically when user changes file type
 Originally Posted by wqweto
Btw, using OFN_ENABLEHOOK used to render old-style OFN dialog
Not if you add the cdlOFNExplorer flag too.
 Originally Posted by wqweto
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.
-
Jan 27th, 2021, 03:21 AM
#8
Re: CommonDialog: update file extension automatically when user changes file type
 Originally Posted by Eduardo-
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.
 Originally Posted by Eduardo-
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>
-
Jan 27th, 2021, 04:17 AM
#9
Re: CommonDialog: update file extension automatically when user changes file type
 Originally Posted by wqweto
Is this OFN_EXPLORER flag?
Must be.
 Originally Posted by wqweto
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.
-
Jan 27th, 2021, 04:38 AM
#10
Re: CommonDialog: update file extension automatically when user changes file type
 Originally Posted by Eduardo-
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>
Last edited by wqweto; Jan 27th, 2021 at 04:46 AM.
-
Jan 27th, 2021, 04:55 AM
#11
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:

I thought you were talking about that.
-
Jan 27th, 2021, 05:08 AM
#12
Re: CommonDialog: update file extension automatically when user changes file type
 Originally Posted by Eduardo-
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.
-
Jan 27th, 2021, 05:19 AM
#13
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|