windowswinapiwindows-shellcommon-dialog

Windows IFileOpenDialog GetCurrentSelection sometimes return the wrong value


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.

Example image

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;
}

Solution

  • This is not a bug. The Shell GUI shown in Explorer (and partially in Common Dialogs) is more or less organized like this:

    Windows Shell GUI

    The IFileDialogEvents logic is the following:

    So, we can't trust GetCurrentSelection() in the OnFolderChange event.