2

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 :(

Xiaofeng Fan
  • 437
  • 1
  • 4
  • 9
  • Your build is defaulted to expect wide character strings but you're using MBCS strings. You can either change your code to use wide character strings when calling the Windows APIs, or you can change your project's setting (Project Properties -> General -> Character Set -> Use Multi-Byte Character Set). – Adrian McCarthy Mar 29 '17 at 22:49
  • In places, the example code you grabbed is explicitly calling the MBCS versions (e.g., `GetOpenFileNameA`), but it's not doing it consistently (e.g., `OPENFILENAME` is defaulting to `OPENFILENAMEW` rather than `OPENFILENAMEA`). In modern Windows programs, using the wide-string versions of the API is preferred. The MBCS feature is mostly for backward compatibility, and it has limitations. – Adrian McCarthy Mar 29 '17 at 22:52
  • Cool. Actually I tried that setting but it was when I test another example then it didn't work Lol. – Xiaofeng Fan Mar 29 '17 at 23:00
  • Not very clear about your second point, yet... got lots of things to study I guess. Thank you anyway. – Xiaofeng Fan Mar 29 '17 at 23:01
  • Use `OPENFILENAMEA` instead of `OPENFILENAME` – Christopher Oicles Mar 30 '17 at 01:00
  • @AdrianMcCarthy Is there a simple way to do save file dialog as well? Like the OPENFILENAME method. – Xiaofeng Fan Mar 30 '17 at 11:12

1 Answers1

3

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.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • For saving file dialog, is there a simple way like the "OPENFILENAME" to do so? – Xiaofeng Fan Mar 30 '17 at 11:02
  • @XiaofengFan: [GetSaveFilename](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646928.aspx) – Jerry Coffin Mar 30 '17 at 14:19
  • Is there any example I can study with? I have checked that documentation from MSDN but the example given was like part of other stuff as well. So I was not able to understand the example given (at this stage). Will really appreciate some executable examples on GetSaveFileName use. – Xiaofeng Fan Mar 30 '17 at 14:30