c++user-interfacevisual-studio-2017windows-api-code-packgetopenfilename

c++ win32 console app adding a "choose file" dialog box using windows api


I am trying to add a choose file dialog box to my project which now can only take the filename input by the user.

I did some search and it seems windows API with GetOpenFileName function is the easiest way for me to do that. However, when I copy&paste the example code whether from MSDN or other websites, I got some build errors.

I am using visual studio 2017. And the example code I used is from http://www.cplusplus.com/forum/windows/169960/:

#include <iostream>

#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>

int main()
{
  char filename[ MAX_PATH ];

  OPENFILENAME ofn;
    ZeroMemory( &filename, sizeof( filename ) );
    ZeroMemory( &ofn,      sizeof( ofn ) );
    ofn.lStructSize  = sizeof( ofn );
    ofn.hwndOwner    = NULL;  // If you have a window to center over, put its HANDLE here
    ofn.lpstrFilter  = "Text Files\0*.txt\0Any File\0*.*\0";
    ofn.lpstrFile    = filename;
    ofn.nMaxFile     = MAX_PATH;
    ofn.lpstrTitle   = "Select a File, yo!";
    ofn.Flags        = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;

  if (GetOpenFileNameA( &ofn ))
  {
    std::cout << "You chose the file \"" << filename << "\"\n";
  }
  else
  {
    // All this stuff below is to tell you exactly how you messed up above. 
    // Once you've got that fixed, you can often (not always!) reduce it to a 'user cancelled' assumption.
    switch (CommDlgExtendedError())
    {
      case CDERR_DIALOGFAILURE   : std::cout << "CDERR_DIALOGFAILURE\n";   break;
      case CDERR_FINDRESFAILURE  : std::cout << "CDERR_FINDRESFAILURE\n";  break;
      case CDERR_INITIALIZATION  : std::cout << "CDERR_INITIALIZATION\n";  break;
      case CDERR_LOADRESFAILURE  : std::cout << "CDERR_LOADRESFAILURE\n";  break;
      case CDERR_LOADSTRFAILURE  : std::cout << "CDERR_LOADSTRFAILURE\n";  break;
      case CDERR_LOCKRESFAILURE  : std::cout << "CDERR_LOCKRESFAILURE\n";  break;
      case CDERR_MEMALLOCFAILURE : std::cout << "CDERR_MEMALLOCFAILURE\n"; break;
      case CDERR_MEMLOCKFAILURE  : std::cout << "CDERR_MEMLOCKFAILURE\n";  break;
      case CDERR_NOHINSTANCE     : std::cout << "CDERR_NOHINSTANCE\n";     break;
      case CDERR_NOHOOK          : std::cout << "CDERR_NOHOOK\n";          break;
      case CDERR_NOTEMPLATE      : std::cout << "CDERR_NOTEMPLATE\n";      break;
      case CDERR_STRUCTSIZE      : std::cout << "CDERR_STRUCTSIZE\n";      break;
      case FNERR_BUFFERTOOSMALL  : std::cout << "FNERR_BUFFERTOOSMALL\n";  break;
      case FNERR_INVALIDFILENAME : std::cout << "FNERR_INVALIDFILENAME\n"; break;
      case FNERR_SUBCLASSFAILURE : std::cout << "FNERR_SUBCLASSFAILURE\n"; break;
      default                    : std::cout << "You cancelled.\n";
    }
  }
}

When I copy&paste to vs, it shows the following:

Severity    Code    Description Project File    Line    Suppression State
Error   C2440   '=': cannot convert from 'char [260]' to 'LPWSTR'   ConsoleApplication1 c:\users\xfan0\documents\visual studio 2017\projects\consoleapplication1\consoleapplication1\consoleapplication1.cpp    18  
Severity    Code    Description Project File    Line    Suppression State
Error   C2440   '=': cannot convert from 'const char [19]' to 'LPCWSTR' ConsoleApplication1 c:\users\xfan0\documents\visual studio 2017\projects\consoleapplication1\consoleapplication1\consoleapplication1.cpp    20  
Severity    Code    Description Project File    Line    Suppression State
Error   C2664   'BOOL GetOpenFileNameA(LPOPENFILENAMEA)': cannot convert argument 1 from 'OPENFILENAME *' to 'LPOPENFILENAMEA'  ConsoleApplication1 c:\users\xfan0\documents\visual studio 2017\projects\consoleapplication1\consoleapplication1\consoleapplication1.cpp    23  

Severity    Code    Description Project File    Line    Suppression State
Error (active)  E0167   argument of type "OPENFILENAME *" is incompatible with parameter of type "LPOPENFILENAMEA"  ConsoleApplication1 c:\Users\XFAN0\Documents\Visual Studio 2017\Projects\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp    23  

Tried to search for that but had not found my luck :(


Solution

  • The method Microsoft normally advocates is to use their "generic text" macros, so your string literals would look like this:

    ofn.lpstrFilter = _T("Text Files\0*.txt\0Any File\0*.*\0");
    ofn.lpstrTitle = _T("Select a File, yo!");
    

    This way, you can build for narrow or wide character strings (the latter by defining UNICODE and _UNICODE). The _T will map to nothing for a narrow-character build, and to a L for a wide-character build, so you automatically get the right kind of string for the way you're building.

    To use this, you include <tchar.h>.

    For example:

    #include <iostream>
    #include <tchar.h>
    
    #ifndef NOMINMAX
    #define NOMINMAX
    #endif
    #include <windows.h>
    
    int main()
    {
      char filename[ MAX_PATH ];
    
      OPENFILENAME ofn;
        ZeroMemory( &filename, sizeof( filename ) );
        ZeroMemory( &ofn,      sizeof( ofn ) );
        ofn.lStructSize  = sizeof( ofn );
        ofn.hwndOwner    = NULL;  // If you have a window to center over, put its HANDLE here
        ofn.lpstrFilter  = _T("Text Files\0*.txt\0Any File\0*.*\0");
        ofn.lpstrFile    = filename;
        ofn.nMaxFile     = MAX_PATH;
        ofn.lpstrTitle   = _T("Select a File, yo!");
        ofn.Flags        = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST;
    
      if (GetOpenFileName( &ofn ))
      {
        std::cout << "You chose the file \"" << filename << "\"\n";
      }
      else
      {
        // All this stuff below is to tell you exactly how you messed up above. 
        // Once you've got that fixed, you can often (not always!) reduce it to a 'user cancelled' assumption.
        switch (CommDlgExtendedError())
        {
          case CDERR_DIALOGFAILURE   : std::cout << "CDERR_DIALOGFAILURE\n";   break;
          case CDERR_FINDRESFAILURE  : std::cout << "CDERR_FINDRESFAILURE\n";  break;
          case CDERR_INITIALIZATION  : std::cout << "CDERR_INITIALIZATION\n";  break;
          case CDERR_LOADRESFAILURE  : std::cout << "CDERR_LOADRESFAILURE\n";  break;
          case CDERR_LOADSTRFAILURE  : std::cout << "CDERR_LOADSTRFAILURE\n";  break;
          case CDERR_LOCKRESFAILURE  : std::cout << "CDERR_LOCKRESFAILURE\n";  break;
          case CDERR_MEMALLOCFAILURE : std::cout << "CDERR_MEMALLOCFAILURE\n"; break;
          case CDERR_MEMLOCKFAILURE  : std::cout << "CDERR_MEMLOCKFAILURE\n";  break;
          case CDERR_NOHINSTANCE     : std::cout << "CDERR_NOHINSTANCE\n";     break;
          case CDERR_NOHOOK          : std::cout << "CDERR_NOHOOK\n";          break;
          case CDERR_NOTEMPLATE      : std::cout << "CDERR_NOTEMPLATE\n";      break;
          case CDERR_STRUCTSIZE      : std::cout << "CDERR_STRUCTSIZE\n";      break;
          case FNERR_BUFFERTOOSMALL  : std::cout << "FNERR_BUFFERTOOSMALL\n";  break;
          case FNERR_INVALIDFILENAME : std::cout << "FNERR_INVALIDFILENAME\n"; break;
          case FNERR_SUBCLASSFAILURE : std::cout << "FNERR_SUBCLASSFAILURE\n"; break;
          default                    : std::cout << "You cancelled.\n";
        }
      }
    }
    

    To get a "save" filename instead, just change the GetOpenFilename to GetSaveFilename. There are a few differences in the flags you're likely to pass in the OPENFILENAME though--for example, it's pretty common to pass OFN_FILEMUSTEXIST when you're opening a file, but you almost never want to when you're saving a file.