NovaMonoFix
Errores PHP
X
Usuario
Password
0 FPS

Tutorial WinAPI C++ 3.2 (Common Dialogs)

06 de Noviembre del 2010 por Josep Antoni Bover, 26 visitas, 0 comentarios, 0 votos
Categorías : Windows, Programación, C y C++.
La web está en segundo plano, animación en pausa.
Cargando animación...
Tutorial WinAPI C++ 3.2 (Common Dialogs)

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.

Aunque el código para mostrar un dialogo sea el mismo de un sistema operativo a otro, nos podemos encontrar que en sistemas operativos mas nuevos tengan mas opciones que no funcionen en sistemas anteriores.

ObjetoDlgAbrir

Bueno pasemos a ver la declaración de ObjetoDlgAbrir :

Archivo : ObjetoDlgAbrir.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Clase que hereda ObjetoControl y se convierte en el marcador para nuestra calculadora
class ObjetoDlgAbrir {
public : //////////////////////// Miembros publicos
// -Constructor
ObjetoDlgAbrir(void);
// -Destructor
~ObjetoDlgAbrir(void);
// -Función que muestra el dialogo abrir
const 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 abrir
inline const UINT TotalArchivos(void) {
return static_cast<unsigned int>(_Archivos.size());
}
// -Función que retorna el archivo de la posición especificada
inline TCHAR *Archivo(const UINT Pos) {
return _Archivos[Pos];
};
// -Operador que retorna el archivo de la posición especificada
inline TCHAR *operator [] (const UINT Pos) {
return _Archivos[Pos];
};
private : /////////////////////// Miembros privados
// -Vector que contiene los archivos a abrir
std::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 :

Archivo : ObjetoDlgAbrir.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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 GetOpenFileName
ZeroMemory(&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 defecto
if (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 actual
ZeroMemory(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 1
if (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 strings
for (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 cadena
if (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 for
if (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ñadimos
if (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 archivos
Tmp = 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.

Si vamos a utilizar un dialogo para abrir en nuestra aplicación, necesitaremos añadir al linker la libreria Shell32.lib (Dependiendo del compilador puede que ya esté enlazada por defecto). Bajo Visual C podeis usar el siguiente codigo para enlazar la libreria al linker :
Archivo : ObjetoDlgAbrir.h
1
2
3
4
5
// 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) :

Archivo : ObjetoDlgGuardar.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Clase que hereda ObjetoControl y se convierte en el marcador para nuestra calculadora
class ObjetoDlgGuardar {
public : //////////////////////////////// Miembros publicos
// -Constructor
ObjetoDlgGuardar(void);
// -Destructor
~ObjetoDlgGuardar(void);
// -Función que muestra el dialogo guardar
const TCHAR *MostrarGuardar( HWND hWndParent, const TCHAR *nTitulo,
const TCHAR *nPathRoot = NULL, const TCHAR *nFiltro = NULL );
// -Función que retorna el archivo a guardar
inline const TCHAR *Archivo(void) {
return _Archivo;
};
// -Operador que retorna el archivo a guardar
inline const TCHAR *operator() (void) {
return _Archivo;
};
private : /////////////////////////////// Miembros privados
// -TCHAR que contiene el archivo a guardar
TCHAR *_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

Archivo : ObjetoDlgGuardar.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// -Función que muestra el dialogo guardar
const TCHAR *ObjetoDlgGuardar::MostrarGuardar( HWND hWndParent, const TCHAR *nTitulo,
const TCHAR *nPathRoot, const TCHAR *nFiltro) {
// - 1 INICIALIZACION
if (_Archivo != NULL) delete [] _Archivo;
_Archivo = NULL;
TCHAR szFile[MAX_PATH +1];
OPENFILENAME ofn;
ZeroMemory(&ofn, sizeof(ofn));
// Creo la estructura para GetSaveFileName
ofn.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 guardar
if (GetSaveFileName(&ofn) == TRUE) {
UINT Tam = wcslen(szFile) + 1;
_Archivo = new TCHAR[Tam];
wcscpy_s(_Archivo, Tam, szFile);
}
// Retorno el path del archivo
return _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.

Si vamos a utilizar un dialogo para guardar en nuestra aplicación, necesitaremos añadir al linker la libreria Shell32.lib (Dependiendo del compilador puede que ya esté enlazada por defecto) Bajo Visual C podeis usar el siguiente codigo para enlazar la libreria al linker :
Archivo : ObjetoDlgGuardar.h
1
2
3
4
5
// 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 :

Archivo : ObjetoDlgDirectorios.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Clase que hereda ObjetoControl y se convierte en el marcador para nuestra calculadora
class ObjetoDlgDirectorios {
public : //////////////////////////////// Miembros publicos
// -Constructor
ObjetoDlgDirectorios(void);
// -Destructor
~ObjetoDlgDirectorios(void);
// -Función que muestra el dialogo buscar directorio
const TCHAR *MostrarDirectorios( HWND hWndParent, const TCHAR *nTitulo, const bool BotonCrearDir = false,
const bool MostrarArchivos = false );
// -Función que retorna el directorio seleccionado
inline const TCHAR *Directorio(void) {
return _Directorio;
};
// -Operador que retorna el directorio seleccionado
inline const TCHAR *operator() (void) {
return _Directorio;
};
private : /////////////////////////////// Miembros privados
// -TCHAR que contiene el directorio seleccionado
TCHAR *_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 :

Archivo : ObjetoDlgDirectorios.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// -Función que muestra el dialogo guardar
const TCHAR *ObjetoDlgDirectorios::MostrarDirectorios( HWND hWndParent, const TCHAR *nTitulo, const bool BotonCrearDir,
const bool MostrarArchivos ) {
// - 1 INICIALIZACION
if (_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 archivos
if (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 directorio
ID = SHBrowseForFolder(&BuscarDir);
if (ID != NULL) SHGetPathFromIDList(ID, TmpDirectorio);
// Asignamos el directorio obtenido con SHGetPathFromIDList
UINT 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 SHGetFolderLocation
ILFree(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.