Tutorial WinAPI C++ 3.2 (Common Dialogs)
Categorías : Windows, Programación, C y C++.

El siguiente tutorial tiene como objetivo mostrar por encima algunos de los common dialogs de windows. Lo primero que debéis saber es que un common dialog se refiere a un dialogo común del sistema, como por ejemplo el dialogo que nos sale cuando guardamos un archivo de texto en el Notepad.
Estos dialogos son usados por el 99% de aplicaciones que requieren trabajar con archivos / directorios, con impresoras, con colores, con fuentes, etc... Es importante conocerlos ya que nos pueden ahorrar un gran trabajo.
Aunque no vamos a verlos todos en este tutorial, veremos los dialogos para trabajar con el sistema de archivos, y que hacen referenca a las acciones : 'abrir', 'guardar' y 'seleccionar directorio'.


La imagen superior pertenece a un dialogo para guardar archivos de Windows7, y la imagen inferior a a un dialogo para guardar archivos de Windows XP. Como podéis observar el aspecto grafico es distinto de un sistema operativo a otro, pero el código para llamar a ese dialogo es exactamente el mismo incluso para Win95,98,ME.
ObjetoDlgAbrir
Bueno pasemos a ver la declaración de ObjetoDlgAbrir :
// Clase que hereda ObjetoControl y se convierte en el marcador para nuestra calculadoraclass ObjetoDlgAbrir {public : //////////////////////// Miembros publicos// -ConstructorObjetoDlgAbrir(void);// -Destructor~ObjetoDlgAbrir(void);// -Función que muestra el dialogo abrirconst UINT MostrarAbrir( HWND hWndParent, const TCHAR *nTitulo, const TCHAR *nPathRoot = NULL,const TCHAR *nFiltro = NULL, const bool nMultiSeleccion = false );// -Función que retorna el total de archivos señalados para abririnline const UINT TotalArchivos(void) {return static_cast<unsigned int>(_Archivos.size());}// -Función que retorna el archivo de la posición especificadainline TCHAR *Archivo(const UINT Pos) {return _Archivos[Pos];};// -Operador que retorna el archivo de la posición especificadainline TCHAR *operator [] (const UINT Pos) {return _Archivos[Pos];};private : /////////////////////// Miembros privados// -Vector que contiene los archivos a abrirstd::vector<TCHAR *> _Archivos;};
En esencia tiene una función para mostrar el dialogo “MostrarAbrir” que nos devolverá el numero de archivos seleccionados. Luego tiene una función TotalArchivos que también nos devuelve el total de archivos seleccionados, una función Archivo y un operador [] que nos devuelven el Path del archivo especificado en el miembro Pos. Y por ultimo un vector de TCHARS que contendrá los paths de los archivos seleccionados.
Ahora veamos la función MostrarAbrir :
const size_t ObjetoDlgAbrir::MostrarAbrir( HWND hWndParent, const TCHAR *nTitulo, const TCHAR *nPathRoot,const TCHAR *nFiltro, const bool nMultiSeleccion ) {OPENFILENAME ofn;TCHAR szFile[32768];bool ObteniendoRuta = true;TCHAR Archi[MAX_PATH +1];TCHAR Path[MAX_PATH +1];TCHAR PathFinal[MAX_PATH +1];UINT Tam = 0;TCHAR *Tmp = NULL;size_t N = 0;size_t i = 0;// - 1 INICIALIZACION// Creo la estructura para GetOpenFileNameZeroMemory(&ofn, sizeof(ofn));ofn.lpstrCustomFilter = NULL;ofn.nFilterIndex = 1;ofn.lpstrFile = szFile;ofn.nMaxFile = 32768;ofn.lpstrFileTitle = NULL;ofn.nMaxFileTitle = 4096;ofn.lpstrTitle = nTitulo;ofn.lStructSize = sizeof(OPENFILENAME);ofn.hwndOwner = hWndParent;// Establezco los estilos por defectoif (nMultiSeleccion == true) ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT;else ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;// Establezco el filtro (Si es NULL usaremos un filtro para todos los archivos)if (nFiltro == NULL) ofn.lpstrFilter = TEXT("Todos los archivos\0*.*\0");else ofn.lpstrFilter = nFiltro;// Establezco la ruta inicial (Si es NULL usaremos la ruta del proyecto)if (nPathRoot == NULL) {TCHAR PathInicial[MAX_PATH +1];GetCurrentDirectory(MAX_PATH, PathInicial);ofn.lpstrInitialDir = PathInicial;}else {ofn.lpstrInitialDir = nPathRoot;}// Limpio la lista de archivos_Archivos.clear();// Limpio el buffer para el archivo actualZeroMemory(szFile, sizeof(TCHAR) * 32768);// - 2 MOSTRAR EL DIALOGO// Ejecuto GetOpenFileName para mostrar el dialogo abrir (Si se pulsa cancelar GetOpenFilename retorna 0)if (GetOpenFileName(&ofn)) {// Si el dialogo no tiene multiseleccion añado el archivo a la lista y retorno 1if (nMultiSeleccion == false) {Tam = wcslen(szFile) +1;Tmp = new TCHAR[Tam];wcscpy_s(Tmp, Tam, szFile);_Archivos.push_back(Tmp);return 1;}// Si llegamos a este punto es que el dialogo se ha creado con multiselección// En este caso szFile tiene dentro varios strings separados por el caracter 0// El primer string corresponde al path donde estan los archivos, ej : c:\mis documentos\// El resto de strings corresponden al nombre del archivo si su ruta, ej : Archivo.txt// Escaneamos toda la cadena para separar los stringsfor (i = 0; i < 32768; i++) {// Guardo el caracter actual, y sumo +1 a N (en este orden)Archi[N++] = szFile[i];// Si encontramos el caracter 0 es que se ha terminado la cadenaif (szFile[i] == 0) {Archi[N] = 0;// Si el tamaño del ultimo archivo es 1 es que ya no quedan mas archivos por lo que salimos del forif (N == 1) break;N = 0;// Si aun no se ha obtenido el path, lo obtenemos// (una vez tenemos el path sabemos que el resto de strings son archivos)if (ObteniendoRuta == true) {wcscpy_s(Path, MAX_PATH +1, Archi);Tam = wcslen(Path);ObteniendoRuta = false;// Si el path no termina con una barra separadora de directorios, la añadimosif (Path[Tam -1] != TEXT('\\')) wcscat_s(Path, MAX_PATH +1, TEXT("\\"));}else {wcscpy_s(PathFinal, MAX_PATH +1, Path);wcscat_s(PathFinal, MAX_PATH +1, Archi);Tam = wcslen(PathFinal) + 1;// Añadimos el archivo con su path completo a la lista de archivosTmp = new TCHAR[Tam];wcscpy_s(Tmp, Tam, PathFinal);_Archivos.push_back(Tmp);}}}}// Aunque se cree el dialogo con multiselección si solo seleccionamos un archivo, nos devolvera la ruta completa// en ve de separar el directorio y el archivo en dos strings.if (_Archivos.size() == 0 && wcslen(Path) != 0) {Tam = wcslen(szFile) + 1;Tmp = new TCHAR[Tam];wcscpy_s(Tmp, Tam, szFile);_Archivos.push_back(Tmp);}return _Archivos.size();}
Esta función es algo grande, por la que la resumiré en 2 partes (INICIALIZACION y MOSTRAR EL DIALOGO)
INICIALIZACION : En esencia rellenamos la estructura OPENFILENAME de forma que la configuremos para realizar la tarea. Lo mas importante de esta estructura aparte de los estilos es el filtro de archivos. Veréis una línea con TEXT(“Todos los archivos\0*.*\0”), esto es el filtro y en este caso se ajusta a todos los archivos. Si por ejemplo queremos crear un filtro para archivos de texto podríamos poner esta cadena : TEXT(“Archivos de texto\0*.txt;*.texto\0Todos los archivos\0*.*\0”). Con esto se crearía un filtro para las extensiones *.txt y *.texto. Si deseamos añadir mas extensiones a un filtro tienen que estar delimitadas por el carácter ‘;’
MOSTRAR EL DIALOGO : Para mostrar el dialogo lo primero es llamar a la API GetOpenFilename. Una vez GetOpenFilename termine querrá decir que el usuario ha seleccionado uno o mas archivos, o que el usuario ha cancelado la selección. En el caso de que el dialogo tenga multiselección GetOpenFilename nos devolverá un string con el directorio al principio seguido de todos los nombres de archivo seleccionados. Esto es algo incomodo ya que vamos a tener que parsear cada archivo seleccionado para añadirle el directorio.
// CUIDADO!! con MINGW no se acepta la directiva pragma comment#if!defined LIBRERIA_SHELL32#define LIBRERIA_SHELL32#pragma comment(lib, "shell32.lib") // Agrego la libreria shell32.lib al proyecto#endif
ObjetoDlgGuardar
Nos toca ver el ObjetoDglGuardar (Dialogo para guardar archivos) :
// Clase que hereda ObjetoControl y se convierte en el marcador para nuestra calculadoraclass ObjetoDlgGuardar {public : //////////////////////////////// Miembros publicos// -ConstructorObjetoDlgGuardar(void);// -Destructor~ObjetoDlgGuardar(void);// -Función que muestra el dialogo guardarconst TCHAR *MostrarGuardar( HWND hWndParent, const TCHAR *nTitulo,const TCHAR *nPathRoot = NULL, const TCHAR *nFiltro = NULL );// -Función que retorna el archivo a guardarinline const TCHAR *Archivo(void) {return _Archivo;};// -Operador que retorna el archivo a guardarinline const TCHAR *operator() (void) {return _Archivo;};private : /////////////////////////////// Miembros privados// -TCHAR que contiene el archivo a guardarTCHAR *_Archivo;};
La idea es prácticamente la misma que con el dialogo abrir, con excepción de que sabemos que este dialogo siempre retornara un único archivo. Por eso la función MostrarGuardar nos retorna directamente el Path del archivo seleccionado.
Veamos la función MostrarGuardar
// -Función que muestra el dialogo guardarconst TCHAR *ObjetoDlgGuardar::MostrarGuardar( HWND hWndParent, const TCHAR *nTitulo,const TCHAR *nPathRoot, const TCHAR *nFiltro) {// - 1 INICIALIZACIONif (_Archivo != NULL) delete [] _Archivo;_Archivo = NULL;TCHAR szFile[MAX_PATH +1];OPENFILENAME ofn;ZeroMemory(&ofn, sizeof(ofn));// Creo la estructura para GetSaveFileNameofn.lStructSize = sizeof(ofn);ofn.hwndOwner = hWndParent;ofn.lpstrFile = szFile;ofn.lpstrTitle = nTitulo;ofn.lpstrFile[0] = 0;ofn.nMaxFile = sizeof(szFile);ofn.nFilterIndex = 1;ofn.lpstrFileTitle = NULL;ofn.nMaxFileTitle = 0;ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST;// Establezco el filtro (Si es NULL usaremos un filtro para todos los archivos)if (nFiltro == NULL) ofn.lpstrFilter = TEXT("Todos los archivos\0*.*\0");else ofn.lpstrFilter = nFiltro;// Establezco la ruta inicial (Si es NULL usaremos la ruta del proyecto)if (nPathRoot == NULL) {TCHAR PathInicial[MAX_PATH +1];GetCurrentDirectory(MAX_PATH, PathInicial);ofn.lpstrInitialDir = PathInicial;}else {ofn.lpstrInitialDir = nPathRoot;}// - 2 MOSTRAR DIALOGO GUARDAR// Utilizo GetSaveFileName para mostrar el dialogo guardarif (GetSaveFileName(&ofn) == TRUE) {UINT Tam = wcslen(szFile) + 1;_Archivo = new TCHAR[Tam];wcscpy_s(_Archivo, Tam, szFile);}// Retorno el path del archivoreturn _Archivo;}
La inicialización de la estructura OPENFILENAME es prácticamente igual a la del dialogo abrir, con la excepción de los estilos. Y para mostrar el dialogo guardar utilizamos la API GetSaveFilename.
// CUIDADO!! con MINGW no se acepta la directiva pragma comment#if!defined LIBRERIA_SHELL32#define LIBRERIA_SHELL32#pragma comment(lib, "shell32.lib") // Agrego la libreria shell32.lib al proyecto#endif
ObjetoDlgDirectorios
Ahora nos toca ver el ObjetoDlgDirectorios :
// Clase que hereda ObjetoControl y se convierte en el marcador para nuestra calculadoraclass ObjetoDlgDirectorios {public : //////////////////////////////// Miembros publicos// -ConstructorObjetoDlgDirectorios(void);// -Destructor~ObjetoDlgDirectorios(void);// -Función que muestra el dialogo buscar directorioconst TCHAR *MostrarDirectorios( HWND hWndParent, const TCHAR *nTitulo, const bool BotonCrearDir = false,const bool MostrarArchivos = false );// -Función que retorna el directorio seleccionadoinline const TCHAR *Directorio(void) {return _Directorio;};// -Operador que retorna el directorio seleccionadoinline const TCHAR *operator() (void) {return _Directorio;};private : /////////////////////////////// Miembros privados// -TCHAR que contiene el directorio seleccionadoTCHAR *_Directorio;};
La declaración de este objeto es prácticamente idéntica a la del dialogo guardar en cuanto a funcionabilidad, solo se diferencian en la función Mostrar…
Veamos como funciona la función MostrarDirectorios :
// -Función que muestra el dialogo guardarconst TCHAR *ObjetoDlgDirectorios::MostrarDirectorios( HWND hWndParent, const TCHAR *nTitulo, const bool BotonCrearDir,const bool MostrarArchivos ) {// - 1 INICIALIZACIONif (_Directorio != NULL) delete [] _Directorio;_Directorio = NULL;BROWSEINFO BuscarDir;PIDLIST_ABSOLUTE ID;PIDLIST_ABSOLUTE IDRoot;TCHAR TmpDirectorio[MAX_PATH + 1] = TEXT("");TCHAR DiName[MAX_PATH +1] = TEXT("");BuscarDir.hwndOwner = hWndParent;BuscarDir.lpszTitle = nTitulo;BuscarDir.lpfn = NULL;BuscarDir.pszDisplayName = DiName;BuscarDir.ulFlags = 0;// BIF_NONEWFOLDERBUTTON Solo funciona a partir de windows vista....if (BotonCrearDir == false) BuscarDir.ulFlags = BuscarDir.ulFlags | BIF_NONEWFOLDERBUTTON;// BIF_BROWSEINCLUDEFILES Mostrar archivosif (MostrarArchivos == true) BuscarDir.ulFlags = BuscarDir.ulFlags | BIF_BROWSEINCLUDEFILES;// SHGetFolderLocation no compila en VC6...HRESULT Res = SHGetFolderLocation(hWndParent, CSIDL_DRIVES, NULL, 0, &IDRoot);BuscarDir.pidlRoot = IDRoot;// - 2 MOSTRAR DIALOGO DIRECTORIOS// Mostramos el dialogo para seleccionar el directorioID = SHBrowseForFolder(&BuscarDir);if (ID != NULL) SHGetPathFromIDList(ID, TmpDirectorio);// Asignamos el directorio obtenido con SHGetPathFromIDListUINT Tam = wcslen(TmpDirectorio) + 1;if (Tam > 1) {if (TmpDirectorio[Tam -2] != TEXT('\\')) {TmpDirectorio[Tam -1] = TEXT('\\');TmpDirectorio[Tam] = TEXT('\0');Tam ++;}_Directorio = new TCHAR[Tam];wcscpy_s(_Directorio, Tam, TmpDirectorio);}// Liberamos de la memoria los datos de SHBrowseForFolder y de SHGetFolderLocationILFree(IDRoot);ILFree(ID);return _Directorio;}
En la inicialización rellenamos la estructura BROWSEINFO, y luego mostramos el dialogo directorios con la API SHBrowseForFolder. La API SHBrowseForFolder nos devuelve un PIDLIST_ABSOLUTE que apunta a la ruta devuelta, para traducir la ruta a un string con un path se utiliza la API SHGetPathFromIDList.
Con esto ya tenemos todo lo que necesitara tanto el instalador como el ensamblador . Para mas información acerca de los dialogos comunes consultad este enlace de la MSDN : Common Dialog Box Library
Siguiente tutorial : 3.3 Creación del ObjetoEditBox.