How Do I Filter File Types in IFileDialog?

I am trying to create "Open" and "Save" dialog boxes with the "IFileDialog" functions. I would like to add the option to filter items by file extension. However, it seems that ALL documentation related to "IFileDialog", "IFileOpenDialog" and "IFileSaveDialog" has been deleted from the Microsoft servers. I only get "404--Page Not Found".

Is anyone familiar with using these file dialog methods? I could really use some help with this, as the code of these methods is completely foreign to me. I can post the working code that I have, if anyone is interested.
Hello Anachronon!

Haven't talked in awhile! Glad you are still hacking Win32 SDK code!

Funny about the COM (Component Object Model) Open File implementation. I'm a COM guy through and through. Spent the best years of my life working with low level COM. However, I never used any of the COM Open File / Save File functionality. But not to worry! I have an example I wrote years and years ago showing how to do just what you want. Here it is.....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// cl Ex03.cpp /O1 /Os /GS- TCLib.lib kernel32.lib ole32.lib uuid.lib
// 3,584 Bytes TCLib Linkage
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include <windows.h>
#include <Shobjidl.h>
#include "string.h"
#include "stdio.h"

int main()
{
 COMDLG_FILTERSPEC ComDlgFS[3] = {{L"C++ code files", L"*.cpp;*.h;*.rc"},{L"Executable Files", L"*.exe;*.dll"}, {L"All Files",L"*.*"}};
 IFileOpenDialog*  pFileOpen   = NULL;
 IShellItem*       pShellItem  = NULL;
 wchar_t*          ppszName    = NULL;
  
 CoInitialize(NULL);
 CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void**)(&pFileOpen));
 pFileOpen->SetFileTypes(3,ComDlgFS);  
 pFileOpen->SetTitle(L"A Single Selection Dialog");
 pFileOpen->Show(0); 
 pFileOpen->GetResult(&pShellItem);
 pShellItem->GetDisplayName(SIGDN_FILESYSPATH,&ppszName);
 wprintf(L"%s\n",ppszName);                    
 CoTaskMemFree(ppszName);
 pShellItem->Release();
 pFileOpen->Release();    
 CoUninitialize();
 getchar();
 
 return 0;
}



The above is a console program.
Last edited on
I posted the above within about 10 minutes of seeing your post. All I did was test compile/link it. I built it with my own C Runtime, so I'll test it with the standard MS C Runtime. I see two lines that ought to be changed. Just give me a minute....
Yea, all I had to do was add some conditional compilation directives to correctly build with Microsoft's C Runtime....

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// cl Ex03.cpp /O1 /Os /GS- TCLib.lib kernel32.lib ole32.lib uuid.lib
// cl Ex03.cpp /O1 /Os kernel32.lib ole32.lib uuid.lib
//  3,584 Bytes TCLib Linkage       VStudio 2008
// 57,344 Bytes MS LIBCMT Linkage   VStudio 2008
// #define TCLib
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include <windows.h>
#include <Shobjidl.h>
#ifdef TCLib
   #include "string.h"
   #include "stdio.h"
#else
   #include <string.h>
   #include <stdio.h>
#endif

int main()
{
 COMDLG_FILTERSPEC ComDlgFS[3] = {{L"C++ code files", L"*.cpp;*.h;*.rc"},{L"Executable Files", L"*.exe;*.dll"}, {L"All Files",L"*.*"}};
 IFileOpenDialog*  pFileOpen   = NULL;
 IShellItem*       pShellItem  = NULL;
 wchar_t*          ppszName    = NULL;
  
 CoInitialize(NULL);
 CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_IFileOpenDialog, (void**)(&pFileOpen));
 pFileOpen->SetFileTypes(3,ComDlgFS);  
 pFileOpen->SetTitle(L"A Single Selection Dialog");
 pFileOpen->Show(0); 
 pFileOpen->GetResult(&pShellItem);
 pShellItem->GetDisplayName(SIGDN_FILESYSPATH,&ppszName);
 wprintf(L"%s\n",ppszName);                    
 CoTaskMemFree(ppszName);
 pShellItem->Release();
 pFileOpen->Release();    
 CoUninitialize();
 getchar();
 
 return 0;
}
This COM stuff is hard, so don't be discouraged if you find it so. It wasn't that long ago I wrote it - 2017. So if you have any questions - just ask.

Cuz I've been at this stuff so long I always used the older File Open methodologies involving the OPENFILENAME struct and comdlg32.lib. The filter strings appear about the same. So it was just inertha that I never used the newer COM functionality.

By the way - I only tested with an old version of VStudio - VS 2008. I expect it'll work with VStudio 2019 though. Let me know if it doesn't and I'll fix it. Right now I'm using my Win 10 box to stream CNN to my TV, and my wife is watching it. So I had to use my older Win 7 box to retrieve and test this code.
Last edited on
This line is something of an oddity...

 
CoTaskMemFree(ppszName);


Haven't looked at the docs on this since 2017, but I believe they specify that the user has to release the string memory. I'd recommend you check it out.
Maybe IFileDialog::SetFileTypes
It's used something like this:

1
2
3
4
5
6
7
8
9
const COMDLG_FILTERSPEC c_rgSaveTypes[] =
{
    {L"Word Document (*.doc)",       L"*.doc"},
    {L"Web Page (*.htm; *.html)",    L"*.htm;*.html"},
    {L"Text Document (*.txt)",       L"*.txt"},
    {L"All Documents (*.*)",         L"*.*"}
};

hr = pfd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes);

I found this example here:
https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/winui/shell/appplatform/commonfiledialog/CommonFileDialogApp.cpp
Yea, that's it dutch. Same as my...

 
COMDLG_FILTERSPEC ComDlgFS[3] = {{L"C++ code files", L"*.cpp;*.h;*.rc"},{L"Executable Files", L"*.exe;*.dll"}, {L"All Files",L"*.*"}};


...just formatted differently. The way you have it formatted on multiple lines is clearer though. As I said, its the same syntax basically as the setup with the older OPENFILENAME struct and GetOpenFileName(...) function of comdlg32.lib.
Thanks Freddy and Dutch for your help. That definitely answers my question---though I will have to wait until tomorrow, to test it. Too many things to do today. Yes, I have been away for a while, busy with other things, like household repairs and doctor appointments.

I do already have a working "File Open" dialog box. Like you though, I tend to go more with the old-school methods. I use the dialog merely to get the file and path string. I then use that string to open a file with more traditional methods. I will try to post my code, once I get the file-type filter working.

 
CoTaskMemFree(ppszName);


From my best guess, that appears to be a pointer to a pointer. At least, it seems to behave that way. Or, it could be a way of addressing the processor's physical memory directly, rather than the standard Windows "virtual" memory. I can't really say, as this COM stuff is all voodoo to me.

I'll try to check back in a day or two. Right now, I need to run over to a buddy's house, to help with a small electrical problem. Luckily, it appears that the Microsoft documentation is again up and running. They must have been changing servers, as all of the "IFileDialog" pages had been down for days.
Last edited on
Again, thanks for all of the help. I have found where to place the customization functions in my own "File Open" code (which I downloaded from MS documentation, some months back).

I have created a custom function, which returns a string with the file path/name. Here is the function definition:

 
void fGetFileNamePath(LPWSTR, int, COMDLG_FILTERSPEC*);


The first parameter is a pointer to a string buffer that will receive the file path/name. The second parameter is the number of file-type selections that I want in the dialog. The third parameter is a pointer to the Filter Spec structure, with the selections of file types that I wish to display. Here is how my function is implemented:

1
2
3
4
wchar_t  szFilePath[MAX_PATH];       //  file path string buffer
szFilePath[0] = 0;                   //  initialize string buffer
COMDLG_FILTERSPEC imgfiles[2] ={{L"Image Files", L"*.jpg;*.png;*.bmp;*.tif"},{L"All Files",L"*.*"}};
fGetFileNamePath(szFilePath, 2, imgfiles);


"MAX_PATH" is a Windows-defined macro, defining the maximum size of a path string--currently equal to 260. Here is the function itself. I used code that I downloaded from MS tutorials. As has been discussed above, this is definitely voodoo code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void fGetFileNamePath(LPWSTR pszFilePath, int numTypes, COMDLG_FILTERSPEC* cdfs) {

    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

    if (SUCCEEDED(hr))
     {
        IFileOpenDialog* pFileOpen=NULL;

        // Create the FileOpenDialog object.
        hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));

        if (SUCCEEDED(hr))
         {
            // Show the Open dialog box.
            pFileOpen->SetFileTypes(numTypes, cdfs);   //  choose file types to be displayed
            pFileOpen->SetTitle(L"Open File");         //  heading of dialog box
            hr = pFileOpen->Show(NULL);

            // Get the file name from the dialog box.
            if (SUCCEEDED(hr))
             {
                IShellItem* pItem;
                hr = pFileOpen->GetResult(&pItem);
                if (SUCCEEDED(hr))
                 {
                    LPWSTR pTemp;
                    hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pTemp);
                    wcscpy_s(pszFilePath, MAX_PATH, pTemp);
                    if (SUCCEEDED(hr))  CoTaskMemFree(pTemp);
                    pItem->Release();
                 }
             }
            pFileOpen->Release();
         }
        CoUninitialize();
     }
    return;
 }
Last edited on
Topic archived. No new replies allowed.