2025-11-28 00:35:46 +09:00

950 lines
40 KiB
C++

//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
#include <windows.h> // For common windows data types and function headers
#define STRICT_TYPED_ITEMIDS
#include <shlobj.h>
#include <objbase.h> // For COM headers
#include <shobjidl.h> // for IFileDialogEvents and IFileDialogControlEvents
#include <shlwapi.h>
#include <knownfolders.h> // for KnownFolder APIs/datatypes/function headers
#include <propvarutil.h> // for PROPVAR-related functions
#include <propkey.h> // for the Property key APIs/datatypes
#include <propidl.h> // for the Property System APIs
#include <strsafe.h> // for StringCchPrintfW
#include <shtypes.h> // for COMDLG_FILTERSPEC
#include <new>
#pragma comment(linker, "\"/manifestdependency:type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
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"*.*"}
};
// Indices of file types
#define INDEX_WORDDOC 1
#define INDEX_WEBPAGE 2
#define INDEX_TEXTDOC 3
// Controls
#define CONTROL_GROUP 2000
#define CONTROL_RADIOBUTTONLIST 2
#define CONTROL_RADIOBUTTON1 1
#define CONTROL_RADIOBUTTON2 2 // It is OK for this to have the same ID as CONTROL_RADIOBUTTONLIST,
// because it is a child control under CONTROL_RADIOBUTTONLIST
// IDs for the Task Dialog Buttons
#define IDC_BASICFILEOPEN 100
#define IDC_ADDITEMSTOCUSTOMPLACES 101
#define IDC_ADDCUSTOMCONTROLS 102
#define IDC_SETDEFAULTVALUESFORPROPERTIES 103
#define IDC_WRITEPROPERTIESUSINGHANDLERS 104
#define IDC_WRITEPROPERTIESWITHOUTUSINGHANDLERS 105
/* File Dialog Event Handler *****************************************************************************************************/
class CDialogEventHandler : public IFileDialogEvents,
public IFileDialogControlEvents
{
public:
// IUnknown methods
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] = {
QITABENT(CDialogEventHandler, IFileDialogEvents),
QITABENT(CDialogEventHandler, IFileDialogControlEvents),
{ 0 },
#pragma warning(suppress:4838)
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return InterlockedIncrement(&_cRef);
}
IFACEMETHODIMP_(ULONG) Release()
{
long cRef = InterlockedDecrement(&_cRef);
if (!cRef)
delete this;
return cRef;
}
// IFileDialogEvents methods
IFACEMETHODIMP OnFileOk(IFileDialog *) { return S_OK; };
IFACEMETHODIMP OnFolderChange(IFileDialog *) { return S_OK; };
IFACEMETHODIMP OnFolderChanging(IFileDialog *, IShellItem *) { return S_OK; };
IFACEMETHODIMP OnHelp(IFileDialog *) { return S_OK; };
IFACEMETHODIMP OnSelectionChange(IFileDialog *) { return S_OK; };
IFACEMETHODIMP OnShareViolation(IFileDialog *, IShellItem *, FDE_SHAREVIOLATION_RESPONSE *) { return S_OK; };
IFACEMETHODIMP OnTypeChange(IFileDialog *pfd);
IFACEMETHODIMP OnOverwrite(IFileDialog *, IShellItem *, FDE_OVERWRITE_RESPONSE *) { return S_OK; };
// IFileDialogControlEvents methods
IFACEMETHODIMP OnItemSelected(IFileDialogCustomize *pfdc, DWORD dwIDCtl, DWORD dwIDItem);
IFACEMETHODIMP OnButtonClicked(IFileDialogCustomize *, DWORD) { return S_OK; };
IFACEMETHODIMP OnCheckButtonToggled(IFileDialogCustomize *, DWORD, BOOL) { return S_OK; };
IFACEMETHODIMP OnControlActivating(IFileDialogCustomize *, DWORD) { return S_OK; };
CDialogEventHandler() : _cRef(1) { };
private:
~CDialogEventHandler() { };
long _cRef;
};
// IFileDialogEvents methods
// This method gets called when the file-type is changed (combo-box selection changes).
// For sample sake, let's react to this event by changing the properties show.
HRESULT CDialogEventHandler::OnTypeChange(IFileDialog *pfd)
{
IFileSaveDialog *pfsd;
HRESULT hr = pfd->QueryInterface(&pfsd);
if (SUCCEEDED(hr))
{
UINT uIndex;
hr = pfsd->GetFileTypeIndex(&uIndex); // index of current file-type
if (SUCCEEDED(hr))
{
IPropertyDescriptionList *pdl = NULL;
switch (uIndex)
{
case INDEX_WORDDOC:
// When .doc is selected, let's ask for some arbitrary property, say Title.
hr = PSGetPropertyDescriptionListFromString(L"prop:System.Title", IID_PPV_ARGS(&pdl));
if (SUCCEEDED(hr))
{
// FALSE as second param == do not show default properties.
hr = pfsd->SetCollectedProperties(pdl, FALSE);
pdl->Release();
}
break;
case INDEX_WEBPAGE:
// When .html is selected, let's ask for some other arbitrary property, say Keywords.
hr = PSGetPropertyDescriptionListFromString(L"prop:System.Keywords", IID_PPV_ARGS(&pdl));
if (SUCCEEDED(hr))
{
// FALSE as second param == do not show default properties.
hr = pfsd->SetCollectedProperties(pdl, FALSE);
pdl->Release();
}
break;
case INDEX_TEXTDOC:
// When .txt is selected, let's ask for some other arbitrary property, say Author.
hr = PSGetPropertyDescriptionListFromString(L"prop:System.Author", IID_PPV_ARGS(&pdl));
if (SUCCEEDED(hr))
{
// TRUE as second param == show default properties as well, but show Author property first in list.
hr = pfsd->SetCollectedProperties(pdl, TRUE);
pdl->Release();
}
break;
}
}
pfsd->Release();
}
return hr;
}
// IFileDialogControlEvents
// This method gets called when an dialog control item selection happens (radio-button selection. etc).
// For sample sake, let's react to this event by changing the dialog title.
HRESULT CDialogEventHandler::OnItemSelected(IFileDialogCustomize *pfdc, DWORD dwIDCtl, DWORD dwIDItem)
{
IFileDialog *pfd = NULL;
HRESULT hr = pfdc->QueryInterface(&pfd);
if (SUCCEEDED(hr))
{
if (dwIDCtl == CONTROL_RADIOBUTTONLIST)
{
switch (dwIDItem)
{
case CONTROL_RADIOBUTTON1:
hr = pfd->SetTitle(L"Longhorn Dialog");
break;
case CONTROL_RADIOBUTTON2:
hr = pfd->SetTitle(L"Vista Dialog");
break;
}
}
pfd->Release();
}
return hr;
}
// Instance creation helper
HRESULT CDialogEventHandler_CreateInstance(REFIID riid, void **ppv)
{
*ppv = NULL;
CDialogEventHandler *pDialogEventHandler = new (std::nothrow) CDialogEventHandler();
HRESULT hr = pDialogEventHandler ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = pDialogEventHandler->QueryInterface(riid, ppv);
pDialogEventHandler->Release();
}
return hr;
}
/* Utility Functions *************************************************************************************************************/
// A helper function that converts UNICODE data to ANSI and writes it to the given file.
// We write in ANSI format to make it easier to open the output file in Notepad.
HRESULT _WriteDataToFile(HANDLE hFile, PCWSTR pszDataIn)
{
// First figure out our required buffer size.
DWORD cbData = WideCharToMultiByte(CP_ACP, 0, pszDataIn, -1, NULL, 0, NULL, NULL);
HRESULT hr = (cbData == 0) ? HRESULT_FROM_WIN32(GetLastError()) : S_OK;
if (SUCCEEDED(hr))
{
// Now allocate a buffer of the required size, and call WideCharToMultiByte again to do the actual conversion.
char *pszData = new (std::nothrow) CHAR[cbData];
hr = pszData ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = WideCharToMultiByte(CP_ACP, 0, pszDataIn, -1, pszData, cbData, NULL, NULL)
? S_OK
: HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
DWORD dwBytesWritten = 0;
hr = WriteFile(hFile, pszData, cbData - 1, &dwBytesWritten, NULL)
? S_OK
: HRESULT_FROM_WIN32(GetLastError());
}
delete [] pszData;
}
}
return hr;
}
// Helper function to write property/value into a custom file format.
//
// We are inventing a dummy format here:
// [APPDATA]
// xxxxxx
// [ENDAPPDATA]
// [PROPERTY]foo=bar[ENDPROPERTY]
// [PROPERTY]foo2=bar2[ENDPROPERTY]
HRESULT _WritePropertyToCustomFile(PCWSTR pszFileName, PCWSTR pszPropertyName, PCWSTR pszValue)
{
HANDLE hFile = CreateFileW(pszFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS, // We will write only if the file exists.
FILE_ATTRIBUTE_NORMAL,
NULL);
HRESULT hr = (hFile == INVALID_HANDLE_VALUE) ? HRESULT_FROM_WIN32(GetLastError()) : S_OK;
if (SUCCEEDED(hr))
{
const WCHAR wszPropertyStartTag[] = L"[PROPERTY]";
const WCHAR wszPropertyEndTag[] = L"[ENDPROPERTY]\r\n";
const DWORD cchPropertyStartTag = (DWORD) wcslen(wszPropertyStartTag);
const static DWORD cchPropertyEndTag = (DWORD) wcslen(wszPropertyEndTag);
DWORD const cchPropertyLine = cchPropertyStartTag +
cchPropertyEndTag +
(DWORD) wcslen(pszPropertyName) +
(DWORD) wcslen(pszValue) +
2; // 1 for '=' + 1 for NULL terminator.
PWSTR pszPropertyLine = new (std::nothrow) WCHAR[cchPropertyLine];
hr = pszPropertyLine ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr))
{
hr = StringCchPrintfW(pszPropertyLine,
cchPropertyLine,
L"%s%s=%s%s",
wszPropertyStartTag,
pszPropertyName,
pszValue,
wszPropertyEndTag);
if (SUCCEEDED(hr))
{
hr = SetFilePointer(hFile, 0, NULL, FILE_END) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
hr = _WriteDataToFile(hFile, pszPropertyLine);
}
}
delete [] pszPropertyLine;
}
CloseHandle(hFile);
}
return hr;
}
// Helper function to write dummy content to a custom file format.
//
// We are inventing a dummy format here:
// [APPDATA]
// xxxxxx
// [ENDAPPDATA]
// [PROPERTY]foo=bar[ENDPROPERTY]
// [PROPERTY]foo2=bar2[ENDPROPERTY]
HRESULT _WriteDataToCustomFile(PCWSTR pszFileName)
{
HANDLE hFile = CreateFileW(pszFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
CREATE_ALWAYS, // Let's always create this file.
FILE_ATTRIBUTE_NORMAL,
NULL);
HRESULT hr = (hFile == INVALID_HANDLE_VALUE) ? HRESULT_FROM_WIN32(GetLastError()) : S_OK;
if (SUCCEEDED(hr))
{
WCHAR wszDummyContent[] = L"[MYAPPDATA]\r\nThis is an example of how to use the IFileSaveDialog interface.\r\n[ENDMYAPPDATA]\r\n";
hr = _WriteDataToFile(hFile, wszDummyContent);
CloseHandle(hFile);
}
return hr;
}
/* Common File Dialog Snippets ***************************************************************************************************/
// This code snippet demonstrates how to work with the common file dialog interface
HRESULT BasicFileOpen()
{
// CoCreate the File Open Dialog object.
IFileDialog *pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Create an event handling object, and hook it up to the dialog.
IFileDialogEvents *pfde = NULL;
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
if (SUCCEEDED(hr))
{
// Hook up the event handler.
DWORD dwCookie;
hr = pfd->Advise(pfde, &dwCookie);
if (SUCCEEDED(hr))
{
// Set the options on the dialog.
DWORD dwFlags;
// Before setting, always get the options first in order not to override existing options.
hr = pfd->GetOptions(&dwFlags);
if (SUCCEEDED(hr))
{
// In this case, get shell items only for file system items.
hr = pfd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
if (SUCCEEDED(hr))
{
// Set the file types to display only. Notice that, this is a 1-based array.
hr = pfd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes);
if (SUCCEEDED(hr))
{
// Set the selected file type index to Word Docs for this example.
hr = pfd->SetFileTypeIndex(INDEX_WORDDOC);
if (SUCCEEDED(hr))
{
// Set the default extension to be ".doc" file.
hr = pfd->SetDefaultExtension(L"doc");
if (SUCCEEDED(hr))
{
// Show the dialog
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
// Obtain the result, once the user clicks the 'Open' button.
// The result is an IShellItem object.
IShellItem *psiResult;
hr = pfd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
// We are just going to print out the name of the file for sample sake.
PWSTR pszFilePath = NULL;
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
if (SUCCEEDED(hr))
{
TaskDialog(NULL,
NULL,
L"CommonFileDialogApp",
pszFilePath,
NULL,
TDCBF_OK_BUTTON,
TD_INFORMATION_ICON,
NULL);
CoTaskMemFree(pszFilePath);
}
psiResult->Release();
}
}
}
}
}
}
}
// Unhook the event handler.
pfd->Unadvise(dwCookie);
}
pfde->Release();
}
pfd->Release();
}
return hr;
}
// The Common Places area in the File Dialog is extensible.
// This code snippet demonstrates how to extend the Common Places area.
// Look at CDialogEventHandler::OnItemSelected to see how messages pertaining to the added
// controls can be processed.
HRESULT AddItemsToCommonPlaces()
{
// CoCreate the File Open Dialog object.
IFileDialog *pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Always use known folders instead of hard-coding physical file paths.
// In this case we are using Public Music KnownFolder.
IKnownFolderManager *pkfm = NULL;
hr = CoCreateInstance(CLSID_KnownFolderManager, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pkfm));
if (SUCCEEDED(hr))
{
// Get the known folder.
IKnownFolder *pKnownFolder = NULL;
hr = pkfm->GetFolder(FOLDERID_PublicMusic, &pKnownFolder);
if (SUCCEEDED(hr))
{
// File Dialog APIs need an IShellItem that represents the location.
IShellItem *psi = NULL;
hr = pKnownFolder->GetShellItem(0, IID_PPV_ARGS(&psi));
if (SUCCEEDED(hr))
{
// Add the place to the bottom of default list in Common File Dialog.
hr = pfd->AddPlace(psi, FDAP_BOTTOM);
if (SUCCEEDED(hr))
{
// Show the File Dialog.
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
//
// You can add your own code here to handle the results.
//
}
}
psi->Release();
}
pKnownFolder->Release();
}
pkfm->Release();
}
pfd->Release();
}
return hr;
}
// This code snippet demonstrates how to add custom controls in the Common File Dialog.
HRESULT AddCustomControls()
{
// CoCreate the File Open Dialog object.
IFileDialog *pfd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
if (SUCCEEDED(hr))
{
// Create an event handling object, and hook it up to the dialog.
IFileDialogEvents *pfde = NULL;
DWORD dwCookie = 0;
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
if (SUCCEEDED(hr))
{
// Hook up the event handler.
hr = pfd->Advise(pfde, &dwCookie);
if (SUCCEEDED(hr))
{
// Set up a Customization.
IFileDialogCustomize *pfdc = NULL;
hr = pfd->QueryInterface(IID_PPV_ARGS(&pfdc));
if (SUCCEEDED(hr))
{
// Create a Visual Group.
hr = pfdc->StartVisualGroup(CONTROL_GROUP, L"Sample Group");
if (SUCCEEDED(hr))
{
// Add a radio-button list.
hr = pfdc->AddRadioButtonList(CONTROL_RADIOBUTTONLIST);
if (SUCCEEDED(hr))
{
// Set the state of the added radio-button list.
hr = pfdc->SetControlState(CONTROL_RADIOBUTTONLIST, CDCS_VISIBLE | CDCS_ENABLED);
if (SUCCEEDED(hr))
{
// Add individual buttons to the radio-button list.
hr = pfdc->AddControlItem(CONTROL_RADIOBUTTONLIST,
CONTROL_RADIOBUTTON1,
L"Change Title to Longhorn");
if (SUCCEEDED(hr))
{
hr = pfdc->AddControlItem(CONTROL_RADIOBUTTONLIST,
CONTROL_RADIOBUTTON2,
L"Change Title to Vista");
if (SUCCEEDED(hr))
{
// Set the default selection to option 1.
hr = pfdc->SetSelectedControlItem(CONTROL_RADIOBUTTONLIST,
CONTROL_RADIOBUTTON1);
}
}
}
}
// End the visual group.
pfdc->EndVisualGroup();
}
pfdc->Release();
}
if (FAILED(hr))
{
// Unadvise here in case we encounter failures before we get a chance to show the dialog.
pfd->Unadvise(dwCookie);
}
}
pfde->Release();
}
if (SUCCEEDED(hr))
{
// Now show the dialog.
hr = pfd->Show(NULL);
if (SUCCEEDED(hr))
{
//
// You can add your own code here to handle the results.
//
}
// Unhook the event handler.
pfd->Unadvise(dwCookie);
}
pfd->Release();
}
return hr;
}
// This code snippet demonstrates how to add default metadata in the Common File Dialog.
// Look at CDialogEventHandler::OnTypeChange to see to change the order/list of properties
// displayed in the Common File Dialog.
HRESULT SetDefaultValuesForProperties()
{
// CoCreate the File Open Dialog object.
IFileSaveDialog *pfsd = NULL;
HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfsd));
if (SUCCEEDED(hr))
{
// Create an event handling object, and hook it up to the dialog.
IFileDialogEvents *pfde = NULL;
DWORD dwCookie = 0;
hr = CDialogEventHandler_CreateInstance(IID_PPV_ARGS(&pfde));
if (SUCCEEDED(hr))
{
// Hook up the event handler.
hr = pfsd->Advise(pfde, &dwCookie);
if (SUCCEEDED(hr))
{
// Set the file types to display.
hr = pfsd->SetFileTypes(ARRAYSIZE(c_rgSaveTypes), c_rgSaveTypes);
if (SUCCEEDED(hr))
{
hr = pfsd->SetFileTypeIndex(INDEX_WORDDOC);
if (SUCCEEDED(hr))
{
hr = pfsd->SetDefaultExtension(L"doc");
if (SUCCEEDED(hr))
{
IPropertyStore *pps = NULL;
// The InMemory Property Store is a Property Store that is
// kept in the memory instead of persisted in a file stream.
hr = PSCreateMemoryPropertyStore(IID_PPV_ARGS(&pps));
if (SUCCEEDED(hr))
{
PROPVARIANT propvarValue = {};
hr = InitPropVariantFromString(L"SampleKeywordsValue", &propvarValue);
if (SUCCEEDED(hr))
{
// Set the value to the property store of the item.
hr = pps->SetValue(PKEY_Keywords, propvarValue);
if (SUCCEEDED(hr))
{
// Commit does the actual writing back to the in memory store.
hr = pps->Commit();
if (SUCCEEDED(hr))
{
// Hand these properties to the File Dialog.
hr = pfsd->SetCollectedProperties(NULL, TRUE);
if (SUCCEEDED(hr))
{
hr = pfsd->SetProperties(pps);
}
}
}
PropVariantClear(&propvarValue);
}
pps->Release();
}
}
}
}
if (FAILED(hr))
{
// Unadvise here in case we encounter failures before we get a chance to show the dialog.
pfsd->Unadvise(dwCookie);
}
}
pfde->Release();
}
if (SUCCEEDED(hr))
{
// Now show the dialog.
hr = pfsd->Show(NULL);
if (SUCCEEDED(hr))
{
//
// You can add your own code here to handle the results.
//
}
// Unhook the event handler.
pfsd->Unadvise(dwCookie);
}
pfsd->Release();
}
return hr;
}
// The following code snippet demonstrates two things:
// 1. How to write properties using property handlers.
// 2. Replicating properties in the "Save As" scenario where the user choses to save an existing file
// with a different name. We need to make sure we replicate not just the data,
// but also the properties of the original file.
HRESULT WritePropertiesUsingHandlers()
{
// CoCreate the File Open Dialog object.
IFileSaveDialog *pfsd;
HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfsd));
if (SUCCEEDED(hr))
{
WCHAR szFullPathToTestFile[MAX_PATH] = {};
// For this exercise, let's just support only one file type to make things simpler.
// Also, let's use the jpg format for sample purpose because the Windows ships with
// property handlers for jpg files.
const COMDLG_FILTERSPEC rgSaveTypes[] = {{L"Photo Document (*.jpg)", L"*.jpg"}};
// Set the file types to display.
hr = pfsd->SetFileTypes(ARRAYSIZE(rgSaveTypes), rgSaveTypes);
if (SUCCEEDED(hr))
{
hr = pfsd->SetFileTypeIndex(0);
if (SUCCEEDED(hr))
{
// Set default file extension.
hr = pfsd->SetDefaultExtension(L"jpg");
if (SUCCEEDED(hr))
{
// Ensure the dialog only returns items that can be represented by file system paths.
DWORD dwFlags;
hr = pfsd->GetOptions(&dwFlags);
if (SUCCEEDED(hr))
{
hr = pfsd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
// Let's first get the current property set of the file we are replicating
// and give it to the file dialog object.
//
// For simplicity sake, let's just get the property set from a pre-existing jpg file (in the Pictures folder).
// In the real-world, you would actually add code to get the property set of the file
// that is currently open and is being replicated.
if (SUCCEEDED(hr))
{
PWSTR pszPicturesFolderPath;
hr = SHGetKnownFolderPath(FOLDERID_SamplePictures, 0, NULL, &pszPicturesFolderPath);
if (SUCCEEDED(hr))
{
hr = PathCombineW(szFullPathToTestFile, pszPicturesFolderPath, L"Flower.jpg") ? S_OK : E_FAIL;
if (SUCCEEDED(hr))
{
IPropertyStore *pps;
hr = SHGetPropertyStoreFromParsingName(szFullPathToTestFile, NULL, GPS_DEFAULT, IID_PPV_ARGS(&pps));
if (FAILED(hr))
{
// Flower.jpg is probably not in the Pictures folder.
TaskDialog(NULL, NULL, L"CommonFileDialogApp", L"Create Flower.jpg in the Pictures folder and try again.",
NULL, TDCBF_OK_BUTTON, TD_ERROR_ICON , NULL);
}
else
{
// Call SetProperties on the file dialog object for getting back later.
pfsd->SetCollectedProperties(NULL, TRUE);
pfsd->SetProperties(pps);
pps->Release();
}
}
CoTaskMemFree(pszPicturesFolderPath);
}
}
}
}
}
}
if (SUCCEEDED(hr))
{
hr = pfsd->Show(NULL);
if (SUCCEEDED(hr))
{
IShellItem *psiResult;
hr = pfsd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
PWSTR pszNewFileName;
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszNewFileName);
if (SUCCEEDED(hr))
{
// This is where you add code to write data to your file.
// For simplicity, let's just copy a pre-existing dummy jpg file.
//
// In the real-world, you would actually add code to replicate the data of
// file that is currently open.
hr = CopyFileW(szFullPathToTestFile, pszNewFileName, FALSE) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
if (SUCCEEDED(hr))
{
// Now apply the properties.
//
// Get the property store first by calling GetPropertyStore and pass it on to ApplyProperties.
// This will make the registered propety handler for the specified file type (jpg)
// do all the work of writing the properties for you.
//
// Property handlers for the specified file type should be registered for this
// to work.
IPropertyStore *pps;
// When we call GetProperties, we get back all the properties that we originally set
// (in our call to SetProperties above) plus the ones user modified in the file dialog.
hr = pfsd->GetProperties(&pps);
if (SUCCEEDED(hr))
{
// Now apply the properties making use of the registered property handler for the file type.
//
// hWnd is used as parent for any error dialogs that might popup when writing properties.
// Pass NULL for IFileOperationProgressSink as we don't want to register any callback for progress notifications.
hr = pfsd->ApplyProperties(psiResult, pps, NULL, NULL);
pps->Release();
}
}
CoTaskMemFree(pszNewFileName);
}
psiResult->Release();
}
}
}
pfsd->Release();
}
return hr;
}
// This code snippet demonstrates how to write properties without using property handlers.
HRESULT WritePropertiesWithoutUsingHandlers()
{
// CoCreate the File Open Dialog object.
IFileSaveDialog *pfsd;
HRESULT hr = CoCreateInstance(CLSID_FileSaveDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfsd));
if (SUCCEEDED(hr))
{
// For this exercise, let's use a custom file type.
const COMDLG_FILTERSPEC rgSaveTypes[] = {{L"MyApp Document (*.myApp)", L"*.myApp"}};
// Set the file types to display.
hr = pfsd->SetFileTypes(ARRAYSIZE(rgSaveTypes), rgSaveTypes);
if (SUCCEEDED(hr))
{
hr = pfsd->SetFileTypeIndex(0);
if (SUCCEEDED(hr))
{
// Set default file extension.
hr = pfsd->SetDefaultExtension(L"myApp");
if (SUCCEEDED(hr))
{
// Ensure the dialog only returns items that can be represented by file system paths.
DWORD dwFlags;
hr = pfsd->GetOptions(&dwFlags);
if (SUCCEEDED(hr))
{
hr = pfsd->SetOptions(dwFlags | FOS_FORCEFILESYSTEM);
if (SUCCEEDED(hr))
{
// Set the properties you want the FileSave dialog to collect from the user.
IPropertyDescriptionList *pdl;
hr = PSGetPropertyDescriptionListFromString(L"prop:System.Keywords", IID_PPV_ARGS(&pdl));
if (SUCCEEDED(hr))
{
// TRUE as second param == show default properties as well, but show Keyword first.
hr = pfsd->SetCollectedProperties(pdl, TRUE);
pdl->Release();
}
}
}
}
}
}
if (SUCCEEDED(hr))
{
// Now show the dialog.
hr = pfsd->Show(NULL);
if (SUCCEEDED(hr))
{
IShellItem *psiResult;
hr = pfsd->GetResult(&psiResult);
if (SUCCEEDED(hr))
{
// Get the path to the file.
PWSTR pszNewFileName;
hr = psiResult->GetDisplayName(SIGDN_FILESYSPATH, &pszNewFileName);
if (SUCCEEDED(hr))
{
// Write data to the file.
hr = _WriteDataToCustomFile(pszNewFileName);
if (SUCCEEDED(hr))
{
// Now get the property store and write each individual property to the file.
IPropertyStore *pps;
hr = pfsd->GetProperties(&pps);
if (SUCCEEDED(hr))
{
DWORD cProps = 0;
hr = pps->GetCount(&cProps);
// Loop over property set and write each property/value pair to the file.
for (DWORD i = 0; i < cProps && SUCCEEDED(hr); i++)
{
PROPERTYKEY key;
hr = pps->GetAt(i, &key);
if (SUCCEEDED(hr))
{
PWSTR pszPropertyName;
hr = PSGetNameFromPropertyKey(key, &pszPropertyName);
if (SUCCEEDED(hr))
{
// Get the value of the property.
PROPVARIANT propvarValue;
PropVariantInit(&propvarValue);
hr = pps->GetValue(key, &propvarValue);
if (SUCCEEDED(hr))
{
WCHAR wszValue[MAX_PATH];
// Always use property system APIs to do the conversion for you.
hr = PropVariantToString(propvarValue, wszValue, ARRAYSIZE(wszValue));
if (SUCCEEDED(hr))
{
// Write the property to the file.
hr = _WritePropertyToCustomFile(pszNewFileName, pszPropertyName, wszValue);
}
}
PropVariantClear(&propvarValue);
CoTaskMemFree(pszPropertyName);
}
}
}
pps->Release();
}
}
CoTaskMemFree(pszNewFileName);
}
psiResult->Release();
}
}
}
pfsd->Release();
}
return hr;
}
// Application entry point
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
TASKDIALOGCONFIG taskDialogParams = { sizeof(taskDialogParams) };
taskDialogParams.dwFlags = TDF_USE_COMMAND_LINKS | TDF_ALLOW_DIALOG_CANCELLATION;
TASKDIALOG_BUTTON const buttons[] =
{
{ IDC_BASICFILEOPEN, L"Basic File Open" },
{ IDC_ADDITEMSTOCUSTOMPLACES, L"Add Items to Common Places" },
{ IDC_ADDCUSTOMCONTROLS, L"Add Custom Controls" },
{ IDC_SETDEFAULTVALUESFORPROPERTIES, L"Change Property Order" },
{ IDC_WRITEPROPERTIESUSINGHANDLERS, L"Write Properties Using Handlers" },
{ IDC_WRITEPROPERTIESWITHOUTUSINGHANDLERS, L"Write Properties without Using Handlers" },
};
taskDialogParams.pButtons = buttons;
taskDialogParams.cButtons = ARRAYSIZE(buttons);
taskDialogParams.pszMainInstruction = L"Pick the file dialog sample you want to try";
taskDialogParams.pszWindowTitle = L"Common File Dialog";
while (SUCCEEDED(hr))
{
int selectedId;
hr = TaskDialogIndirect(&taskDialogParams, &selectedId, NULL, NULL);
if (SUCCEEDED(hr))
{
if (selectedId == IDCANCEL)
{
break;
}
else if (selectedId == IDC_BASICFILEOPEN)
{
BasicFileOpen();
}
else if (selectedId == IDC_ADDITEMSTOCUSTOMPLACES)
{
AddItemsToCommonPlaces();
}
else if (selectedId == IDC_ADDCUSTOMCONTROLS)
{
AddCustomControls();
}
else if (selectedId == IDC_SETDEFAULTVALUESFORPROPERTIES)
{
SetDefaultValuesForProperties();
}
else if (selectedId == IDC_WRITEPROPERTIESUSINGHANDLERS)
{
WritePropertiesUsingHandlers();
}
else if (selectedId == IDC_WRITEPROPERTIESWITHOUTUSINGHANDLERS)
{
WritePropertiesWithoutUsingHandlers();
}
}
}
CoUninitialize();
}
return 0;
}