NovaMonoFix
Errores PHP
X
Usuario
Password
0 FPS

Tutorial WinAPI C++ 3.7 (Creación del ObjetoListView)

06 de Noviembre del 2010 por Josep Antoni Bover, 0 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.7 (Creación del ObjetoListView)

En este tutorial veremos el funcionamiento del control LISTVIEW de windows. El ListView es un control que nos permite hacer listas de varios tipos : Iconos grandes, Iconos pequeños, Lista horizontal, y Lista report. En especial nos centraremos en el modo Lista Report, ya que los otros modos en esencia funcionan prácticamente igual pero no soportan columnas, por lo que cada item tiene un único texto.

Vamos a seguir como en los últimos controles creando un nuevo objeto al que llamaremos ObjetoListView que heredara de ObjetoControlEstandar (La clase ObjetoControlEstandar se describe en el tutorial 3.3 Creación del ObjetoEditBox).

Veamos su declaración :

Archivo : ObjetoListView.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
27
28
29
30
31
32
33
34
35
36
37
38
// Clase que hereda ObjetoControlEstandar y se centra en las funciones del ListView
class ObjetoListView : public ObjetoControlEstandar {
public : //////// Miembros publicos
// -Constructor
ObjetoListView(void);
// -Destructor
~ObjetoListView(void);
// -Función para crear el ListView
void CrearListView( HWND hWndParent, const UINT nEstilos, const UINT nID,
const int cX, const int cY,
const int cAncho, const int cAlto );
// Función que agrega un item al final de la lista
int AgregarItem(const int nIcono, TCHAR *nTexto, ...);
// Función que agrega una columna
void AgregarColumna(TCHAR *nTexto, const int nAncho);
// Función para obtener el texto de un Item/SubItem
void ObtenerTexto( const UINT PosItem, const UINT PosSubItem,
TCHAR *nTxt, const UINT TamTxt);
// Función que borra el item especificado en la posición
void BorrarItem(const UINT nPos);
// Función que borra todos los items
void BorrarItems(void);
// Función que devuelve si el item de la posición especificada
// está seleccionado
BOOL Seleccionado(const UINT nPos);
// Función que devuelve el total de items dentro del listivew
inline int TotalItems(void) const { return ListView_GetItemCount(_hWnd); };
// Función que devuelve el total de columnas dentro del listivew
inline int TotalColumnas(void) const { return _Columnas; }
// Función que devuelve la posición del item marcado con el foco.
// Devuelve -1 si no hay item.
int ItemMarcado(void) const;
protected : ///// Miembros protegidos
// Fuente del listview (para creaciones dinamicas 'CrearListView')
HFONT _Fuente;
// Total de columnas
int _Columnas;
};

Para resumir, esta clase tiene un miembro para crear el ListView, y varios miembros para trabajar con items y columnas : AgregarItem, AgregarColumna, ObtenerTexto, BorrarItem, BorrarItems, Seleccionado, TotalItems, TotalColumnas, y ItemMarcado.

Bueno ahora que sabemos mas o menos que hacen los miembros vamos a verlos con mas detenimiento, para empezar veamos la funcion CrearListView :

Archivo : ObjetoListView.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Función para crear el ListView
void ObjetoListView::CrearListView( HWND hWndParent, const UINT nEstilos, const UINT nID,
const int cX, const int cY, const int cAncho, const int cAlto ) {
// Si ya se ha creado salimos.
if (_hWnd != NULL) return;
// Iniciamos los CommonControls
ObjetoIniciarCommonControls::Iniciar();
// Creamos la ventana con la clase WC_LISTVIEW
_hWnd = CreateWindowEx( NULL, WC_LISTVIEW, NULL, nEstilos, cX, cY, cAncho, cAlto, hWndParent,
reinterpret_cast<HMENU>(IntToPtr(nID)), GetModuleHandle(NULL), NULL );
// Conectamos el WindowProcedure del LISTVIEW con esta clase
_ConectarControl();
// Creamos la fuente que usara el ListView
_Fuente = CreateFont( 13, 0, 0, 0, FW_NORMAL, false, false, false, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY, FF_ROMAN, TEXT("Tahoma") );
// Mandamos un mensaje al ListView con su nueva fuente.
SendMessage(_hWnd, WM_SETFONT, (WPARAM)_Fuente , 0);
// Por defecto no hay columnas
_Columnas = 0;
}

Lo primero que se hace es llamar a la función ObjetoIniciarCommonControls::Iniciar. Esta función llama a la API InitCommonControlsEx para iniciar los controles estándar de windows para nuestra aplicación. Antiguamente en VC6 esto era necesario si queríamos trabajar con controles estándar, pero ahora mismo a decir verdad ya no se si es necesario, porque en mi maquina me funcionan igual los ejemplos tanto si llamo a InitCommonControlsEx como si no… y en la MSDN no dice nada.. Por lo que prefiero mantener este código por si las moscas.

Lo segundo que se hace es llamar a la API CreateWindowEx con el segundo parámetro "WC_LISTVIEW" (que es una macro para identificar la clase). Esto hace que el control creado se convierta en un LISTVIEW.

Lo siguiente es llamar a la función _ConectarControl, que re-emplazara el WindowProcedure del ListView por el nuestro.

Y por último creamos una fuente con la API CreateFont para el listview, y le mandamos el mensaje WM_SETFONT al listview con la API SendMessage, cosa que le dirá al listview que debe usar esa fuente.

Ahora veamos la funcion AgregarColumna :

Archivo : ObjetoListView.cpp
1
2
3
4
5
6
7
8
9
10
11
// Función que agrega una columna
void ObjetoListView::AgregarColumna(TCHAR *nTexto, const int nAncho) {
LVCOLUMN Columna;
Columna.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM;
Columna.cx = nAncho;
Columna.pszText = nTexto;
Columna.cchTextMax = static_cast<int>(wcslen(nTexto));
Columna.iOrder = _Columnas;
ListView_InsertColumn(_hWnd, 0, &Columna);
_Columnas ++;
}

Esta función nos pide un nombre para la columna, y su ancho en pixeles. Para empezar rellenamos la estructura LVCOLUMN con los datos de la nueva columna, hay que destacar que en el miembro "mask" especificamos que miembros deberá utilizar windows a la hora de crear la columna. En este caso utilizamos las constantes LVCF_TEXT, LVCF_WIDTH, y LVCF_SUBITEM para que windows mire los miembros cx, pszText, cchTextMax y iOrder. Por último llamamos a la macro ListView_InserColumn para agregar la columna al ListView.

Las columnas solo se tienen en cuenta en el modo LVS_REPORT, si el listview está en cualquier otro modo, las columnas serán ignoradas, y solo se mostrara un string por item.

Visto el tema de las columnas podemos empezar con los items. Veamos la funcion AgregarItem :

Archivo : ObjetoListView.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Función que agrega un item al inicio de la lista
int ObjetoListView::AgregarItem(const int nIcono, TCHAR *nTexto, ...) {
LV_ITEM LVItem;
ZeroMemory(&LVItem, sizeof(LV_ITEM));
LVItem.mask = LVIF_TEXT | LVIF_IMAGE;
LVItem.pszText = nTexto;
LVItem.cchTextMax = static_cast<int>(wcslen(nTexto));
LVItem.iSubItem = 0;
LVItem.iImage = nIcono;
LVItem.iItem = TotalItems();
int Ret = ListView_InsertItem(this->_hWnd, &LVItem);
va_list Marker;
va_start(Marker, nTexto);
for (int n = 0; n < _Columnas; n++) {
ListView_SetItemText(_hWnd, Ret, n +1, va_arg(Marker, TCHAR *));
}
va_end(Marker);
return Ret;
}

Esta función nos pide un ID de icono, un texto para el item, y luego tenemos un parámetro opcional al que hay que agregar los textos de las siguientes columnas. Para que quede mas claro, si hacemos un listview con tres columnas habrá que llamar a la función AgregarItem, pasándole una ID de icono, y luego tres strings.

Para empezar esta función rellena una estructura LV_ITEM con los datos necesarios para crear el item. Hay que remarcar que en el miembro "mask" hay que decirle que miembros deberá windows tener en cuenta (en este caso usamos LVIF_TEXT para que mire el texto, y LVIF_IMAGE para que mire la ID del icono). Una vez rellenada la estructura se llama a la macro ListView_InsertItem para agregar el item.

Al usar ListView_InsertItem solo agregamos el texto para la columna 0, los demás textos los añadiremos después utilizando la macro ListView_SetItemText, y especificando el numero de columna de la que queremos asignar el texto.

Ahora veamos la funcion ObtenerTexto :

Archivo : ObjetoListView.cpp
1
2
3
4
5
6
7
8
9
10
// Función para obtener el texto de un Item/SubItem
void ObjetoListView::ObtenerTexto(const UINT PosItem, const UINT PosSubItem, TCHAR *nTxt, const UINT TamTxt) {
LV_ITEM LVItem;
LVItem.mask = LVIF_TEXT;
LVItem.pszText = nTxt;
LVItem.cchTextMax = TamTxt;
LVItem.iSubItem = PosSubItem;
LVItem.iItem = PosItem;
ListView_GetItem(_hWnd, &LVItem);
}

Esta función nos pide la posición del item, la posición del subitem (que seria la columna), un TCHAR para hacer de buffer del string, y el tamaño del buffer.

Lo que hace la función es rellenar la estructura LV_ITEM de forma que enlacemos el buffer con esta, y luego hace una llamada a la macro ListView_GetItem, que copiara el texto del item/subitem especificados en nuestro buffer. Fijaros que ahora en el miembro "mask" solo especificamos LVIF_TEXT, ya que no necesitamos obtener nada mas que el texto.

Para terminar con esta clase veamos los miembros BorrarItem, BorrarItems, Seleccionado y ItemMarcado :

Archivo : ObjetoListView.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
// Función que borra el item especificado en la posición
void ObjetoListView::BorrarItem(const UINT nPos) {
ListView_DeleteItem(_hWnd, nPos);
}
// Función que borra todos los items
void ObjetoListView::BorrarItems(void) {
ListView_DeleteAllItems(_hWnd);
}
// Función que devuelve si el item de la posicion especificada esta seleccionado
BOOL ObjetoListView::Seleccionado(const UINT nPos) {
if (ListView_GetItemState(_hWnd, nPos, LVIS_SELECTED) == LVIS_SELECTED) return TRUE;
else return FALSE;
}
// Función que devuelve la posición del item marcado con el foco. Devuelve -1 si no hay item.
int ObjetoListView::ItemMarcado(void) const {
int Tam = static_cast<int>(TotalItems());
for (int i = 0; i < Tam; i++) {
if (ListView_GetItemState(_hWnd, i, LVIS_FOCUSED) == LVIS_FOCUSED) return i;
}
return -1;
}

Para borrar un item utilizamos la macro ListView_DeleteItem especificando la posición del item. Para borrar todos los items utilizamos la macro ListView_DeleteAllItems. Para mirar si un item esta seleccionado hacemos uso de la macro ListView_GetItemState especificando la posición del item, y miramos que devuelva LVIS_SELECTED. Y por ultimo para saber que item está marcado recorremos toda la lista con un bucle, y mediante la macro ListView_GetItemState miramos cual retorna LVIS_FOCUSED.

Puede haber varios items seleccionados, pero como mucho solo tendrá uno marcado. El item marcado es aquel que tiene el foco del teclado, y no tiene porque estar seleccionado.

Con esto ya hemos visto el ObjetoListView bastante a fondo, ahora veamos como podemos trabajar con el. Para empezar vamos a asignarle el estilo extendido LVS_EX_FULLROWSELECT, para que al seleccionar un item del listview se extienda esa selección a todos sus subitems. Además también enlazaremos el ListView con un ImageList para utilizar sus iconos.

Veamos la función Crear de MiVentana :

Archivo : 3.7 Tutorial crear ObjetoListView.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
void MiVentana::Crear(void) {
// Creamos un Imagelist de 16 * 16, para 3 iconos
ImageList = ImageList_Create(16, 16, ILC_COLORDDB | ILC_MASK, 3, 3);
// Creamos los iconos en memoria y los agregamos al ImageList
Icon1 = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON1));
ImageList_AddIcon(ImageList, Icon1);
Icon2 = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON2));
ImageList_AddIcon(ImageList, Icon2);
Icon3 = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON3));
ImageList_AddIcon(ImageList, Icon3);
// Creamos la ventana
CrearVentana(NULL, TEXT("MiVentana"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, TEXT("Ejemplo tutorial 3.7"), 100, 100, 350, 180, NULL );
ListView.CrearListView(_hWnd, WS_CHILD | WS_VISIBLE | WS_BORDER | LVS_REPORT, ID_LISTVIEW, 10, 10, 315, 120);
// Añadimos el estilo LVS_EX_FULLROWSELECT
ListView_SetExtendedListViewStyle(ListView.hWnd(), LVS_EX_FULLROWSELECT);
// Enlazamos el listview con el imagelist para iconos pequeños
ListView_SetImageList(ListView.hWnd(), ImageList, LVSIL_SMALL);
// Agregamos 3 columnas
ListView.AgregarColumna(TEXT("Columna3"), 100);
ListView.AgregarColumna(TEXT("Columna2"), 100);
ListView.AgregarColumna(TEXT("Columna1"), 95);
// Agregamos 20 items
TCHAR TextoItem[256];
UINT Icono = 0;
for (size_t i = 0; i < 20; i++) {
if (Icono == 3) Icono = 0;
swprintf(TextoItem, 256, TEXT("Item%d"), i);
ListView.AgregarItem(Icono, TextoItem, TEXT("SubItem1"), TEXT("SubItem2"));
Icono ++;
}
};

El imagelist para que os hagáis una idea es una lista de iconos cargada en memoria que el listview utilizara para pintar los iconos de sus items. Además los imagelist pueden ser compartidos por varios controles a la vez, con lo que conseguimos no tener que cargar iconos independientemente para cada control, lo que nos ahorra código y memoria.

Para empezar creamos un ImageList con la macro ImageList_Create de 16*16 con capacidad para 3 iconos. En segundo lugar cargamos en memoria los 3 iconos de los recursos con la API LoadIcon, y luego los añadimos al ImageList con la macro ImageList_AddIcon. En tercer lugar llamamos a la funcion CrearVentana para crear la ventana principal, y luego creamos el Listview. En cuarto lugar añadimos el estilo LVS_EX_FULLROWSELECT utilizando la macro ListView_SetExtendedListViewStyle, y luego asignamos el imagelist al ListView con la macro ListView_SetImageList. Y por ultimo creamos 3 columnas y 20 items.

Hasta aquí tenemos hecho un listview con 3 columnas que tendrá 20 items, y que mostrara un icono por item.

Como los demás controles estándar que se han mostrado anteriormente el ListView tambien envía notificaciones para ciertos eventos, pero en vez de enviarlas por el mensaje WM_COMMAND, las manda por el mensaje WM_NOTIFY. Veamos como captaríamos cuando el usuario hace un dobleclick encima de un item :

Archivo : 3.7 Tutorial crear ObjetoListView.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
LRESULT MiVentana::Evento_Notificacion(const UINT cID, LPNMHDR Datos) {
// Si la ID es la del listview y el codigo se refiere a un DobleClick
if (cID == ID_LISTVIEW && Datos->code == NM_DBLCLK) {
// Obtenemos el item marcado
int ItemMarcado = ListView.ItemMarcado();
// Si el item marcado no es -1 mostramos un mensaje con el texto.
if (ItemMarcado != -1) {
TCHAR TextoItem[512];
ListView.ObtenerTexto(ItemMarcado, 0, TextoItem, 512);
MessageBox(NULL, TextoItem, TEXT("DobleClick"), MB_OK);
}
}
return 0;
}

En nuestro caso como tenemos el ListView encapsulado utilizamos la función virtual Evento_Notificacion, en la que básicamente estamos comprobando que la ID del control sea la del ListView, y que el código de la notificación sea NM_DBLCLK. En caso de que la comprobación sea positiva, obtenemos el item marcado y lo mostramos con la API MessageBox.

Para mas información referente al control ListView consulta el siguiente enlace de la MSDN : ListView Control.

Siguiente tutorial : 3.8 Creación del ObjetoTreeView.