Custom Draw LIstCtrl and scrolling
Hi,
I wrote a custom draw CListCtrl that allow to display different row
height on windows mobile(see code at the end - trick relies on
OnSetFont, MeasureItem and LVS_OWNERDRAWFIXED).
My problem now is about scrolling because it seems that I have to handle
it manually.
I tried but my vertical scrollbar seems too long.
In my OnSize here is what I do :
void CListCtrlCommands::OnSize( UINT nType, int cx, int cy )
{
if ( (cx > 0) )
{
CRect rect;
GetClientRect( &rect );
m_nWndHeight = rect.Height();
m_nWndWidth = rect.Width();
ReArrangeWholeLayout();
// Always resize last column to take maximum space
if ((m_bColResizing == true) && (rect.Width() > 0))
{
//TODO : more resize options (for now only last col)
int nCols = GetColumnCount();
//TRACE(_T("CListCtrlCommands::OnSize() : nCols=%d, rc.with()=%d\n"),
nCols, rect.Width());
if (nCols >= 1){
int nColInterval = 0, nLastColSize = 0, nClientWidth = 0;
for (int i = 0; i < nCols - 1; i++)
{
nLastColSize = GetColumnWidth(i);
nColInterval += nLastColSize;
}
nClientWidth = rect.Width();
/*vlwTrace( "ncols=%d, nClientWidth=%d, nLastColSize=%d,
nColInterval=%d\r\n",
nCols, nClientWidth, nLastColSize, nColInterval);*/
SetColumnWidth(nCols - 1, (nClientWidth - nColInterval) );
}
}
// Handle scrolling
SCROLLINFO scrlinfo = {0};
scrlinfo.cbSize = sizeof(scrlinfo);
scrlinfo.fMask = SIF_PAGE|SIF_RANGE;
scrlinfo.nMax = m_nListHeight;
scrlinfo.nMin = 0;
scrlinfo.nPage = m_nWndHeight + 1;
scrlinfo.nPos = 0;
SetScrollInfo(SB_VERT,&scrlinfo);
CListCtrl::OnSize(nType, cx, cy);
TRACE(_T("/OnSize : m_nListHeight=%d, m_nWndHeight=%d\n"),
m_nListHeight, m_nWndHeight);
}
}
When I try the code above, vertical scrollbar is too big, I can scroll
above my items.
m_nListHeight is equal to total list heigth
m_nWndHeight is equal to the listctrl size.
and I have added 1 because if nMax == nPage the scrollbar is displayed.
**************************************************************************************************************
#include "stdafx.h"
#include "MemDC.h"
#include "ListCtrlCommands.h"
#include "DrawHTML.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
IMPLEMENT_DYNAMIC(CListCtrlCommands, CListCtrl)
CListCtrlCommands::CListCtrlCommands():
CListCtrl(),
m_nWndHeight(0),
m_nWndWidth(0),
m_styles(0),
m_removeOwnerDraw(false),
m_nImgWidth(0),
m_bColResizing(false),
m_nItemHeight(0),
m_bCircular(true)
{
}
CListCtrlCommands::~CListCtrlCommands()
{
}
BEGIN_MESSAGE_MAP(CListCtrlCommands, CListCtrl)
ON_WM_CREATE()
ON_WM_SIZE()
ON_MESSAGE(WM_SETFONT, OnSetFont)
ON_WM_GETDLGCODE()
ON_WM_KEYDOWN()
ON_WM_MEASUREITEM_REFLECT()
//ON_WM_ERASEBKGND()
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CListCtrlCommands::OnCustomDraw)
END_MESSAGE_MAP()
BOOL CListCtrlCommands::Create(DWORD dwStyle, const RECT& rect, CWnd*
pParentWnd, UINT nID)
{
// The owner draw style is inserted at creation and remove straight
afterwards
// in WM_CREATE after the default OnCreate() has been called. It needs
to be
// set for this short period so that WM_MEASUREITEM is used. The
WM_MEASUREITEM
// message allows for the setting of a custom row height.
LVS_OWNERDRAWFIXED
// cannot be left set when the control is repainted since no code has
been set up
// to perform this task. Setting LVS_OWNERDRAWFIXED in WM_CREATE just
before the
// default OnCreate() call doesn't work.
m_removeOwnerDraw = !(dwStyle & LVS_OWNERDRAWFIXED);
return CListCtrl::Create(dwStyle|LVS_OWNERDRAWFIXED, rect, pParentWnd,
nID);
}
void CListCtrlCommands::PreSubclassWindow()
{
// call ancestor
CListCtrl::PreSubclassWindow();
CRect rc;
GetClientRect(&rc);
m_nWndWidth = rc.Width();
m_nWndHeight = rc.Height();
}
int CListCtrlCommands::GetColumnCount()
{
int nCount = -1;
CHeaderCtrl* pHeader = GetHeaderCtrl();
if (pHeader != NULL)
nCount = pHeader->GetItemCount();
return nCount;
}
int CListCtrlCommands::GetItemCheckedCount()
{
int nRet = -1;
DWORD dwStyleEx = GetExtendedStyle();
if ( dwStyleEx & LVS_EX_CHECKBOXES ){
int nItems = GetItemCount();
if ( nItems > 0){
nRet = 0;
for (int i = 0; i < nItems; i++){
if ( GetCheck( i ) == TRUE)
nRet++;
}
}
}
return nRet;
}
int CListCtrlCommands::InsertItem(int nItem, LPCTSTR lpszItem,int
nImage, int nHeight /*= 0*/ )
{
return CListCtrl::InsertItem(nItem, lpszItem, nImage);
}
BOOL CListCtrlCommands::GetCellRect(int iRow, int iCol, int nArea, CRect
&rect)
{
if(iCol)
return GetSubItemRect(iRow, iCol, nArea, rect);
if(GetColumnCount()== 1)
return GetItemRect(iRow, rect, nArea);
iCol = 1;
CRect rCol1;
if(!GetSubItemRect(iRow, iCol, nArea, rCol1))
return FALSE;
if(!GetItemRect(iRow, rect, nArea))
return FALSE;
rect.right = rCol1.left;
return TRUE;
}
void CListCtrlCommands::SetItemHeight(int nItemHeight)
{
m_nItemHeight = nItemHeight;
CFont* pFont = GetFont();
if (pFont == NULL)
pFont = CFont::FromHandle( (HFONT) ::GetStockObject(SYSTEM_FONT) );
HFONT hFont = (HFONT)pFont;
::SendMessage(m_hWnd,WM_SETFONT, (WPARAM)hFont, 0L);
}
//////////////////////////////////////////////////////////////////////////
// CListCtrlCommands AFX Message Handlers
//////////////////////////////////////////////////////////////////////////
int CListCtrlCommands::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CListCtrl::OnCreate(lpCreateStruct) != 0)
{
return -1;
}
if (m_removeOwnerDraw)
{
ModifyStyle(LVS_OWNERDRAWFIXED, 0);
}
return 0;
}
LRESULT CListCtrlCommands::OnSetFont(WPARAM wParam, LPARAM lParam)
{
//TRACE(_T("OnSetFont : "));
// Set the font as normal by using the default proc.
LRESULT result = DefWindowProc(WM_SETFONT, wParam, lParam);
// Remember if owner draw is initially set.
m_removeOwnerDraw = !(GetStyle() & LVS_OWNERDRAWFIXED);
// The font change will cause CListCtrl to re-adjust its own row heights.
// Force the control resize so that WM_MEASUREITEM will be processed again.
// LVS_OWNERDRAWFIXED needs to be set for WM_MEASUREITEM to be used, so
turn
// it on just for this part.
if (m_removeOwnerDraw)
{
ModifyStyle(0, LVS_OWNERDRAWFIXED);
}
// Get the control's original dimensions.
CRect rect;
GetWindowRect(rect);
// Force the window to resize.
SetWindowPos(NULL, 0, 0, 0, 0,
SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOREDRAW|SWP_NOOWNERZORDER|SWP_NOZORDER);
SetWindowPos(NULL, 0, 0, rect.Width(), rect.Height(),
SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOREDRAW|SWP_NOOWNERZORDER|SWP_NOZORDER);
// Turn off owner draw if it's not actually used.
if (m_removeOwnerDraw)
{
ModifyStyle(LVS_OWNERDRAWFIXED, 0);
}
//TRACE(_T("/OnSetFont\n"));
return result;
}
void CListCtrlCommands::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
//TRACE(_T("OnKeyDown : "));
if (m_bCircular == true)
{
int nSelItem = GetNextItem(-1, LVNI_SELECTED);
int nCount = GetItemCount();
if (nChar == VK_UP)
{
if(nSelItem == 0)
{
SetItem( nCount-1, 0, LVIF_STATE, NULL, 0, LVIS_SELECTED |
LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED, 0 );
return;
}
}
else if (nChar == VK_DOWN)
{
if(nSelItem == nCount-1)
{
SetItem( 0, 0, LVIF_STATE, NULL, 0, LVIS_SELECTED | LVIS_FOCUSED,
LVIS_SELECTED | LVIS_FOCUSED, 0 );
return;
}
}
}
CListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
//TRACE(_T("/OnKeyDown\n"));
}
void CListCtrlCommands::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
if (lpMeasureItemStruct == NULL)
return;
//TRACE( _T("CListCtrlCommands::MeasureItem(%d)\r\n"),
lpMeasureItemStruct->itemHeight);
if (m_nItemHeight != 0)
lpMeasureItemStruct->itemHeight = m_nItemHeight;
}
///////////////////////////////////////////////////////////////////////////////
// DrawCheckbox
int CListCtrlCommands::DrawCheckbox( CDC* pDC, CRect rc, LPLVITEM lpItem)
{
ATLASSERT(pDC);
if ((pDC == NULL) || (lpItem == NULL) || (rc.IsRectEmpty()))
return 0;
CDC& dc = *pDC;
int nCheckedState = GetCheck( lpItem->iItem );
CRect chkboxrect;
chkboxrect = rc;
int nWidth = rc.Height() / 4;
int nXOffset = 5;
chkboxrect.top += nWidth;
chkboxrect.bottom -= nWidth;
chkboxrect.left += nXOffset;
chkboxrect.right = chkboxrect.left + 3 + nWidth + nWidth; // width = height
//TRACE(_T("%d, %d\n"), chkboxrect.Width(), chkboxrect.Height() );
// fill rect around checkbox with white
dc.FillSolidRect( &chkboxrect, ::GetSysColor( COLOR_WINDOW ) );
// draw border
CBrush brush;
brush.CreateSolidBrush( RGB(51,102,153) );
dc.FrameRect( &chkboxrect, &brush );
if (nCheckedState == 1) {
HPEN hOldPen = NULL;
CPen blackpen;
blackpen.CreatePen( PS_SOLID, 1, RGB(51,153,51) );
hOldPen = (HPEN) dc.SelectObject( blackpen );
// draw the checkmark
int x = chkboxrect.left + nXOffset + nWidth;
ATLASSERT(x < chkboxrect.right);
int y = chkboxrect.top + 3;
for (int i = 0; i < 4; i++)
{
dc.MoveTo( x, y );
dc.LineTo( x, y + 3 );
x--;
y++;
}
for (int i = 0; i < 3; i++)
{
dc.MoveTo( x, y );
dc.LineTo( x, y + 3 );
x--;
y--;
}
// restore PEN
dc.SelectObject( hOldPen );
}
return chkboxrect.right;
}
int CListCtrlCommands::DrawIcon( CDC* pDC, CRect rc, LPLVITEM lpItem)
{
//TRACE(_T("DrawIcon : "));
ATLASSERT(pDC);
if ((pDC == NULL) || (lpItem == NULL) || (rc.IsRectEmpty()))
return 0;
CDC& dc = *pDC;
CRect rcIcon;
rcIcon = rc;
CImageList* pImgList = GetImageList( LVSIL_SMALL );
if (pImgList != NULL)
{
int nImgWidth = 0, nImgHeight = 0;
IMAGEINFO imgInfo = {0};
pImgList->GetImageInfo(lpItem->iImage, &imgInfo);
CRect rcImg(imgInfo.rcImage);
CPoint pt(rcIcon.left, (rcIcon.top + (rcIcon.Height() - rcImg.Height()
) /2 )) ;
pImgList->Draw ( pDC, lpItem->iImage, pt, ILD_TRANSPARENT );
}
//TRACE(_T("/DrawIcon\n"));
return rcIcon.left;
}
DWORD CListCtrlCommands::OnSubItemPrePaint( NMLVCUSTOMDRAW* pnmcd )
{
//TRACE(_T("OnSubItemPrePaint : "));
NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pnmcd);
int nItem = static_cast<int> (pLVCD->nmcd.dwItemSpec);
int nSubItem = pLVCD->iSubItem;
COLORREF crText = ::GetSysColor( COLOR_WINDOWTEXT );
COLORREF crBkgnd = ::GetSysColor( COLOR_WINDOW );
CDC *pDC= CDC::FromHandle(pLVCD->nmcd.hdc);
LVITEM rItem = {0};
rItem.mask = LVIF_IMAGE | LVIF_STATE;
rItem.iItem = nItem;
rItem.iSubItem = nSubItem;
rItem.stateMask = -1;
GetItem ( &rItem );
//TRACE(_T("Item = %d, image = %d, iRow=%d\n"), rItem.iItem,
rItem.iImage, nItem);
CRect rc, rcBounds;
GetSubItemRect( nItem, nSubItem, LVIR_BOUNDS, rcBounds );
rc = rcBounds;
// Draw Selected item Background
CBrush* pOldBrush = NULL;
CBrush brSelBar;
if (rItem.state & LVIS_SELECTED)
{
brSelBar.CreateSolidBrush( ::GetSysColor( COLOR_HIGHLIGHT ) );
}
else
{
brSelBar.CreateSolidBrush( RGB(255, 255, 255) );
}
pOldBrush = (CBrush*)pDC->SelectObject(&brSelBar);
pDC->FillRect(rc, &brSelBar);
// Check our control has LVS_EX_CHECKBOXES style
if ( (GetExtendedStyle() & LVS_EX_CHECKBOXES) )
{
rc.left += DrawCheckbox( pDC, rc, &rItem);
rc.left += 5;
}
// Draw Icon
/*rc.left += DrawIcon( pDC, rc, &rItem);
rc.left += 5;*/
CImageList* pImgList = GetImageList( LVSIL_SMALL );
if (pImgList != NULL)
{
int nImgWidth = 0, nImgHeight = 0;
IMAGEINFO imgInfo = {0};
pImgList->GetImageInfo(rItem.iImage, &imgInfo);
CRect rcImg(imgInfo.rcImage);
CPoint pt(rc.left, (rc.top + (rc.Height() - rcImg.Height() ) /2 )) ;
pImgList->Draw ( pDC, rItem.iImage, pt, ILD_TRANSPARENT );
rc.left += rcImg.Width() + 5;
}
// Draw label vertically centered
//RECT rcVert;
CString sItem = GetItemText(nItem, nSubItem);
//pDC->DrawText(sItem , rc, DT_VCENTER);
RECT rcVert = rc;
int nHeight = DrawHTML(pDC->GetSafeHdc(), (LPCTSTR)sItem,
sItem.GetLength(), &rcVert, DT_LEFT|DT_WORDBREAK|DT_CALCRECT);
if (nHeight > 0)
{
int nDiff = ((rc.bottom - rc.top) - (nHeight)) / 2 ;
rc.top += nDiff;
rc.bottom = rc.top + nHeight;
}
//
// now we really draw
DrawHTML(pDC->GetSafeHdc(), (LPCTSTR)sItem,
sItem.GetLength(), &rc, DT_LEFT|DT_WORDBREAK);
// Draw Focus rect
if (rItem.state & LVIS_SELECTED)
pDC->DrawFocusRect(&rcBounds);
if (pOldBrush != NULL)
pDC->SelectObject(pOldBrush);
//TRACE(_T("/OnSubItemPrePaint\n"));
return CDRF_SKIPDEFAULT; // We've painted everything.
}
void CListCtrlCommands::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
{
NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
//TRACE(_T("dwDrawStage = 0x%x\n"), pLVCD->nmcd.dwDrawStage);
switch(pLVCD->nmcd.dwDrawStage)
{
case CDDS_PREPAINT:
//TRACE(_T("CDDS_PREPAINT\n"));
*pResult = CDRF_NOTIFYSUBITEMDRAW; // ask for subitem
notifications.
break;
case CDDS_ITEMPREPAINT:
//TRACE(_T("CDDS_ITEMPREPAINT\n"));
*pResult = CDRF_NOTIFYSUBITEMDRAW|CDRF_NOTIFYPOSTPAINT;
break;
case CDDS_ITEMPREPAINT|CDDS_SUBITEM:
//TRACE(_T("CDDS_ITEMPREPAINT|CDDS_SUBITEM\n"));
OnSubItemPrePaint( pLVCD );
*pResult= CDRF_SKIPDEFAULT;
break;
case CDDS_ITEMPOSTPAINT:
//TRACE(_T("CDDS_ITEMPOSTPAINT\n"));
*pResult = CDRF_DODEFAULT;
break;
/*case CDDS_PREERASE:
TRACE(_T("CDDS_PREERASE\n"));
*pResult = CDRF_NOTIFYPOSTERASE;
break;
case CDDS_POSTERASE:
TRACE(_T("CDDS_POSTERASE\n"));
*pResult = CDRF_DODEFAULT;
break;*/
default:// it wasn't a notification that was interesting to us.
*pResult = CDRF_DODEFAULT;
}
//// Take the default processing unless we set this to something else
below.
//*pResult = CDRF_DODEFAULT;
//if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )
//{
// *pResult = CDRF_NOTIFYITEMDRAW;
//}
//else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage )
//{
// // This is the pre-paint stage for an item. We need to make another
// // request to be notified during the post-paint stage.
// *pResult = CDRF_NOTIFYPOSTPAINT;
//}
//else if ( CDDS_ITEMPOSTPAINT == pLVCD->nmcd.dwDrawStage )
//{
// // This is the prepaint stage for a subitem. Here's where we set the
// // item's text and background colors. Our return value will tell
// // Windows to draw the subitem itself, but it will use the new colors
// // we set here.
// int nItem = static_cast<int> (pLVCD->nmcd.dwItemSpec);
// int nSubItem = pLVCD->iSubItem;
// LVITEM rItem;
// ZeroMemory ( &rItem, sizeof(LVITEM) );
// rItem.mask = LVIF_IMAGE | LVIF_STATE;
// rItem.iItem = nItem;
// rItem.stateMask = LVIS_SELECTED;
// GetItem ( &rItem );
// //If this item is selected, redraw the icon with its normal colors.
// if ( rItem.state & LVIS_SELECTED )
// {
// CDC* pDC = CDC::FromHandle ( pLVCD->nmcd.hdc );
// // Get the rect that holds the item's icon.
// CRect rc;
// GetItemRect ( nItem, &rc, LVIR_ICON );
// // Draw the icon.
// //if (m_cImageList.GetSafeHandle() != NULL){
// CImageList* pImgList = GetImageList( LVSIL_SMALL );
// if (pImgList != NULL){
// pImgList->Draw ( pDC, rItem.iImage, rc.TopLeft(),
// ILD_TRANSPARENT );
// }
// //delete pMemDC;
// *pResult = CDRF_SKIPDEFAULT;
// }
//}
}
//
// returns the Width of each image
//
int
CListCtrlCommands::InitControl( LPCTSTR lpszFileName, int nImgCount )
{
//TRACE(_T("InitControl: "));
// set up image list
CImageList img;
CSize szBmp = UIManager::GetInstance()->CreateImageList( img,
lpszFileName, nImgCount );
// Init the Image List image and style
CImageList* pOldImg = SetImageList(&img, LVSIL_SMALL);
if (pOldImg != NULL) {
// remove old one
VERIFY(pOldImg->DeleteImageList());
}
img.Detach();
// remember image width
m_nImgWidth = (szBmp.cx / nImgCount);
//
// Create Columns
//
int nColumns = GetColumnCount();
if (nColumns == 0)
{
LV_COLUMN Column = {0};
Column.mask = LVCF_FMT;
Column.fmt = LVCFMT_LEFT;
InsertColumn( 0, &Column );
}
//TRACE(_T("/InitControl\n"));
return m_nImgWidth;
}
//
// returns the Width of each image
//
int
CListCtrlCommands::UpdateControl( LPCTSTR lpszFileName, int nImgCount )
{
//TRACE(_T("UpdateControl: "));
// remove previous items
DeleteAllItems();
ASSERT(GetItemCount() == 0);
//TRACE(_T("/UpdateControl\n"));
return InitControl( lpszFileName, nImgCount );
}
UINT CListCtrlCommands::OnGetDlgCode()
{
return (DLGC_WANTCHARS | DLGC_HASSETSEL | DLGC_WANTARROWS |
DLGC_WANTALLKEYS);
}
void CListCtrlCommands::ReArrangeWholeLayout()
{
CRect rcBounds;
m_nListHeight = 0;
for (int i = 0; i < GetItemCount(); i++)
{
GetSubItemRect( i, 0, LVIR_BOUNDS, rcBounds );
m_nListHeight += rcBounds.Height();
}
TRACE(_T("CListCtrlCommands::ReArrangeWholeLayout:
m_nListHeight=%d\n"),m_nListHeight );
}
void CListCtrlCommands::OnSize( UINT nType, int cx, int cy )
{
if ( (cx > 0) )
{
CRect rect;
GetClientRect( &rect );
m_nWndHeight = rect.Height();
m_nWndWidth = rect.Width();
ReArrangeWholeLayout();
if ((m_bColResizing == true) && (rect.Width() > 0))
{
//TODO : more resize options (for now only last col)
int nCols = GetColumnCount();
//TRACE(_T("CListCtrlCommands::OnSize() : nCols=%d, rc.with()=%d\n"),
nCols, rect.Width());
if (nCols >= 1){
int nColInterval = 0, nLastColSize = 0, nClientWidth = 0;
for (int i = 0; i < nCols - 1; i++)
{
nLastColSize = GetColumnWidth(i);
nColInterval += nLastColSize;
}
nClientWidth = rect.Width();
/*vlwTrace( "ncols=%d, nClientWidth=%d, nLastColSize=%d,
nColInterval=%d\r\n",
nCols, nClientWidth, nLastColSize, nColInterval);*/
SetColumnWidth(nCols - 1, (nClientWidth - nColInterval) );
}
}
SCROLLINFO scrlinfo = {0};
scrlinfo.cbSize = sizeof(scrlinfo);
scrlinfo.fMask = SIF_PAGE|SIF_RANGE;
scrlinfo.nMax = m_nListHeight;
scrlinfo.nMin = 0;
scrlinfo.nPage = m_nWndHeight + 1;
scrlinfo.nPos = 0;
SetScrollInfo(SB_VERT,&scrlinfo);
CListCtrl::OnSize(nType, cx, cy);
TRACE(_T("/OnSize : m_nListHeight=%d, m_nWndHeight=%d\n"),
m_nListHeight, m_nWndHeight);
}
}
BOOL CListCtrlCommands::OnEraseBkgnd(CDC* pDC)
{
UNUSED_ALWAYS(pDC);
return TRUE;
}