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

An extended ListBox control

, 17 Dec 2002
A control that offers extensible entry specification and full keyboard support.
listboxex_demo.zip
ListDemo.zip
ListSubclass.zip
listboxex_src.zip
// 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.

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

ClearlyDotNet
Web Developer
United States United States
No Biography provided

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 18 Dec 2002
Article Copyright 2002 by ClearlyDotNet
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid