Hopefully I'm allowed to ask this question, because while I think I've found a Windows bug, there's also a chance that my code is at fault. Especially since I'm just starting to learn about using IFileDialog
in its various forms. Here's my question:
I'm having trouble with IFileOpenDialog
in Folder mode. My current code (including a bunch of debug output) is at the bottom of this post.
Almost everything is working, but it seems that either the events are unreliable in some way. The image below shows a piece of the dialog I was using. When I click on any of the folders within the simLoop2 folder, I get OnSelectionChange
events, as expected, and when I use GetCurrentSelection
to get the newly selected folder, I also get the expected value. Generally, that works for the OnFolderChange
event as well, however when I selected the simLoop2\Saved INI Files folder (in the right-hand pane) and then clicked on the simLoop1 folder (in the left-hand pane) I receive the OnFolderChange
event, but when I call GetCurrentSelection
during the event, I get the previously selected folder name! (simLoop2\Saved INI Files). Clicking on the simLoop1 folder a second time gets things back in sync and I get the correct name for the selected folder.
So my code works almost all the time, but every once in a while, when the selection changes from the right to the left pane, my code gets the wrong selected folder name. Before you ask, this does not happen all the time, either.
I'm trying to figure out what I've done wrong, but I think I do the same thing every single time and I just cannot find a mistake. I'm thinking there is a bug in how/when the event handler is called, but I have no idea how to prove (or report) that to Microsoft.
the code: [edited to add headers and a simplistic main] [edited again after some debugging.]
// IFileTest.cpp : Defines the entry point for the application.
#include <Windows.h>
#define COBJMACROS
#include <shobjidl.h>
#include <Shlwapi.h>
#pragma comment(lib, "OneCore.lib")
// ------------------------------------------------------------------------
int sc_CheckSimIniFileSet( char *szPath )
{
int len = (int)lstrlen(szPath);
return (szPath[len-1] & 3); // Use the last letter to make a value from -..3
};
// ------------------------------------------------------------------------
#define FullIniCount 4
#define c_idMsg 12345
// ------------------------------------------------------------------------
static LPWSTR _MakeWide( char *sz, LPWSTR wsz )
{
DWORD len;
len = (DWORD)strlen( sz ) + 1;
OemToCharBuffW( sz, wsz, len );
return wsz;
}
// ------------------------------------------------------------------------
static char *_MakeAscii( LPWSTR wsz, char *sz )
{
DWORD len;
len = (DWORD)lstrlenW( wsz ) + 1;
CharToOemBuffW( wsz, sz, len );
return sz;
}
class CFolderSelectCallback : public IFileDialogEvents, public IFileDialogControlEvents
{
public:
CFolderSelectCallback()
{}
// IUnknown
IFACEMETHODIMP QueryInterface( REFIID riid, void **ppv )
{
static const QITAB qit[] ={
QITABENT( CFolderSelectCallback, IFileDialogEvents ),
QITABENT( CFolderSelectCallback, IFileDialogControlEvents ),
{ 0 },
};
return QISearch( this, qit, riid, ppv );
}
IFACEMETHODIMP_( ULONG ) AddRef() { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return 3; }
IFACEMETHODIMP_( ULONG ) Release() { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return 2; }
// IFileDialogEvents
IFACEMETHODIMP OnFileOk( IFileDialog * /* pfd */ ) { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return E_NOTIMPL; }
IFACEMETHODIMP OnFolderChanging( IFileDialog * /* pfd */, IShellItem * /* psi */ ) { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return E_NOTIMPL; }
IFACEMETHODIMP OnFolderChange( IFileDialog * pfd )
{
// Update the text of the Open/Add button here based on the selection
IShellItem *psi;
HRESULT hr = pfd->GetCurrentSelection( &psi );
if( SUCCEEDED( hr ) )
{
LPWSTR wszPath;
IFileDialogCustomize *pfdc;
if( SUCCEEDED( pfd->QueryInterface( &pfdc ) ) )
{
if( SUCCEEDED( psi->GetDisplayName( SIGDN_DESKTOPABSOLUTEPARSING, &wszPath ) ) )
{
char szPath[_MAX_PATH];
_MakeAscii( wszPath, szPath );
int cIni = sc_CheckSimIniFileSet( szPath );
OutputDebugString("DEBUG: " __FUNCTION__ " path '");
OutputDebugString(szPath);
switch( cIni )
{
default: OutputDebugString("' event: default: \n" ); break;
case 0 : OutputDebugString("' event: case 0 : \n" ); break;
case 1 : OutputDebugString("' event: case 1 : \n" ); break;
case 2 : OutputDebugString("' event: case 2 : \n" ); break;
case 3 : OutputDebugString("' event: case 3 : \n" ); break;
case FullIniCount: OutputDebugString("' event: case FullIniCount:\n" ); break;
}
switch( cIni )
{
default:
case 0 : pfdc->SetControlLabel( c_idMsg, L"missing/invalid" ); pfd->SetOkButtonLabel( L"No INI Set" ); break;
case 1 : pfdc->SetControlLabel( c_idMsg, L"Only 1/4 found" ); pfd->SetOkButtonLabel( L"Bad INI Set" ); break;
case 2 : pfdc->SetControlLabel( c_idMsg, L"Only 2/4 found" ); pfd->SetOkButtonLabel( L"Bad INI Set" ); break;
case 3 : pfdc->SetControlLabel( c_idMsg, L"Only 3/4 found" ); pfd->SetOkButtonLabel( L"Bad INI Set" ); break;
case FullIniCount: pfdc->SetControlLabel( c_idMsg, L"Complete INI Set" ); pfd->SetOkButtonLabel( L"Choose" ); break;
}
CoTaskMemFree( wszPath );
}
else
{
OutputDebugString("DEBUG: " __FUNCTION__ " event\n" );
pfd->SetOkButtonLabel( L"GDN Error" );
}
pfdc->Release();
}
else
{
OutputDebugString("DEBUG: " __FUNCTION__ " event\n" );
pfd->SetOkButtonLabel( L"QI Error" );
}
psi->Release();
}
else
{
OutputDebugString("DEBUG: " __FUNCTION__ " event:\n" );
pfd->SetOkButtonLabel( L"GCS Error" );
}
return S_OK;
}
IFACEMETHODIMP OnSelectionChange( IFileDialog *pfd )
{
// Update the text of the Open/Add button here based on the selection
IShellItem *psi;
HRESULT hr = pfd->GetCurrentSelection( &psi );
if( SUCCEEDED( hr ) )
{
LPWSTR wszPath;
IFileDialogCustomize *pfdc;
if( SUCCEEDED( pfd->QueryInterface( &pfdc ) ) )
{
if( SUCCEEDED( psi->GetDisplayName( SIGDN_DESKTOPABSOLUTEPARSING, &wszPath ) ) )
{
char szPath[_MAX_PATH];
_MakeAscii( wszPath, szPath );
int cIni = sc_CheckSimIniFileSet( szPath );
OutputDebugString("DEBUG: " __FUNCTION__ " path '");
OutputDebugString(szPath);
switch( cIni )
{
default: OutputDebugString("' event: default: \n" ); break;
case 0 : OutputDebugString("' event: case 0 : \n" ); break;
case 1 : OutputDebugString("' event: case 1 : \n" ); break;
case 2 : OutputDebugString("' event: case 2 : \n" ); break;
case 3 : OutputDebugString("' event: case 3 : \n" ); break;
case FullIniCount: OutputDebugString("' event: case FullIniCount:\n" ); break;
}
switch( cIni )
{
default:
case 0 : pfdc->SetControlLabel( c_idMsg, L"missing/invalid" ); pfd->SetOkButtonLabel( L"No INI Set" ); break;
case 1 : pfdc->SetControlLabel( c_idMsg, L"Only 1/4 found" ); pfd->SetOkButtonLabel( L"Bad INI Set" ); break;
case 2 : pfdc->SetControlLabel( c_idMsg, L"Only 2/4 found" ); pfd->SetOkButtonLabel( L"Bad INI Set" ); break;
case 3 : pfdc->SetControlLabel( c_idMsg, L"Only 3/4 found" ); pfd->SetOkButtonLabel( L"Bad INI Set" ); break;
case FullIniCount: pfdc->SetControlLabel( c_idMsg, L"Complete INI Set" ); pfd->SetOkButtonLabel( L"Choose" ); break;
}
CoTaskMemFree( wszPath );
}
else
{
OutputDebugString("DEBUG: " __FUNCTION__ " event\n" );
pfd->SetOkButtonLabel( L"GDN Error" );
}
pfdc->Release();
}
else
{
OutputDebugString("DEBUG: " __FUNCTION__ " event\n" );
pfd->SetOkButtonLabel( L"QI Error" );
}
psi->Release();
}
else
{
OutputDebugString("DEBUG: " __FUNCTION__ " event:\n" );
pfd->SetOkButtonLabel( L"GCS Error" );
}
return S_OK;
}
IFACEMETHODIMP OnShareViolation( IFileDialog * /* pfd */, IShellItem * /* psi */, FDE_SHAREVIOLATION_RESPONSE * /* pResponse */ ) { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return E_NOTIMPL; }
IFACEMETHODIMP OnTypeChange( IFileDialog * /* pfd */ ) { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return E_NOTIMPL; }
IFACEMETHODIMP OnOverwrite( IFileDialog * /* pfd */, IShellItem * /* psi */, FDE_OVERWRITE_RESPONSE * /* pResponse */ ) { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return E_NOTIMPL; }
// IFileDialogControlEvents
IFACEMETHODIMP OnItemSelected( IFileDialogCustomize * /* pfdc */, DWORD /* dwIDCtl */, DWORD /* dwIDItem */ ) { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return E_NOTIMPL; }
IFACEMETHODIMP OnButtonClicked( IFileDialogCustomize * /* pfdc */, DWORD /* dwIDCtl */ ) { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return E_NOTIMPL; }
IFACEMETHODIMP OnCheckButtonToggled( IFileDialogCustomize * /* pfdc */, DWORD /* dwIDCtl */, BOOL /* bChecked */ ) { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return E_NOTIMPL; }
IFACEMETHODIMP OnControlActivating( IFileDialogCustomize * /* pfdc */, DWORD /* dwIDCtl */ ) { OutputDebugString("DEBUG: " __FUNCTION__ " event\n" ); return E_NOTIMPL; }
};
// ------------------------------------------------------------------------
extern "C" BOOL nfio_ChooseFolder( char *szPath, char *szTitle )
{
IFileDialog *pfd;
WCHAR wszOldPath[_MAX_FNAME];
LPWSTR wszNewPath;
BOOL bResult = FALSE;
WCHAR wszTitle[256];
if( SUCCEEDED( CoCreateInstance( CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS( &pfd ) ) ) )
{
DWORD dwOptions;
if( SUCCEEDED( pfd->GetOptions( &dwOptions ) ) )
{
pfd->SetOptions( dwOptions | FOS_PICKFOLDERS );
}
pfd->SetTitle( _MakeWide( szTitle, wszTitle ) );
IShellItem *pFolder;
HRESULT hr;
_MakeWide( szPath, wszOldPath );
hr = SHCreateItemFromParsingName( wszOldPath, NULL, IID_PPV_ARGS(&pFolder) );
if( FAILED( hr ) ||
(hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) ||
(hr == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE )) )
{
OutputDebugString("SIMCONFIG: Unable to create IShellItem from folder name\n");
hr = SHCreateItemFromParsingName( L".\\", NULL, IID_PPV_ARGS(&pFolder) );
if( FAILED( hr ) ||
(hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) ||
(hr == HRESULT_FROM_WIN32(ERROR_INVALID_DRIVE )) )
{
DebugBreak();
}
}
pfd->SetFileName( wszOldPath );
pfd->SetDefaultFolder( pFolder );
// Need to figure out how to add a Preview handler to display available simIni types/versions
CFolderSelectCallback foacb;
DWORD dwCookie;
if( SUCCEEDED( pfd->Advise( &foacb, &dwCookie ) ) )
{
IFileDialogCustomize *pfdc;
if( SUCCEEDED( pfd->QueryInterface( &pfdc ) ) )
{
//pfdc->StartVisualGroup( c_idGrp, L"" );
pfdc->AddText( c_idMsg, L"no selection" );
//pfdc->EndVisualGroup();
pfdc->Release();
}
if( SUCCEEDED( pfd->Show( NULL ) ) )
{
IShellItem *psi;
if( SUCCEEDED( pfd->GetResult( &psi ) ) )
{
if( !SUCCEEDED( psi->GetDisplayName( SIGDN_DESKTOPABSOLUTEPARSING, &wszNewPath ) ) )
{
MessageBox( NULL, "GetIDListName() failed", NULL, NULL );
}
else
{
_MakeAscii( wszNewPath, szPath );
CoTaskMemFree( wszNewPath );
bResult = TRUE;
}
psi->Release();
}
}
pfd->Unadvise( dwCookie );
}
pfd->Release();
pFolder->Release();
}
return bResult;
}
// ----------------------------------------------
//int WINAPI WinMain( _In_ HINSTANCE hInstance,
// _In_opt_ HINSTANCE hUseless,
// _In_ LPSTR lpCmdLine,
// _In_ int nCmdShow )
//{
// nfio_ChooseFolder( ".", "Sample title" );
// return 0;
//}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
// CHANGE THE PATH TO SOMETHING VALID FOR YOUR TESTS! //
char szPath[_MAX_PATH] = "C:\\RMCS\\simLoop2";
char szTitle[] = "Sample title";
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
SetCurrentDirectory( szPath );
nfio_ChooseFolder( szPath, szTitle );
return 0;
}
This is not a bug. The Shell GUI shown in Explorer (and partially in Common Dialogs) is more or less organized like this:
The IFileDialogEvents logic is the following:
OnSelectionChange
is triggered by the "Folder View" (usually visible as a list of items and folders) and you can use IFileDialog::GetCurrentSelection()
when it's triggered.OnFolderChange
is triggered by the "Tree View" and you can use IFileDialog::GetFolder()
when it's triggered.So, we can't trust GetCurrentSelection()
in the OnFolderChange
event.