Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version
Go to top

How to write a simple but effective TCP/IP port scanner for Win32

, 27 Oct 2001
An article on how to write a TCP/IP port scanner with a GUI, based on the MFC's property sheet paradigm
/*
	CListCtrlEx.cpp
	Classe derivata per la gestione di CListCtrl con menu a comparsa (MFC).
	Luca Piergentili, 06/07/98
	lpiergentili@yahoo.com
	http://www.geocities.com/lpiergentili/
*/
#include "env.h"
#include "pragma.h"
#include "window.h"
#include <commctrl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "strcpyn.h"
#include "win32api.h"
#include "CNodeList.h"
#include "CListCtrlEx.h"

static BOOL		bAscending = FALSE;				// flag per direzione ordinamento righe
static char		cType = 'C';					// tipo dati per ordinamento righe
static int CALLBACK	CompareItems(LPARAM,LPARAM,LPARAM);// confronta i due elementi (ordinamento)
static LPCSTR		GetItemToken(int,LPCSTR);		// estrae dalla riga il campo relativo alla colonna

BEGIN_MESSAGE_MAP(CListCtrlEx,CListCtrl)
	ON_WM_LBUTTONDOWN()							// click bottone sinistro
	ON_WM_LBUTTONUP()							// rilascio bottone sinistro
	ON_WM_RBUTTONDOWN()							// click bottone destro
	ON_WM_RBUTTONUP()							// rilascio bottone destro
	ON_WM_LBUTTONDBLCLK()						// doppio click bottone sinistro
	ON_WM_RBUTTONDBLCLK()						// doppio click bottone destro
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK,OnColumnClick)	// click sull'header della colonna
END_MESSAGE_MAP()

/*
	CListCtrlEx()
*/
CListCtrlEx::CListCtrlEx()
{
	Initialize();
}

/*
	~CListCtrlEx()
*/
CListCtrlEx::~CListCtrlEx()
{
	Destroy();
}

/*
	Initialize()
*/
void CListCtrlEx::Initialize(void)
{
	m_nRedrawCount          = 0;
	m_nCurrentItem          = LB_ERR;
	m_nTotColumns           = 0;
	/*
	#define ILC_MASK                0x0001
	#define ILC_COLOR               0x0000
	#define ILC_COLORDDB            0x00FE
	#define ILC_COLOR4              0x0004
	#define ILC_COLOR8              0x0008
	#define ILC_COLOR16             0x0010
	#define ILC_COLOR24             0x0018
	#define ILC_COLOR32             0x0020
	#define ILC_PALETTE             0x0800      // (not implemented)
	*/
	m_IlcColor              = ILC_MASK;
	m_pWnd                  = NULL;
	m_pWinApp               = NULL;
	m_Point.x               = 0;
	m_Point.y               = 0;
	m_nMenuMessage          = (UINT)-1;
	m_nLButtonDownMessage   = (UINT)-1;
	m_nLButtonUpMessage     = (UINT)-1;
	m_nRButtonDownMessage   = (UINT)-1;
	m_nRButtonUpMessage     = (UINT)-1;
	m_nLButtonDblClkMessage = (UINT)-1;
	m_nRButtonDblClkMessage = (UINT)-1;
	m_bRightClickSelects    = FALSE;
	m_NullRow.ico           = -1;
	m_NullRow.item = new char[8];
	if(m_NullRow.item)
		strcpy(m_NullRow.item,"[null]");
	m_NullRow.menu          = -1;
	m_NullRow.selected      = FALSE;
	m_NullRow.index         = -1;
	m_bMultipleSelection    = FALSE;
}

/*
	Destroy()
*/
void CListCtrlEx::Destroy(void)
{
	m_nCurrentItem          = LB_ERR;
	m_RowList.DeleteAll();
	m_ColList.DeleteAll();
	m_ImageList.DeleteImageList();
	m_ImageListSmall.DeleteImageList();
	m_nTotColumns           = 0;
	m_pWnd                  = NULL;
	m_pWinApp               = NULL;
	m_Point.x               = 0;
	m_Point.y               = 0;
	m_nMenuMessage          = (UINT)-1;
	m_nLButtonDownMessage   = (UINT)-1;
	m_nLButtonUpMessage     = (UINT)-1;
	m_nRButtonDownMessage   = (UINT)-1;
	m_nRButtonUpMessage     = (UINT)-1;
	m_nLButtonDblClkMessage = (UINT)-1;
	m_nRButtonDblClkMessage = (UINT)-1;
	m_bRightClickSelects    = FALSE;
	m_NullRow.ico           = -1;
	if(m_NullRow.item)
	{
		delete [] m_NullRow.item;
		m_NullRow.item = NULL;
	}
	m_NullRow.menu          = -1;
	m_NullRow.selected      = FALSE;
	m_NullRow.index         = -1;
}

/*
	Create()

	Crea il controllo.
	
	CWnd* pCWnd = AfxGetMainWnd()		puntatore alla finestra principale per l'invio dei messaggi
	CWinApp* pCWinApp = AfxGetApp()	puntatore all'applicazione principale gli id delle icone
*/
BOOL CListCtrlEx::Create(CWnd* pCWnd,CWinApp* pCWinApp)
{
	// inizializza
	Destroy();
	Initialize();

	// puntatore alla finestra/applicazione principale
	m_pWnd = pCWnd;
	m_pWinApp = pCWinApp;

	// elimina le colonne eventualmente presenti
	while(CListCtrl::DeleteColumn(0))
		;

	// elimina le righe eventualmente presenti
	CListCtrl::DeleteAllItems();

	// liste immagini
	BOOL bCreated = TRUE;
	if(bCreated)
		bCreated = m_ImageList.Create(32,32,m_IlcColor/*ILC_COLOR32*/ /*1*/,0,5);
	if(bCreated)
		bCreated = m_ImageListSmall.Create(16,16,m_IlcColor/*ILC_COLOR32*/ /*1*/,0,5);

	return(bCreated);
}

/*
	SetRedraw()
*/
void CListCtrlEx::SetRedraw(BOOL bRedraw)
{
	if(!bRedraw)
	{
		if(m_nRedrawCount++ <= 0)
			CListCtrl::SetRedraw(false);
	}
	else
	{
		if(--m_nRedrawCount <= 0)
		{
			CListCtrl::SetRedraw(true);
			m_nRedrawCount = 0;
			CListCtrl::Invalidate();
		}
	}
}

/*
	AutoSizeColumns()

	Dimensiona automaticamente le colonne in base al contenuto/etichetta.
	Da chiamare dopo aver riempito il controllo.l
	Da usare solo se il controllo si trova in modalita' report.
*/
void CListCtrlEx::AutoSizeColumns(int nCol/*=-1*/,int nWidth/*=0*/) 
{
	SetRedraw(FALSE);

	if(!(GetStyle() & LVS_REPORT))
		return;

	int nMinCol = nCol < 0 ? 0 : nCol;
	int nMaxCol = nCol < 0 ? GetColumnCount()-1 : nCol;
	
	for(nCol = nMinCol; nCol <= nMaxCol; nCol++) 
	{
		if(nWidth==0)
		{
			CListCtrl::SetColumnWidth(nCol,LVSCW_AUTOSIZE);
			int nWidthAutosize = CListCtrl::GetColumnWidth(nCol);

			CListCtrl::SetColumnWidth(nCol,LVSCW_AUTOSIZE_USEHEADER);
			int nWidthUseHeader = CListCtrl::GetColumnWidth(nCol);

			CListCtrl::SetColumnWidth(nCol,max(MINCOLWIDTH,max(nWidthAutosize,nWidthUseHeader)));
		}
		else
			CListCtrl::SetColumnWidth(nCol,nWidth);     
	}

	SetRedraw(TRUE);
}

/*
	GetColumnCount()

	Ricava il numero di colonne del controllo.
*/
int CListCtrlEx::GetColumnCount(void)
{
	CHeaderCtrl* pHeader = (CHeaderCtrl*)CListCtrl::GetDlgItem(0);
	return(pHeader ? pHeader->GetItemCount() : 0);
}

/*
	GetRowByItem()

	Ricava il puntatore alla riga relativa all'elemento.
	L'elemento (indice base 0 dell'elemento di CListCtrl) corrisponde con l'indice (base 0) della riga,
	da mantenere aggiornato in base agli spostamenti degli items di ClistCtrl.
	Da non confondere con l'indice interno di CNodeList.

	int nItem		indice (base 0) dell'item per la riga da cercare
*/
CTRL_ROW* CListCtrlEx::GetRowByItem(int nItem)
{
	ITERATOR iter;
	CTRL_ROW* ra = NULL;

	if((iter = m_RowList.First())!=(ITERATOR)NULL)
		while(iter!=(ITERATOR)NULL)
		{
			ra = (CTRL_ROW*)iter->data;
			if(ra)
			{
				if(ra->index==nItem)
					break;
				else
					ra = NULL;
			}
			iter = m_RowList.Next(iter);
		}

	return(ra);
}

/*
	GetColByItem()

	Ricava il puntatore alla colonna relativa all'elemento.
	L'elemento (indice base 0 dell'elemento di CListCtrl) corrisponde con l'indice (base 0) della riga.
	Dato che le colonne non cambiano di posizione, tale indice corrisponde con l'indice interno di
	CNodeList, per cui la ricerca puo essere effettuata con FindAt().

	int nItem		indice (base 0) dell'item per la colonna da cercare
*/
CTRL_COL* CListCtrlEx::GetColByItem(int nItem)
{
	ITERATOR iter;
	CTRL_COL* ca = NULL;

	if((iter = m_ColList.FindAt(nItem))!=NULL)
		ca = (CTRL_COL*)iter->data;

	return(ca);
}

/*
	AddCol()

	Aggiunge la colonna al controllo.

	LPCSTR lpcszColumnText	testo per l'intestazione della colonna
	char cDataType	= 'C'	carattere per il tipo dei dati contenuti nella colonna: 'C' carattere, 'N' numerico
	int nWidth = -1		dimensione della colonna (in pixel)
	BOOL bDescend = FALSE	flag per ordinamento (FALSE=discendente, TRUE=ascendente)
*/
int CListCtrlEx::AddCol(LPCSTR lpcszColumnText,char cDataType,int nWidth,BOOL bDescend)
{
	int nCurrentCol = LB_ERR;

	// crea la colonna
	CTRL_COL* ca = new CTRL_COL;

	if(ca)
	{
		memset(ca,'\0',sizeof(CTRL_COL));
		int len = strlen(lpcszColumnText)+1;
		ca->item = new char[len];
		memset(ca->item,'\0',len);
		
		// aggiunge la colonna alla lista
		if(m_ColList.Add(ca))
		{
			CSize size;
			LPSTR lpToken;
			LV_COLUMN lvcolumn;

			// indice (base 0) della nuova colonna
			int nCol = m_nTotColumns++;

			strcpy(ca->item,lpcszColumnText);

			// ordinamento colonna (default FALSE = discendente)
			ca->type  = cDataType;
			ca->order = bDescend;
			
			// elemento
			lvcolumn.mask     = LVCF_FMT | LVCF_SUBITEM | LVCF_TEXT | LVCF_WIDTH;
			lvcolumn.fmt      = LVCFMT_LEFT;
			lvcolumn.pszText  = ca->item;
			lvcolumn.iSubItem = nCol;
			
			// calcola la dimensione (in pixel) della colonna in base alla dimensione del testo
			// della colonna e del testo della prima riga (+15 per l'icona)
			if(nWidth <= 0)
			{
				lpToken = "";

				if(CListCtrl::GetItemCount() > 0)
				{
					CTRL_ROW* ra;
					if((ra = GetRowByItem(0))!=NULL)
						lpToken = (LPSTR)GetItemToken(nCol,ra->item);
				}
				else
				{
					lpToken = ca->item;
				}

				int nColSize  = CListCtrl::GetStringWidth(lvcolumn.pszText);
				int nItemSize = CListCtrl::GetStringWidth(lpToken);
				size.cx       = nColSize > nItemSize ? nColSize : nItemSize;
				lvcolumn.cx   = size.cx + 15;
			}
			else
			{
				lvcolumn.cx = nWidth;
			}
			
			// inserisce la colonna
			if((nCurrentCol = CListCtrl::InsertColumn(nCol,&lvcolumn)) < 0)
				nCurrentCol = LB_ERR;
		}
	}

	return(nCurrentCol);
}

/*
	AddItem()

	Aggiunge la riga al controllo.

	LPCSTR lpcszItemText	testo per l'elemento della riga (separare il testo per le colonne con ';')
	int nIconIndex = 0		indice dell'icona relativa
	int nMenuIndex = -1		indice del menu relativo
*/
int CListCtrlEx::AddItem(LPCSTR lpcszItemText,int nIconIndex,int nMenuIndex) 
{
	int nCurrentItem = LB_ERR;

	// crea la riga
	CTRL_ROW* ra = new CTRL_ROW;
	
	if(ra)
	{
		memset(ra,'\0',sizeof(CTRL_ROW));
		int len = strlen(lpcszItemText)+1;
		ra->item = new char[len];
		memset(ra->item,'\0',len);
		strcpy(ra->item,lpcszItemText);

		// aggiunge la riga alla lista
		if(m_RowList.Add(ra))
		{
			int nItem,nSubItem;
			LV_ITEM lvitem;

			// indice (base 0) della nuova riga
			nItem = CListCtrl::GetItemCount();

			// colonne della riga
			for(nSubItem = 0,nCurrentItem = 0; nSubItem < m_nTotColumns && nCurrentItem!=LB_ERR; nSubItem++)
			{
				// status della riga (default su non selezionata)
				ra->ico      = nIconIndex;
				ra->menu     = nMenuIndex;
				ra->selected = FALSE;
				ra->index    = nItem;

				// la riga viene passata in un unico buffer, diviso in colonne dal carattere ';'
				lvitem.pszText = (LPSTR)GetItemToken(nSubItem,ra->item);
				
				int nItemSize = CListCtrl::GetStringWidth(lvitem.pszText);
				//int nColSize = CListCtrl::GetColumnWidth(nSubItem);
				if(nItemSize >= CListCtrl::GetColumnWidth(nSubItem))
					CListCtrl::SetColumnWidth(nSubItem,nItemSize+15);

				lvitem.iSubItem = nSubItem;

				if(nSubItem==0)
				{
					lvitem.mask   = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
					lvitem.iItem  = nItem;
					lvitem.iImage = ra->ico; // icona relativa (indice base 0 nell'array delle immagini)
					lvitem.lParam = (LPARAM)(ra->item);
					
					if((nCurrentItem  = CListCtrl::InsertItem(&lvitem)) < 0)
						nCurrentItem = LB_ERR;
				}
				else
				{
					lvitem.mask  = LVIF_TEXT;
					lvitem.iItem = nCurrentItem;
					
					CListCtrl::SetItem(&lvitem);
				}
			}
		}
	}

	return(nCurrentItem);
}

/*
	UpdateItem()

	Aggiorna l'elemento del controllo.
*/
BOOL CListCtrlEx::UpdateItem(int nItem,LPCSTR lpcszItemText,int nIconIndex/*=0*/,int nMenuIndex/*=-1*/)
{
	BOOL bUpdated = FALSE;
	ITERATOR iter;
	CTRL_ROW* ra = NULL;

	// cerca la riga relativa all'indice
	if((iter = m_RowList.First())!=(ITERATOR)NULL)
	{
		while(iter!=(ITERATOR)NULL)
		{
			ra = (CTRL_ROW*)iter->data;
			if(ra)
			{
				if(ra->index==nItem)
					break;
				else
					ra = NULL;
			}
			iter = m_RowList.Next(iter);
		}
	}

	if(ra)
	{
		int nCurrent = GetCurrentItem();
		
		CListCtrl::DeleteItem(nItem);

		if(ra->item)
		{
			delete [] ra->item;
			ra->item = NULL;
		}
		int len = strlen(lpcszItemText)+1;
		ra->item = new char[len];
		memset(ra->item,'\0',len);
		strcpy(ra->item,lpcszItemText);

		int nSubItem,nCurrentItem;
		LV_ITEM lvitem;

		// colonne della riga
		for(nSubItem = 0,nCurrentItem = 0; nSubItem < m_nTotColumns && nCurrentItem!=LB_ERR; nSubItem++)
		{
			// status della riga (default su non selezionata)
			ra->ico      = nIconIndex;
			ra->menu     = nMenuIndex;
			ra->selected = FALSE;
			ra->index    = nItem;

			// la riga viene passata in un unico buffer, diviso in colonne dal carattere ';'
			lvitem.pszText = (LPSTR)GetItemToken(nSubItem,ra->item);
			
			int nItemSize = CListCtrl::GetStringWidth(lvitem.pszText);
			//int nColSize = CListCtrl::GetColumnWidth(nSubItem);
			if(nItemSize >= CListCtrl::GetColumnWidth(nSubItem))
				CListCtrl::SetColumnWidth(nSubItem,nItemSize+15);

			lvitem.iSubItem = nSubItem;

			if(nSubItem==0)
			{
				lvitem.mask   = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
				lvitem.iItem  = nItem;
				lvitem.iImage = ra->ico; // icona relativa (indice base 0 nell'array delle immagini)
				lvitem.lParam = (LPARAM)(ra->item);
				
				if((nCurrentItem  = CListCtrl::InsertItem(&lvitem)) < 0)
					nCurrentItem = LB_ERR;
			}
			else
			{
				lvitem.mask  = LVIF_TEXT;
				lvitem.iItem = nCurrentItem;
				
				CListCtrl::SetItem(&lvitem);
			}
		}
		
		SelectItem(nCurrent);
		CListCtrl::Update(nItem);
		
		bUpdated = TRUE;
	}

	return(bUpdated);
}

/*
	DeleteItem()

	Elimina l'elemento dal controllo.

	int nItem		indice (base 0) della riga.
*/
BOOL CListCtrlEx::DeleteItem(int nItem)
{
	ITERATOR iter;
	CTRL_ROW* ra = NULL;

	// elimina la riga dal controllo
	BOOL bDeleted = CListCtrl::DeleteItem(nItem);

	// cerca la riga relativa all'indice e la elimina dalla lista
	if((iter = m_RowList.First())!=(ITERATOR)NULL)
	{
		while(iter!=(ITERATOR)NULL)
		{
			ra = (CTRL_ROW*)iter->data;
			if(ra)
			{
				if(ra->index==nItem)
					break;
				else
					ra = NULL;
			}
			iter = m_RowList.Next(iter);
		}
	}
	
	if(ra)
		m_RowList.Delete(iter);

	// rinumera gli indici delle righe presenti nella lista
	int tot = m_RowList.Count()+1;

	for(int n = ++nItem; n < tot; n++)
	{
		if((ra = GetRowByItem(n))!=(CTRL_ROW*)NULL)
			ra->index = n-1;
	}

	return(bDeleted);
}

/*
	DeleteAllItems()

	Elimina tutti gli elementi dal controllo.
*/
BOOL CListCtrlEx::DeleteAllItems(void)
{
	// elimina le righe dal controllo
	BOOL bDeleted = CListCtrl::DeleteAllItems();

	// elimina le righe dalla lista
	m_RowList.DeleteAll();

	return(bDeleted);
}

/*
	FindItem()

	Cerca l'elemento nel controllo.

	LPCSTR lpcszText	testo dell'elemento da cercare.
*/
int CListCtrlEx::FindItem(LPCSTR lpcszText)
{
	LVFINDINFO lv = {0};
	lv.flags = LVFI_STRING;
	lv.psz = lpcszText;
	return(CListCtrl::FindItem(&lv,-1));
}

/*
	FindItem()

	Cerca la coppia di elementi nel controllo.

	LPCSTR lpcszItemText	testo dell'elemento da cercare.
	LPCSTR lpcszColumnText	testo addizionale dell'elemento da cercare.
	UINT nCol				indice (base 0) della colonna per il testo addizionale
*/
int CListCtrlEx::FindItem(LPCSTR lpcszItemText,LPCSTR lpcszColumnText,UINT nCol)
{
	char szItem[MAX_CTRLITEM_SIZE+1] = {0};
	LVFINDINFO lv = {0};
	lv.flags = LVFI_STRING;
	lv.psz = lpcszItemText;
	int nItem = CListCtrl::FindItem(&lv,-1);
	while(nItem >= 0)
	{
		CListCtrl::GetItemText(nItem,nCol,szItem,sizeof(szItem)-1);
		if(strcmp(szItem,lpcszColumnText)==0)
			break;
		else
			nItem = CListCtrl::FindItem(&lv,nItem);
	}
	return(nItem);
}

/*
	MoveItem()

	Sposta l'elemento in alto o in basso di una posizione.

	int nItem			indice (base 0) dell'elemento
	ITEM_SKIP skip		flag per tipo spostamento (precedente/successivo)
*/
int CListCtrlEx::MoveItem(int nItem,ITEM_SKIP skip)
{
	int nCurrentItem = LB_ERR;
	int nNewItem = (nItem + (skip==PREVIOUS_ITEM ? -1 : (skip==NEXT_ITEM ? +1 : -1)));

	// controlla l'intervallo
	if(nNewItem >= 0 && nNewItem < CListCtrl::GetItemCount())
	{
		// riga da spostare
		CTRL_ROW* ra = GetRowByItem(nItem);
		
		// riga precedente/successiva
		CTRL_ROW* ra_prevnext = GetRowByItem(nNewItem);
		
		if(ra && ra_prevnext)
		{
			int nSubItem;
			LV_ITEM lvitem;
			char szItem[MAX_CTRLROW_SIZE+1];

			// elimina l'elemento dal controllo (chiama la classe base perche' non deve aggiornare il contatore interno)
			CListCtrl::DeleteItem(nItem);

			// testo dell'elemento
			strcpyn(szItem,ra->item,sizeof(szItem));
			
			// rinumera gli indici dei due elementi coinvolti nello spostamento
			ra->index = nNewItem;
			ra_prevnext->index = nItem;

			// colonne dell'elemento
			for(nSubItem = 0,nCurrentItem = 0; nSubItem < m_nTotColumns && nCurrentItem!=LB_ERR; nSubItem++)
			{
				// l'elemento (riga) viene passata in un unico buffer, diviso in colonne dal carattere ';'
				lvitem.pszText = (LPSTR)GetItemToken(nSubItem,szItem);
				
				lvitem.iSubItem = nSubItem;

				if(nSubItem==0)
				{
					lvitem.mask   = LVIF_TEXT | LVIF_PARAM | LVIF_IMAGE;
					lvitem.iItem  = nNewItem;
					lvitem.iImage = ra->ico;
					lvitem.lParam = (LPARAM)(ra->item);
					
					if((nCurrentItem  = CListCtrl::InsertItem(&lvitem)) < 0)
						nCurrentItem = LB_ERR;
				}
				else
				{
					lvitem.mask  = LVIF_TEXT;
					lvitem.iItem = nCurrentItem;
					
					CListCtrl::SetItem(&lvitem);
				}
			}

			// imposta la riga come selezionata
			nCurrentItem = SelectItem(nNewItem);
		}
	}

	return(nCurrentItem);
}

/*
	AddIcon()

	Aggiunge l'icona alla lista delle immagini per la lista.

	int nIcon		id della risorsa relativo all'icona
*/
int CListCtrlEx::AddIcon(int nIcon)
{
	int nCurrentIcon = LB_ERR;

	if(!m_pWinApp)
		return(nCurrentIcon);

	if((nCurrentIcon = m_ImageList.Add(m_pWinApp->LoadIcon(nIcon))) < 0)
		return(nCurrentIcon);
	CListCtrl::SetImageList(&m_ImageList,LVSIL_NORMAL);
	
	if((nCurrentIcon = m_ImageListSmall.Add(m_pWinApp->LoadIcon(nIcon))) < 0)
		return(nCurrentIcon);
	CListCtrl::SetImageList(&m_ImageListSmall,LVSIL_SMALL);

	return(nCurrentIcon);
}

/*
	AddIcon()

	Aggiunge l'icona alla lista delle immagini per la lista.

	HICON hIcon	handle relativo all'icona
*/
int CListCtrlEx::AddIcon(HICON hIcon)
{
	int nCurrentIcon = LB_ERR;

	if((nCurrentIcon = m_ImageList.Add(hIcon)) < 0)
		return(nCurrentIcon);
	CListCtrl::SetImageList(&m_ImageList,LVSIL_NORMAL);
	
	if((nCurrentIcon = m_ImageListSmall.Add(hIcon)) < 0)
		return(nCurrentIcon);
	CListCtrl::SetImageList(&m_ImageListSmall,LVSIL_SMALL);

	return(nCurrentIcon);
}

/*
	RemoveIcon()
*/
void CListCtrlEx::RemoveIcon(int nIcon)
{
	m_ImageList.Remove(nIcon);
	m_ImageListSmall.Remove(nIcon);
}

/*
	RemoveAllIcons()
*/
void CListCtrlEx::RemoveAllIcons(void)
{
	int i,nTot;
	for(i = 0,nTot = m_ImageList.GetImageCount(); i < nTot; i++)
		m_ImageList.Remove(0);
	for(i = 0,nTot = m_ImageListSmall.GetImageCount(); i < nTot; i++)
		m_ImageListSmall.Remove(0);
}

/*
	SelectItem()

	Imposta l'elemento come selezionato.
	Se la selezione riesce, imposta l'indice interno relativo alla riga corrente.

	int nItem		indice (base 0) dell'elemento
*/
int CListCtrlEx::SelectItem(int nItem)
{
	// riga corrente
	int iCurrentItem = GetCurrentItem();

	// controlla l'indice
	if(nItem >=0 && nItem < CListCtrl::GetItemCount())
	{
		/* M$VC 6.0
		POSITION position;
		position = GetFirstSelectedItemPosition();
		while(position)
			iCurrentItem = GetNextSelectedItem(position);
		*/

		LV_ITEM lvitem;

		// ricava la riga selezionata
		if((iCurrentItem = CListCtrl::GetNextItem(0,LVNI_ALL|LVNI_FOCUSED|LVNI_SELECTED)) < 0)
			iCurrentItem = 0;

		if(iCurrentItem >= 0)
		{
			// elimina la selezione per la riga corrente
			memset(&lvitem,'\0',sizeof(LV_ITEM));
			lvitem.mask = LVIF_STATE;
			lvitem.iItem = iCurrentItem;
			lvitem.iSubItem = 0;
			lvitem.state = 0;
			lvitem.stateMask = LVIS_FOCUSED | LVIS_SELECTED ;
			if(CListCtrl::SetItemState(iCurrentItem,&lvitem))
			{
				// imposta come selezionata la riga specificata
				memset(&lvitem,'\0',sizeof(LV_ITEM));
				lvitem.mask = LVIF_STATE;
				lvitem.iItem = nItem;
				lvitem.iSubItem = 0;
				lvitem.state = LVIS_FOCUSED | LVIS_SELECTED;
				lvitem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
				if(CListCtrl::SetItemState(nItem,&lvitem))
				{
					// si assicura che la riga sia visibile ed aggiorna l'indice interno
					SetRedraw(FALSE);
					CListCtrl::EnsureVisible(nItem,FALSE);
					SetRedraw(TRUE);

					SetCurrentItem(nItem);
				}
			}
		}
	}

	return(iCurrentItem);
}

/*
	SetCurrentItem()

	Aggiorna l'indice interno relativo alla riga corrente.
	Notare che la riga corrente puo' non corrispondere con quella selezionata (focused).

	int nItem		indice (base 0) dell'elemento
*/
int CListCtrlEx::SetCurrentItem(int nItem)
{
	if(CListCtrl::GetItemCount() <= 0)
		return(LB_ERR);

	if(nItem < 0 || nItem > CListCtrl::GetItemCount())
		nItem = 0;

	m_nCurrentItem = nItem;

	return(m_nCurrentItem);
}

/*
	GetCurrentItem()

	Restituisce l'indice interno relativo alla riga corrente.
	Notare che la riga corrente puo' non corrispondere con quella selezionata (focused).
*/
int CListCtrlEx::GetCurrentItem(void)
{
	if(CListCtrl::GetItemCount() <= 0)
		return(LB_ERR);

	if(m_nCurrentItem==LB_ERR)
		m_nCurrentItem = 0;

	return(m_nCurrentItem);
}

/*
	SetItemSelection()

	Imposta lo status (interno) dell'elemento (selezionato o meno), restituendo l'anteriore.

	int nItem			indice (base 0) dell'elemento
	BOOL bSelected		flag per selezione elemento
*/
BOOL CListCtrlEx::SetItemSelection(int nItem,BOOL bSelected)
{
	BOOL bIsSelected = FALSE;
	CTRL_ROW* ra = GetRowByItem(nItem);
	
	if(ra)
	{
		bIsSelected = ra->selected;
		ra->selected = bSelected;
	}

	return(bIsSelected);
}

/*
	GetItemSelection()

	Restituisce lo status (interno) dell'elemento (selezionata o meno).

	int nItem			indice (base 0) dell'elemento
*/
BOOL CListCtrlEx::GetItemSelection(int nItem)
{
	BOOL bIsSelected = FALSE;
	CTRL_ROW* ra = GetRowByItem(nItem);
	
	if(ra)
		bIsSelected = ra->selected;

	return(bIsSelected);
}

/*
	ResetItemsSelection()

	Azzera lo status (interno) degli elementi (imposta su non selezionati).
*/
void CListCtrlEx::ResetItemsSelection(void)
{
	CTRL_ROW* ra;

	for(int i = 0; i < CListCtrl::GetItemCount(); i++)
		if((ra = GetRowByItem(i))!=(CTRL_ROW*)NULL)
			ra->selected = FALSE;
}

/*
	CompareItems()

	Confronta i due elementi per l'ordinamento del controllo.

	LPARAM lParam1		puntatore al testo del primo elemento
	LPARAM lParam2		puntatore al testo del secondo elemento
	LPARAM lCol		indice (base 0) della colonna
*/
int CALLBACK CompareItems(LPARAM lParam1,LPARAM lParam2,LPARAM lCol)
{
	int nCmp = 0;
	int n1,n2;
	static char s1[MAX_CTRLITEM_SIZE + 1],s2[MAX_CTRLITEM_SIZE + 1];
	static int n = 0;

	strcpyn(s1,GetItemToken(lCol,(LPCSTR)lParam1),sizeof(s1));
	strcpyn(s2,GetItemToken(lCol,(LPCSTR)lParam2),sizeof(s2));

	if(bAscending)
	{
		if(cType=='N')
		{
			n1 = atoi(s1);
			n2 = atoi(s2);
			
			if(n1 < n2)
				nCmp = -1;
			else if(n1 > n2)
				nCmp = 1;
		}
		else if(cType=='C')
			nCmp = strcmp(s1,s2);
	}
	else
	{
		if(cType=='N')
		{
			n1 = atoi(s1);
			n2 = atoi(s2);
			
			if(n2 < n1)
				nCmp = -1;
			else if(n2 > n1)
				nCmp = 1;
		}
		else if(cType=='C')
			nCmp = strcmp(s2,s1);
	}

	if(++n > 5)
	{
		n = 0;
		::PeekAndPump();
	}

	return(nCmp);
}

/*
	GetItemToken()

	Estrae dalla riga l'elemento relativo alla colonna.
	Gli elementi, all'interno della riga, devono essere separati dal carattere ';'.

	int nItem			indice (base 0) dell'elemento
	LPCSTR lpcszRow	riga contenente gli elementi separati dal carattere ';'
*/
LPCSTR GetItemToken(int nItem,LPCSTR lpcszRow)
{
	int i;
	LPSTR lpToken;
	static char szItem[MAX_CTRLROW_SIZE + 1];

	strcpy(szItem,"[null]");

	if(lpcszRow && *lpcszRow)
	{
		strcpyn(szItem,lpcszRow,sizeof(szItem));

		for(i = 0,lpToken = strtok(szItem,";"); lpToken!=NULL; i++)
		{
			if(i==nItem)
			{
				strcpyn(szItem,lpToken,sizeof(szItem));
				break;
			}

			lpToken = strtok(NULL,";");
		}
	}

	return(szItem);
}

/*
	PopUpMenu()

	Visualizza il menu popup sul controllo.
	Il menu visualizzato e' il primo sottomenu (base 0) del menu relativo all'id nel caso in cui venga
	fatto click su un elemento o il secondo se viene fatto click in una zona del controllo senza elementi.

	const CPoint& point		coordinate del click
*/
void CListCtrlEx::PopUpMenu(const CPoint& point)
{
	int i;
	CRect rect;
	CMenu menu;
	CMenu* pSubMenu = NULL;
	CTRL_ROW* ra;

	// coordinate dell'area client (CWnd, base 0,0)
	CWnd::GetClientRect(&rect);
	
	// trasforma le coordinate in assolute (schermo)
	// da aggiungere alle coordinate del click del mouse per ottenere le coordinate a cui visualizzare il menu a comparsa
	CWnd::ClientToScreen(&rect);	
	
	// click su un elemento del controllo
	if((i = CListCtrl::HitTest(point)) >= 0)
	{
		// carica il menu relativo all'id specificato dall'elemento su cui e' stato fatto click
		// (primo sottomenu, base 0)
		if((ra = GetRowByItem(i))!=(CTRL_ROW*)NULL)
			if(ra->menu > 0)
				if(menu.LoadMenu(ra->menu))
					pSubMenu = menu.GetSubMenu(0);
	}
	// click fuori dall'elemento
	else
	{
		// click su una zona del controllo contenente almeno un elemento
		if((ra = GetRowByItem(0))!=(CTRL_ROW*)NULL)
		{
			// carica il menu relativo all'id specificato dal primo elemento della lista
			// (secondo sottomenu, base 0)
			if(ra->menu > 0)
				if(menu.LoadMenu(ra->menu))
					pSubMenu = menu.GetSubMenu(1);
		}
		// click su una zona del controllo senza nessun elemento
		else
		{
			// carica il menu di default
			// (primo sottomenu, base 0)
			if(m_NullRow.menu > 0)
				if(menu.LoadMenu(m_NullRow.menu))
					pSubMenu = menu.GetSubMenu(0);
		}
	}

	// visualizza il menu popup
	if(pSubMenu && m_pWnd)
	{
		if(m_nMenuMessage!=(UINT)-1)
			::SendMessage(m_pWnd->m_hWnd,m_nMenuMessage,(WPARAM)m_nCurrentItem,(LPARAM)pSubMenu);

		pSubMenu->TrackPopupMenu(TPM_LEFTALIGN|TPM_RIGHTBUTTON,point.x+rect.left,point.y+rect.top,m_pWnd);
	}
}

/*
	Sort()
*/
void	CListCtrlEx::Sort(int nCol/* = 0*/)
{
	LRESULT lResult = 0L;
	NM_LISTVIEW nm_listview;
	memset(&nm_listview,'\0',sizeof(NM_LISTVIEW));
	nm_listview.iSubItem = nCol;
	OnColumnClick((NMHDR*)&nm_listview,&lResult);
}

/*
	OnColumnClick()

	Gestore per il click sull'header della colonna.
	Ordina le righe presenti nella colonna.
*/
void CListCtrlEx::OnColumnClick(NMHDR* pNMHDR,LRESULT* pResult) 
{
	// verifica che il controllo contenga almeno un elemento
	if(CListCtrl::GetItemCount() > 0)
	{
		NM_LISTVIEW* pNM_LISTVIEW = (NM_LISTVIEW*)pNMHDR;
		int nCol = pNM_LISTVIEW->iSubItem;
		CTRL_COL* ca = GetColByItem(nCol);

		if(ca)
		{
			// imposta l'ordinamento corrente (ascendente/discendente) in base al precedente
			ca->order = (ca->order==FALSE ? TRUE : FALSE);
			bAscending = ca->order;
			
			// distingue tra dati numerici e carattere
			cType = ca->type;
			if(cType!='C' && cType!='N')
				cType = 'C';
			
			// ordina le righe della colonna
			CListCtrl::SortItems((PFNLVCOMPARE)CompareItems,nCol);

			// rinumera l'indice (interno) delle rige secondo l'ordinamento corrente
			
			// notare che l'ordinamento avviene considerando la prima colonna (base 0) della lista
			// motivo per cui tale colonna non puo' contenere elementi duplicati: in tal caso il
			// confronto (vedi la strcmp() di cui sotto) tra i due elementi selezionerebbe sempre
			// il primo e tanti saluti alla rinumerazione dell'indice interno che se ne andrebbe a
			// puttane
			// il confronto non puo' essere fatto con memcmp() su n caratteri perche' fallirebbe
			// nel caso in cui ci siano elementi che iniziano con gli stessi caratteri (ftp e ftp-wpd)
			// per cui deve estrarre il testo relativo dal buffer interno (testo;testo;testo;etc.) e
			// confrontarlo con quello della riga con strcmp()
			ITERATOR iter;
			CTRL_ROW* ra = NULL;
			LPSTR p;
			char szItem[MAX_CTRLITEM_SIZE+1];
			char buffer[MAX_CTRLITEM_SIZE+1];
			int tot = CListCtrl::GetItemCount();
			register int i,n;

			// per ogni riga
			for(i = 0,n = 0; i < tot; i++)
			{
				// ricava il testo della riga a colonna 0
				if(CListCtrl::GetItemText(i,0,buffer,sizeof(buffer)-1) > 0)
					// cerca l'entrata corrispondente (testo;testo;testo;etc.) nella lista interna
					if((iter = m_RowList.First())!=(ITERATOR)NULL)
						while(iter!=(ITERATOR)NULL)
						{
							// puntatore alla riga
							ra = (CTRL_ROW*)iter->data;
							if(ra)
							{
								// estrae il testo corrispondente alla colonna 0
								if((p = strchr(ra->item,';'))!=NULL)
								{
									int n = p - ra->item;
									memcpy(szItem,ra->item,n);
									szItem[n] = '\0';
									
									if(strcmp(buffer,szItem)==0)
									{
										ra->index = i;
										break;
									}
								}
							}
							iter = m_RowList.Next(iter);
						}

				if(++n > 5)
				{
					n = 0;
					::PeekAndPump();
				}
			}
		}
	}

	*pResult = 0;
}

/*
	OnLButtonDown()

	Gestore per il click con il bottone sinistro del mouse sull'elemento del controllo.
	Imposta la riga come selezionata ed invia il messaggio alla finestra principale (in wParam l'indice
	dell'elemento selezionato ed in lParam le coordinate del click).
	Per catturare il rilascio del bottone sinistro del mouse con OnLButtonUp() anche quando il cursore
	del mouse e' stato spostato fuori dall'area client, utilizzare la SetCapture().
*/
void CListCtrlEx::OnLButtonDown(UINT nFlags,CPoint point)
{
	// salva le coordinate del click
	m_Point.x = point.x;
	m_Point.y = point.y;

	m_MouseClick.point.x = point.x;
	m_MouseClick.point.y = point.y;
	m_MouseClick.flags = nFlags;

	// chiama il gestore originale
	CListCtrl::OnLButtonDown(nFlags,point);

	// per continuare a ricevere i messaggi quando il mouse si sposta fuori dell'area client
	//::SetCapture(this->m_hWnd);
	
	// indice dell'elemento cliccato
	m_nCurrentItem = CListCtrl::HitTest(point);

	// imposta il flag relativo alla selezione
	// se viene cambiato l'ordinamento del controllo l'elemento relativo all'indice non corrisponde a quanto
	// presente nella lista del chiamante, per cui deve cercare l'elemento relativo prima di impostare il
	// flag per la selezione
	// effettua tale ricerca con il primo subitem (indice 0), per cui il valore presente in tale posizione
	// deve essere unico
	int nTotItems = CListCtrl::GetItemCount();

	if(m_nCurrentItem >= 0 && m_nCurrentItem < nTotItems)
	{
		CTRL_ROW* ra = NULL;
		ITERATOR iter;
		register int i;
		char buffer[MAX_CTRLITEM_SIZE+1];

		// azzera le selezioni correnti
		if((iter = m_RowList.First())!=(ITERATOR)NULL)
			while(iter!=(ITERATOR)NULL)
			{
				ra = (CTRL_ROW*)iter->data;
				if(ra)
					ra->selected = FALSE;
				iter = m_RowList.Next(iter);
			}

		// per ogni elemento del controllo verifica se e' stato selezionato, ricava il valore relativo al
		// subitem con indice 0 e lo confronta con l'elemento corrente: se coincidono imposta il flag per
		// la selezione
		// deve scorrere il controllo per gestire le selezioni multiple (click sul primo, shift+click
		// sull'ultimo)
		for(i = 0; i < nTotItems/*CListCtrl::GetItemCount()*/; i++)
		{
			// il cazzo di elemento e' stato cliccato ?
			// (click sull'elemento o elemento nel mezzo di una selezione click su i, shift+click su i+n)
			if(CListCtrl::GetItemState(i,LVIS_SELECTED) & LVIS_SELECTED)
			{
				memset(buffer,'\0',sizeof(buffer));

				LV_ITEM lvitem;
				lvitem.mask = LVIF_TEXT;
				lvitem.iItem = i;
				lvitem.iSubItem = 0;
				lvitem.pszText = buffer;
				lvitem.cchTextMax = sizeof(buffer)-1;
				CListCtrl::GetItem(&lvitem);

				if((ra = GetRowByItem(i))!=(CTRL_ROW*)NULL)
				{
					if(memcmp(ra->item,buffer,strlen(buffer))==0)
					{
						ra->selected = TRUE;
						break;
					}
				}

				if(!m_bMultipleSelection)
					break;
			}
		}
	}

	// salva l'indice (base 0) dell'elemento corrente (selezionato), controllando l'intervallo
	if(m_nCurrentItem < 0)
		m_nCurrentItem = LB_ERR;
	else if(m_nCurrentItem >= nTotItems/*CListCtrl::GetItemCount()*/)
		m_nCurrentItem = nTotItems/*CListCtrl::GetItemCount()*/ - 1;

	// se e' stato specificato un gestore, invia il messaggio relativo
	// in wParam l'indice dell'elemento ed in lParam le coordinate del click
	if(m_pWnd!=NULL && m_nLButtonDownMessage!=(UINT)-1)
		::SendMessage(m_pWnd->m_hWnd,m_nLButtonDownMessage,(WPARAM)m_nCurrentItem,(LPARAM)&m_MouseClick);
}

/*
	OnLButtonUp()

	Gestore per il rilascio del bottone sinistro del mouse.
	Invia il messaggio alla finestra principale (in wParam l'indice dell'elemento su cui e' stato rilasciato
	il mouse ed in lParam le coordinate del rilascio).
	Vedi le note in OnLButtonDown() a proposito di SetCapture().
*/
void CListCtrlEx::OnLButtonUp(UINT nFlags,CPoint point)
{
	// salva le coordinate del click
	m_Point.x = point.x;
	m_Point.y = point.y;

	// chiama il gestore originale
	CListCtrl::OnLButtonUp(nFlags,point);

	// per terminare di ricevere i messaggi con il mouse fuori dell'area client
	//if(::GetCapture()==this->m_hWnd)
	//	::ReleaseCapture();

	// indice dell'elemento su cui e' stato effettuato il rilascio
	int m_nCurrentItem = CListCtrl::HitTest(point);

	// se e' stato specificato un gestore, invia il messaggio relativo
	// in wParam l'indice dell'elemento ed in lParam le coordinate del rilascio
	if(m_pWnd!=NULL && m_nLButtonUpMessage!=(UINT)-1)
		::SendMessage(m_pWnd->m_hWnd,m_nLButtonUpMessage,(WPARAM)m_nCurrentItem,(LPARAM)&m_Point);
}

/*
	OnRButtonDown()

	Gestore per il click con il bottone destro del mouse sull'elemento del controllo.
	Visualizza il menu popup ed invia il messaggio alla finestra principale (in wParam
	l'indice dell'elemento selezionato ed in lParam le coordinate del click).
*/
void CListCtrlEx::OnRButtonDown(UINT nFlags,CPoint point)
{
	// salva le coordinate del click
	m_Point.x = point.x;
	m_Point.y = point.y;

	// chiama il gestore originale
	CListCtrl::OnRButtonDown(nFlags,point);

	// indice dell'item cliccato
	m_nCurrentItem = CListCtrl::HitTest(point);

	// a seconda del flag imposta l'item corrente come selezionato
	// (per default solo il click con il sinistro imposta l'elemento come selezionato)
	if(m_bRightClickSelects)
	{
		// resetta le selezioni correnti dato la selezione multiple puo' effettuarsi solo con CTRL+click destro
		ResetItemsSelection();
		
		// seleziona l'item
		SetItemSelection(m_nCurrentItem,TRUE);
	}

	// visualizza il menu a comparsa
	PopUpMenu(point);

	// se e' stato specificato un gestore, invia il messaggio relativo
	// in wParam l'indice dell'elemento ed in lParam le coordinate del click
	if(m_pWnd!=NULL && m_nRButtonDownMessage!=(UINT)-1)
		::PostMessage(m_pWnd->m_hWnd,m_nRButtonDownMessage,(WPARAM)m_nCurrentItem,(LPARAM)&m_Point);
}

/*
	OnRButtonUp()

	Gestore per il rilascio del bottone destro del mouse.
	Invia il messaggio alla finestra principale (in wParam l'indice dell'elemento su cui e' stato rilasciato
	il mouse ed in lParam le coordinate del rilascio).
*/
void CListCtrlEx::OnRButtonUp(UINT nFlags,CPoint point)
{
	// salva le coordinate del click
	m_Point.x = point.x;
	m_Point.y = point.y;

	// chiama il gestore originale
	CListCtrl::OnRButtonUp(nFlags,point);

	// indice dell'elemento su cui e' stato effettuato il rilascio
	int m_nCurrentItem = CListCtrl::HitTest(point);

	// se e' stato specificato un gestore, invia il messaggio relativo
	// in wParam l'indice dell'elemento ed in lParam le coordinate del rilascio
	if(m_pWnd!=NULL && m_nRButtonUpMessage!=(UINT)-1)
		::SendMessage(m_pWnd->m_hWnd,m_nRButtonUpMessage,(WPARAM)m_nCurrentItem,(LPARAM)&m_Point);
}

/*
	OnLButtonDblClk()

	Gestore per il doppio click con il bottone sinistro del mouse sull'elemento del controllo.
	Invia il messaggio alla finestra principale (in wParam l'indice dell'elemento selezionato ed in lParam
	le coordinate del click).
*/
void CListCtrlEx::OnLButtonDblClk(UINT nFlags,CPoint point)
{
	// salva le coordinate del click
	m_Point.x = point.x;
	m_Point.y = point.y;

	// chiama il gestore originale
	CListCtrl::OnLButtonDblClk(nFlags,point);

	// indice dell'elemento cliccato
	m_nCurrentItem = CListCtrl::HitTest(point);

	// se e' stato specificato un gestore, invia il messaggio relativo
	// in wParam l'indice dell'elemento ed in lParam le coordinate del click
	if(m_pWnd!=NULL && m_nLButtonDblClkMessage!=(UINT)-1)
		::PostMessage(m_pWnd->m_hWnd,m_nLButtonDblClkMessage,(WPARAM)m_nCurrentItem,(LPARAM)&m_Point);
}

/*
	OnRButtonDblClk()

	Gestore per il doppio click con il bottone destro del mouse sull'elemento del controlo.
	Invia il messaggio alla finestra principale (in wParam l'indice dell'elemento selezionato ed in lParam
	le coordinate del click).
*/
void CListCtrlEx::OnRButtonDblClk(UINT nFlags,CPoint point)
{
	// salva le coordinate del click
	m_Point.x = point.x;
	m_Point.y = point.y;

	// chiama il gestore originale
	CListCtrl::OnRButtonDblClk(nFlags,point);

	// indice dell'elemento cliccato
	m_nCurrentItem = CListCtrl::HitTest(point);

	// se e' stato specificato un gestore, invia il messaggio relativo
	// in wParam l'indice dell'elemento ed in lParam le coordinate del click
	if(m_pWnd!=NULL && m_nRButtonDblClkMessage!=(UINT)-1)
		::PostMessage(m_pWnd->m_hWnd,m_nRButtonDblClkMessage,(WPARAM)m_nCurrentItem,(LPARAM)&m_Point);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Luca Piergentili
Web Developer
Italy Italy
I like C and C++, Acid Jazz, James Brown, gli Spaghetti Aglio e Olio, alla Bolognesa, alla Puttanesca e le Fettuccine alla Matriciana ('Maccaroni' over the world). Of course I like beautiful big tits girls too, my little car, Frank Zappa, the art of Zen, italian coffee and much more...

| Advertise | Privacy | Mobile
Web03 | 2.8.140922.1 | Last Updated 28 Oct 2001
Article Copyright 2001 by Luca Piergentili
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid