📄 catlistbox.cpp
字号:
// CatListBox.cpp : Category Listbox implementation file.
//
// Author: Joshua Quick
//
// Features:
// - Organizes listbox strings by categories.
// - Categories can be opened to reveal their strings or closed to hide them.
// - Category items can have checkboxes.
// - Can store a DWORD with each category item.
//
// Do the following to add this control to a dialog:
// 1) Add a listbox to a dialog resource.
// 2) Setup listbox resource for "Owner Draw: Fixed" and check "Has Strings".
// 3) Create CCatListBox member variable in dialog by hand or wizard.
// *) If created by hand, then call <variable>.SubclassDlgItem(LISTBOX_ID,this).
//
/////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "CatListBox.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CCatListBox::CCatListBox()
{
m_mapCat.clear();
m_bShowStates = false;
m_bDoDraw = true;
}
CCatListBox::~CCatListBox()
{
ResetCategoryInfo();
}
//////////////////////////////////////////////////////////////////////
// Message mapping.
//////////////////////////////////////////////////////////////////////
BEGIN_MESSAGE_MAP(CCatListBox, CListBox)
//{{AFX_MSG_MAP(CCatListBox)
ON_WM_LBUTTONDOWN()
ON_WM_KEYDOWN()
ON_WM_LBUTTONDBLCLK()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//////////////////////////////////////////////////////////////////////
// Message/event functions.
//////////////////////////////////////////////////////////////////////
// Draws strings into listbox.
void CCatListBox::DrawItem( LPDRAWITEMSTRUCT lpDrawItemStruct )
{
// Do not continue if draw flag is FALSE.
// Works better than LockWindowUpdate() which prevents draws for all UI in app.
if (!m_bDoDraw)
return;
// Validate argument.
if (!lpDrawItemStruct || ((int)(lpDrawItemStruct->itemID) < 0))
return;
// Get list item's category info.
CatListBoxCatInfo* pCatInfo = (CatListBoxCatInfo*)lpDrawItemStruct->itemData;
if (!pCatInfo)
return;
// Setup device context.
CDC* pDC = CDC::FromHandle( lpDrawItemStruct->hDC );
// Determine if list item is highlighted.
bool bHighlight = ((lpDrawItemStruct->itemState & ODS_SELECTED) &&
!(GetStyle() & LBS_NOSEL) && IsWindowEnabled());
// Draw category's background.
CRect rcValue = lpDrawItemStruct->rcItem;
COLORREF crNew = GetSysColor( bHighlight ? COLOR_HIGHLIGHT : COLOR_WINDOW );
COLORREF crOldBk = pDC->SetBkColor( crNew );
CBrush brWindow( crNew );
pDC->FillRect( rcValue, &brWindow );
// Determine position of category's button or item's checkbox.
CRect rcButton( rcValue );
rcButton.top += Y_MARGIN;
rcButton.left += X_MARGIN;
rcButton.bottom -= Y_MARGIN + 1;
rcButton.right = rcButton.left + rcButton.Height();
// Draw category/item.
if (lpDrawItemStruct->itemID == pCatInfo->iListIndex) {
// Draw category's background.
rcValue.bottom--;
crNew = GetSysColor( COLOR_3DFACE );
if (!bHighlight) {
pDC->SetBkColor( crNew );
CBrush brBackGround( crNew );
pDC->FillRect( rcValue, &brBackGround );
}
// Draw category's button frame.
pDC->DrawFrameControl( rcButton, DFC_BUTTON, DFCS_BUTTONPUSH );
// Draw "-" in button.
crNew = GetSysColor( IsWindowEnabled() ? COLOR_BTNTEXT : COLOR_GRAYTEXT );
int iX = rcButton.left + X_MARGIN;
int iY = rcButton.top + (rcButton.Height() / 2);
for (; iX < (rcButton.right - X_MARGIN); iX++)
pDC->SetPixel( iX, iY, crNew );
// Draw "|" in button if category is open, therefore forming a "+".
if (!pCatInfo->bIsOpen) {
iX = rcButton.left + (rcButton.Width() / 2);
for (iY = rcButton.top + X_MARGIN; iY < (rcButton.bottom - X_MARGIN); iY++)
pDC->SetPixel( iX, iY, crNew );
}
}
else if (m_bShowStates) {
// Draw item's checkbox according to its current state.
CatListBoxStlListIter iter;
int iCount = (lpDrawItemStruct->itemID - pCatInfo->iListIndex) - 1;
for (iter = pCatInfo->lstItems.begin(); iter != pCatInfo->lstItems.end(); iter++) {
if (iCount <= 0) {
if (0 == (*iter).iState) // Draw a checked checkbox.
pDC->DrawFrameControl( rcButton, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT );
else if (1 == (*iter).iState) // Draw an unchecked checkbox.
pDC->DrawFrameControl( rcButton, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT | DFCS_CHECKED );
break;
}
iCount--;
}
}
// Draw text.
if (!IsWindowEnabled())
crNew = GetSysColor( COLOR_GRAYTEXT );
else if (bHighlight)
crNew = GetSysColor( COLOR_HIGHLIGHTTEXT );
else
crNew = GetSysColor( COLOR_WINDOWTEXT );
COLORREF crOldTxt = pDC->SetTextColor( crNew );
CString sText;
GetText( lpDrawItemStruct->itemID, sText );
int iTextPos = lpDrawItemStruct->rcItem.left;
if (m_bShowStates || (lpDrawItemStruct->itemID == pCatInfo->iListIndex))
iTextPos = rcButton.right;
iTextPos += X_MARGIN;
pDC->TextOut( iTextPos, lpDrawItemStruct->rcItem.top + Y_MARGIN, sText );
// If this item has the focus, then draw a frame around it.
if ((GetFocus() == this) && IsWindowEnabled() &&
(lpDrawItemStruct->itemState & ODS_FOCUS)) {
pDC->DrawFocusRect( &(lpDrawItemStruct->rcItem) );
}
// Restore DC's old settings.
pDC->SetBkColor( crOldBk );
pDC->SetTextColor( crOldTxt );
}
// Opens/closes categories or checks/unchecks items.
void CCatListBox::OnLButtonDown( UINT nFlags, CPoint point )
{
CatListBoxCatInfo* pCatInfo;
CString sCategory;
CRect rcValue;
int iHeight;
int iListIndex;
// Get listbox's height.
GetWindowRect( rcValue );
iHeight = rcValue.Height();
// Determine if a category button has been clicked.
for (iListIndex = GetTopIndex(); iListIndex < GetCount(); iListIndex++) {
// Get item's category info.
pCatInfo = (CatListBoxCatInfo*)GetItemData( iListIndex );
if (!pCatInfo)
continue;
// Calculate button position.
GetItemRect( iListIndex, rcValue );
rcValue.top += Y_MARGIN;
rcValue.bottom -= Y_MARGIN + 1;
rcValue.left += X_MARGIN;
rcValue.right = rcValue.left + rcValue.Height();
// Stop now if reached lower displayable bound of listbox.
if (rcValue.top >= iHeight)
break;
// Determine if item's button was clicked.
if (rcValue.PtInRect( point )) {
GetText( pCatInfo->iListIndex, sCategory );
SetCurSel( iListIndex );
if (iListIndex == pCatInfo->iListIndex) {
// Open/close category.
SetCategoryState( (LPCTSTR)sCategory, !(pCatInfo->bIsOpen) );
}
else if (m_bShowStates) {
// Check/uncheck item's checkbox if it has a checkbox.
int iItemIndex = (iListIndex - pCatInfo->iListIndex) - 1;
int iState = GetCategoryItemState( (LPCTSTR)sCategory, iItemIndex );
if (0 == iState)
SetCategoryItemState( (LPCTSTR)sCategory, iItemIndex, 1 );
else if (1 == iState)
SetCategoryItemState( (LPCTSTR)sCategory, iItemIndex, 0 );
}
break; // Done. State has been updated.
}
}
CListBox::OnLButtonDown( nFlags, point );
}
// Opens/closes categories.
void CCatListBox::OnLButtonDblClk( UINT nFlags, CPoint point )
{
CatListBoxCatInfo* pCatInfo;
CString sCategory;
CRect rcValue;
int iHeight;
int iListIndex;
// Get listbox's height.
GetWindowRect( rcValue );
iHeight = rcValue.Height();
// Determine if a category has been double clicked.
for (iListIndex = GetTopIndex(); iListIndex < GetCount(); iListIndex++) {
// Get listbox item's category info.
pCatInfo = (CatListBoxCatInfo*)GetItemData( iListIndex );
if (!pCatInfo)
continue;
// Skip item if it's not a category.
if (iListIndex != pCatInfo->iListIndex)
continue;
// Determine if category has been double clicked.
GetItemRect( iListIndex, rcValue );
if (rcValue.top >= iHeight)
break; // Stop searching. Lower listbox bound has been reached.
if (rcValue.PtInRect( point )) {
GetText( iListIndex, sCategory );
SetCurSel( iListIndex );
SetCategoryState( (LPCTSTR)sCategory, !(pCatInfo->bIsOpen) );
break; // Done. Category has been opened/closed.
}
}
CListBox::OnLButtonDblClk( nFlags, point );
}
// Opens/closes categories or checks/unchecks items when the space bar is pressed.
void CCatListBox::OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags )
{
if (VK_SPACE == nChar) {
int iListIndex = GetCurSel();
if (iListIndex != LB_ERR) {
CatListBoxCatInfo* pInfo = (CatListBoxCatInfo*)GetItemData( iListIndex );
if (pInfo && (LB_ERR != (int)pInfo)) {
CString sCategory;
GetText( pInfo->iListIndex, sCategory );
if (iListIndex == pInfo->iListIndex) {
// Open/close category.
SetCategoryState( (LPCTSTR)sCategory, !(pInfo->bIsOpen) );
}
else if (m_bShowStates) {
// Check/uncheck item if it has a checkbox.
int iItemIndex = (iListIndex - pInfo->iListIndex) - 1;
int iState = GetCategoryItemState( (LPCTSTR)sCategory, iItemIndex );
if (0 == iState)
SetCategoryItemState( (LPCTSTR)sCategory, iItemIndex, 1 );
else if (1 == iState)
SetCategoryItemState( (LPCTSTR)sCategory, iItemIndex, 0 );
}
}
}
}
CListBox::OnKeyDown( nChar, nRepCnt, nFlags );
}
//////////////////////////////////////////////////////////////////////
// Add functions.
//////////////////////////////////////////////////////////////////////
// Adds a new category to listbox. (Category name must be unique!)
HRESULT CCatListBox::AddCategory( LPCTSTR pCategory )
{
CatListBoxStlMapIter iter;
CatListBoxCatInfo* pCatInfo;
CatListBoxCatInfo* pTempInfo;
// Validate argument.
if (!pCategory)
return E_POINTER;
if (_tcslen( pCategory ) <= 0)
return E_INVALIDARG;
if (m_mapCat.end() != m_mapCat.find(CString(pCategory))) // Category name must be unique!
return E_INVALIDARG;
// Add category to STL map.
try {
pCatInfo = new CatListBoxCatInfo;
if (!pCatInfo)
return E_FAIL;
pCatInfo->bIsOpen = false;
pCatInfo->iListIndex = -1;
m_mapCat.insert( CatListBoxStlMap::value_type( CString(pCategory), pCatInfo ) );
}
catch (...) {
ASSERT(0);
return E_FAIL;
}
// Add category to listbox.
if (LBS_SORT & GetStyle()) {
for (iter = m_mapCat.begin(); iter != m_mapCat.end(); iter++) {
pTempInfo = (*iter).second;
if (!pTempInfo || (pTempInfo == pCatInfo))
continue;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -