Click here to Skip to main content
15,884,298 members
Articles / Programming Languages / C#

An Extended ListBox Control

Rate me:
Please Sign up or sign in to vote.
3.78/5 (9 votes)
17 Dec 20021 min read 129.6K   1.3K   44  
A control that offers extensible entry specification and full keyboard support
// ListBoxEx.cpp : implementation file
//

#include "stdafx.h"
#include "ListDemo.h"
#include "ListBoxEx.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CListBoxEx

CListBoxEx::CListBoxEx()
{
}

CListBoxEx::~CListBoxEx()
{
	m_BkBrush.DeleteObject();
}


BEGIN_MESSAGE_MAP(CListBoxEx, CListBox)
	//{{AFX_MSG_MAP(CListBoxEx)
	ON_WM_MOUSEMOVE()
	ON_MESSAGE(WM_MOUSELEAVE,OnMouseLeave)
	ON_WM_CTLCOLOR_REFLECT()
	ON_WM_NCCALCSIZE()
	ON_WM_NCPAINT()
	ON_WM_NCHITTEST()
	ON_WM_NCLBUTTONDOWN()
	ON_WM_TIMER()
	ON_WM_ENABLE()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CListBoxEx message handlers

void CListBoxEx::PreSubclassWindow() 
{
	// TODO: Add your specialized code here and/or call the base class
	m_bOver = FALSE;
	m_ItemHeight=18; 
	m_crTextHlt=GetSysColor(COLOR_HIGHLIGHTTEXT);
	m_crTextClr=GetSysColor(COLOR_WINDOWTEXT);
	m_HBkColor=GetSysColor(COLOR_HIGHLIGHT);
	m_BmpWidth=16;
	m_BmpHeight=16;


	CListBox::PreSubclassWindow();

}

void CListBoxEx::DrawBorders()
{
	//Gets the Controls device context
	CDC *pDC=GetDC();
	
	//Gets the size of the control's client area
	CRect rect;
	GetClientRect(rect);
	
	/*
	Inflates the size of rect by the size of the default border
	Suppose rect is (0,0,100,200) and the default border is 2 pixels,
	after InflateRect, rect should be (-2,-2, 102,202) and the border will be drawn
	from -2 to 0, -2 -> 0, 102->100, 202->200.
	*/
	rect.InflateRect(CSize(GetSystemMetrics(SM_CXEDGE), GetSystemMetrics(SM_CYEDGE)));
	
	//Draws the edge of the border depending on whether the mouse is over or not
	
	if (m_bOver)pDC->DrawEdge(rect,EDGE_BUMP ,BF_RECT );
	else pDC->DrawEdge(rect,EDGE_SUNKEN,BF_RECT );
	
	ReleaseDC(pDC); //Frees the DC
}

void CListBoxEx::OnMouseMove(UINT nFlags, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	
	//If m_bOver==FALSE, and this function is called, it means that the mouse entered.
	
	if (!m_bOver){ 
		m_bOver=TRUE; //Now the mouse is over
		DrawBorders(); //Self explanatory
	
		TRACKMOUSEEVENT track; //Declares structure
		track.cbSize=sizeof(track);
		track.dwFlags=TME_LEAVE; //Notify us when the mouse leaves
		track.hwndTrack=m_hWnd; //Assigns this window's hwnd
		TrackMouseEvent(&track); //Tracks the events like WM_MOUSELEAVE
	
	}
	
	CListBox::OnMouseMove(nFlags, point);
}

LRESULT CListBoxEx::OnMouseLeave(WPARAM wParam, LPARAM lParam){

	m_bOver=FALSE;
	DrawBorders();
	
	return 0;
}



HBRUSH CListBoxEx::CtlColor(CDC* pDC, UINT nCtlColor) 
{
	// TODO: Change any attributes of the DC here
	if (!IsWindowEnabled()){
	CBrush br(GetSysColor(COLOR_INACTIVEBORDER));
	return br;
	}
	
	// TODO: Return a non-NULL brush if the parent's handler should not be called
	return m_BkBrush;
}
void CListBoxEx::SetBkColor(COLORREF crBkColor,COLORREF crSelectedColor)
{
	m_HBkColor=crSelectedColor;
	m_BkBrush.DeleteObject(); //Deletes previous brush. Must do it or there would be a run-time error.
	m_BkBrush.CreateSolidBrush(crBkColor); //Sets the brush the specified background color
	Invalidate(); //Forces Redraw
}

void CListBoxEx::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) 
{
	// TODO: Add your code to determine the size of specified item
	lpMeasureItemStruct->itemHeight=m_ItemHeight;

}

void CListBoxEx::SetItemHeight(int newHeight)
{
	m_ItemHeight=newHeight;
	Invalidate();
}


void CListBoxEx::AddItem(UINT IconID, LPCTSTR lpszText)
{
	//Adds a string ans assigns nIndex the index of the current item
	int nIndex=AddString(lpszText);
	//If no error, associates the index with the bitmap ID
	if (nIndex!=LB_ERR&&nIndex!=LB_ERRSPACE)
		SetItemData(nIndex, IconID);
}


void CListBoxEx::InsertItem(int nIndex, UINT nID, LPCTSTR lpszText)
{
	int result=InsertString(nIndex,lpszText); //Inserts the string
	if (result!=LB_ERR||result!=LB_ERRSPACE) SetItemData(nIndex,nID); //Associates the ID with the index
}

void CListBoxEx::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
	// TODO: Add your code to draw the specified item
		// TODO: Add your message handler code here
	CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC); //Gets the item DC
	UINT nID=(UINT)lpDrawItemStruct->itemData; //Retrieves the ID we set using SetItemData
	CRect rect=lpDrawItemStruct->rcItem; //Gets the rect of the item
	UINT action=lpDrawItemStruct->itemAction; //What it wants to do
	UINT state=lpDrawItemStruct->itemState; //The item current state
	COLORREF TextColor=m_crTextClr; //Text color that we'll use
	
	//Action statements
	if ((state & ODS_SELECTED) &&
		(action & ODA_SELECT))
		//Used when an item need to be selected
	{
		//Since it will be highlighted, we create a brush with the highlighted color
		CBrush brush(m_HBkColor); 
		//Draws the highlighted rect	
		pDC->FillRect(rect, &brush);

	}
	
	if (!(state & ODS_SELECTED) &&	(action & ODA_SELECT))
		//The item needs to be deselected
	{
		//We draw the background color	
		pDC->FillRect(rect, &m_BkBrush);
	
	}
	
	if ((action & ODA_FOCUS) && (state & ODS_FOCUS)&&(state&ODS_SELECTED)){
		//It has the focus,
		//Draws a 3D focus rect	
		pDC->Draw3dRect(rect,RGB(255,255,255),RGB(0,0,0)); 
		TextColor=m_crTextHlt;	

	}
	
	if ((action & ODA_FOCUS) && !(state & ODS_FOCUS)&&(state&ODS_SELECTED)){
		
		//If the focus needs to be removed.
		CBrush brush(m_HBkColor); 
		pDC->FillRect(rect, &brush);
		TextColor=m_crTextHlt;	
	}

	if (state&ODS_DISABLED) TextColor=GetSysColor(COLOR_3DSHADOW); //If disabled
	CString text;
	GetText(lpDrawItemStruct->itemID, text); //Gets the item text
	pDC->SetTextColor(TextColor); //Mo need to explain
	pDC->SetBkMode(TRANSPARENT); //Sets text background transparent

	
	
	if (nID!=NO_BMP_ITEM){ //If the item has a bitmap
		
		CDC dcMem; //New device context used as the source DC
		//Creates a deice context compatible to pDC
		dcMem.CreateCompatibleDC(pDC); 
		
		CBitmap bmp; //Bitmap object
		//Loads the bitmap with the specified resource ID
		bmp.LoadBitmap(nID); 
		//Saves the old bitmap object so that the GDI resources are not depleted
		CBitmap* oldbmp=dcMem.SelectObject(&bmp); 
		
		if (nID!=BLANK_BMP) //Draws the bitmap if it is not blank
			//Copies the bitmap to the screen
			pDC->BitBlt(rect.left+5,rect.top,m_BmpWidth,m_BmpHeight,&dcMem,0,0,SRCCOPY); 
		
		//Selects the saved bitmap object
		dcMem.SelectObject(oldbmp); 
		
		bmp.DeleteObject(); //Deletes the bitmap
		
		//Displays the text 
		pDC->TextOut(rect.left+10+m_BmpWidth,rect.top,text); 
	}
	//Displays the text without identing it
	else pDC->TextOut(rect.left+5,rect.top,text); 
	
	
	
}

void CListBoxEx::SetTextColor(COLORREF crTextColor, COLORREF crHighlight)
{
	m_crTextClr=crTextColor;
	m_crTextHlt=crHighlight;
	Invalidate();
}

void CListBoxEx::SetBMPSize(int Height, int Width)
{
	m_BmpHeight=Height;
	m_BmpWidth=Width;
	Invalidate();
}

void CListBoxEx::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp) 
{
	// TODO: Add your message handler code here and/or call default
	
	lpncsp->rgrc[0].top += 16; //Top
	lpncsp->rgrc[0].bottom -= 16; //Bottom

	CListBox::OnNcCalcSize(bCalcValidRects, lpncsp);
}

void CListBoxEx::OnNcPaint() 
{
	// TODO: Add your message handler code here

	static BOOL before=FALSE;
	if (!before) { //If first time, the OnNcCalcSize function will be called
			SetWindowPos(NULL,0,0,0,0,SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE); 
			before=TRUE;
	}
	
	DrawBorders();
	DrawScrolls(SC_UP,SC_NORMAL);
	DrawScrolls(SC_DOWN,SC_NORMAL);

	// Do not call CListBox::OnNcPaint() for painting messages
}

void CListBoxEx::DrawScrolls(UINT WhichOne, UINT State)
{

	CDC *pDC=GetDC();
	CRect rect;
	GetClientRect(rect); //Gets the dimensions
	
	if (!IsWindowEnabled())State=SC_DISABLED; //If the window is not enabled, set state to disabled
	
	rect.left-=GetSystemMetrics(SM_CYEDGE); //Expands the so that it does not draw over the borders
	rect.right+=GetSystemMetrics(SM_CXEDGE);
	
	if (WhichOne==SC_UP){ //The one to draw is the up one
		
		//Calculates the rect of the up scroll
		rect.bottom=rect.top-GetSystemMetrics(SM_CXEDGE);
		rect.top=rect.top-16-GetSystemMetrics(SM_CXEDGE);
		
		//Draws the scroll up
		pDC->DrawFrameControl(rect,DFC_SCROLL,State|DFCS_SCROLLUP);
	}
	else{ //Needs to draw down
		
		rect.top=rect.bottom+GetSystemMetrics(SM_CXEDGE);;
		rect.bottom=rect.bottom+16+GetSystemMetrics(SM_CXEDGE);
		
		pDC->DrawFrameControl(rect,DFC_SCROLL,State|DFCS_SCROLLDOWN);
		
	}	
	ReleaseDC(pDC);
}

UINT CListBoxEx::OnNcHitTest(CPoint point) 
{
	// TODO: Add your message handler code here and/or call default
	
	CRect rect,top,bottom; 
	//Gets the windows rect, relative to the parent, so rect.left and rect.top might not be 0.
	GetWindowRect(rect); 
	ScreenToClient(rect); //Converts the rect to the client
	
	//Calculates the rect of the bottom and top scrolls
	top=bottom=rect;
	top.bottom=rect.top+16;
	bottom.top=rect.bottom-16;
	
	
	UINT where = CListBox::OnNcHitTest(point); //Obtains where the mouse is
	
	ScreenToClient(&point); //Converts the point so its relative to the client area
	
	if (where == HTNOWHERE) //If mouse is not in a place it recognizes
		if (top.PtInRect(point)) //Check to see if the mouse is on the top
			where = HTVSCROLL;
		else if (bottom.PtInRect(point)) where=HTHSCROLL; //Check to see if its on the bottom
		
		return where; //Returns where it is
}


void CListBoxEx::OnNcLButtonDown(UINT nHitTest, CPoint point) 
{
	// TODO: Add your message handler code here and/or call default

	if (nHitTest==HTVSCROLL) //Up scroll Pressed
	{
		DrawScrolls(SC_UP,SC_PRESSED);
		SendMessage(WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0); //Scroll up 1 line
		
		
		SetTimer(1,100,NULL); //Sets the timer ID 1
	}
	else if (nHitTest==HTHSCROLL) //Down scroll Pressed
	{
		DrawScrolls(SC_DOWN,SC_PRESSED);
		SendMessage(WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0); //Scroll down 1 line
		
		SetTimer(2,100,NULL); //Sets the timer ID 2
	}
	
	CListBox::OnNcLButtonDown(nHitTest, point);
}

void CListBoxEx::OnTimer(UINT nIDEvent) 
{
	// TODO: Add your message handler code here and/or call default
	
	//Gets the state of the left button to see if it is pressed
	short result=GetKeyState(VK_LBUTTON);
	
	if (nIDEvent==1){ //Up timer
		
		//If it returns negative then it is pressed
		if (result<0){
			SendMessage(WM_VSCROLL,MAKEWPARAM(SB_LINEUP,0),0);	
		}
		else { //No longer pressed
			
			KillTimer(1);
			DrawScrolls(SC_UP,SC_NORMAL);		
		}
	}
	else { //Down timer
		
		//If it returns negative then it is pressed
		if (result<0){
			SendMessage(WM_VSCROLL,MAKEWPARAM(SB_LINEDOWN,0),0);	
		}
		else {
			KillTimer(2);
			DrawScrolls(SC_DOWN,SC_NORMAL);		
		}
	}
	
	CListBox::OnTimer(nIDEvent);
}

void CListBoxEx::OnEnable(BOOL bEnable) 
{
	CListBox::OnEnable(bEnable);
	
	// TODO: Add your message handler code here
	//SC_NORMAL will be changed to SC_DISABLED if the window is disabled
	DrawScrolls(SC_UP,SC_NORMAL);
	DrawScrolls(SC_DOWN,SC_NORMAL);
	Invalidate();
}

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.


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions