Introduction
I have been programming in MFC for about five years and it has always been a problem for me to find help in the area of advanced user interfaces. I am developing version 3.0 of a very complex DJ audio application and I needed to have a slick-looking interface. Skinning the application itself is no big deal, but I needed to have lists in my application that did everything a CListCtrl
could do, but also take on a custom look and feel.
Now, I had a few choices.
- I could buy a third-party list control that had all the functionality of a
CListCtrl
and allowed me to skin it, but I couldn't even find one anywhere for any price.
- I could use SkinMagic, ActiveSkin or DirectSkin, but those products are slow, expensive, and don't skin
CListCtrl
controls without flickering or other annoying bugs.
- I could develop my own list control from scratch and try to add in the dragging and dropping of column headers, virtual list support, multiple columns, highlighting, drag and drop, resizing of columns, sorting and so on, but this would take forever.
- I could just try to skin the existing
CListCtrl
.
Obviously, I chose the latter because time is of the essence.
If I would have had this source code before I undertook this task myself, it would have saved me many hours of work even with the not so clean code. So, I hope this helps a few of you out there.
It is very hard to explain how to do this because there are so many different elements working together, so you are probably better off just checking out the demo project. However this article should give a good idea of how I did this.
The code in this article was developed on Windows 2000 SP3 using Microsoft Visual C++ 6.0 with common controls DLL file version 5.81.4916.400 and product version 5.50.4916.400. This code was also tested on Windows 98 Second Edition.
Chronological Order of Efforts
First, I had to find a way to customize the existing column headers or make my own instead of settling for the typical grey ones. So, I derived a class from CHeaderCtrl
and did an override for the OnPaint
function and used bitmaps in place of the ugly grey headers and subclassed the CHeaderCtrl
in my CSkinListCtrl
class. This worked while retaining all the functionality of a CHeaderCtrl
!
Next, I had to find a way to customize the existing scrollbars or else make my own. So, I tried to subclass the CScrollbar
class; whenever I tried to use the GetScollbarCtrl()
function from the CListCtrl
, it returned null
. Obviously, the scrollbars are not real. Unfortunately, this means I had to hide the existing scrollbars and create my own (a lot more work than just skinning the existing ones).
I began to try to hide the scrollbars of the CListCtrl
and then somehow create my own. I found a solution for hiding the scrollbars in a CListCtrl
on the CodeGuru message boards from Filbert Fox. This worked great, so my next task was to create my own scrollbars.
I chose to derive a class from CStatic
and create the scrollbar from scratch using bitmaps. It took a while and a lot of tweaking, but I got the custom scrollbar created and working including the wheel mouse, arrow keys, and pageup/pagedown keys.
Now, I can't tell you how happy I was when I got this working! Some cool additions I hope to add to this source code, which would be fairly easy to add, would be rollover images for the up/down arrows, thumb control, and column header controls.
How to use the source code in your own projects
To use this source code for you own CListCtrl
s, all you have to do is copy the files (CSkinListCtrl.h, CSkinListCtrl.cpp, CSkinHeaderCtrl.h, CSkinHeaderCtrl.cpp, CSkinHorizontalScrollbar.h, CSkinHorizontalScrollbar.cpp, CSkinVerticleScrollbar.h, and CSkinVerticleScrollbar.cpp) into your project and add the files to your project (Project, Add to Project, files...). Now go into each of the CPP files you just copied and change the #include "SkinList.h"
to #include "<yourapp>.h"
.
Next you must have some graphics you would like to use for your scrollbars and headerctrl (Look at my graphics in the res folder to see how I cut them up to make them work properly). Import those BMP graphics into your resource tab and give them all meaningful names. Then you will have to go through the source code in the CSkinVerticleScrollbar
, CSkinHorizontalScrollbar
, and CSkinHeaderCtrl
classes and change the code to make it work with your bitmaps. There are hardcoded numbers in these classes used to position the bitmaps properly. For example, my left arrow is 26 pixels wide, so my thumb control is positioned 25 pixels from the left. You will see the correlation between the numbers and the size of the graphics when you look at the source code. It will take a bit of playing around to get it working with your graphics especially if your design is a lot different, but it should be a lot easier than having to write all this code from scratch.
Now once you are done all that, all you have to do is create your CListCtrl
controls on your dialog in the resource editor within Visual Studio. When you create a member variable for your CListCtrl
just make sure to select CSkinListCtrl
as the control class instead of CListCtrl
.
Note: If you don't see CSkinListCtrl
as a choice for your control class when you create the member variable in the class wizard, then you must delete the .clw file in the folder of your project and then the next time you open the class wizard (Ctrl+W) it will rebuild the .clw file using the classes you added and you will then see the CSkinListCtrl
as a choice for a control class.
Now in order for everything to work, you must add the line m_SkinList.Init();
. This is very important because this Init
function is what creates the scrollbars and positions them to the CListCtrl
. You must call this in your OnInitDialog
function before you try to use the list of course.
BOOL CSkinListDlg::OnInitDialog()
{
...
m_SkinList.Init();
m_SkinList.SetBkColor(RGB(76,85,118));
m_SkinList.SetTextColor(RGB(222,222,222));
LOGFONT lf;
memset(&lf, 0, sizeof(LOGFONT));
lf.lfHeight = 12;
strcpy(lf.lfFaceName, "Verdana");
font.CreateFontIndirect(&lf);
m_SkinList.SetFont(&font, TRUE);
m_SkinList.InsertColumn(0, "BLANK", LVCFMT_LEFT, 0);
m_SkinList.InsertColumn(1, "SONG", LVCFMT_LEFT, 100);
m_SkinList.InsertColumn(2, "ARTIST", LVCFMT_LEFT, 100);
m_SkinList.InsertColumn(3, "GENRE", LVCFMT_LEFT, 100);
m_SkinList.SetRedraw(FALSE);
CString cszItem;
for(int i=0; i<1000; i++)
{
cszItem.Format("%d - %s", i,
"Matthew Good - Near Fantastica");
m_SkinList.InsertItem(i, cszItem);
m_SkinList.SetItemText(i, 1, cszItem);
m_SkinList.SetItemText(i, 2, "Matthew Good");
m_SkinList.SetItemText(i, 3, "Rock");
m_SkinList.SetRedraw(TRUE);
ListView_SetExtendedListViewStyle(m_SkinList.m_hWnd,
LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP);
...
}
How I customized the CHeaderCtrl
Here we will set up the CSkinHeaderCtrl
class that we made. We have to skin the header control using our own graphics.
public:
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
Now, override the OnPaint
event in the CSkinHeaderCtrl
class and write code to skin the column headers with our own graphics. If you use your own graphics and they are different sizes, you will have to modify the code in the OnPaint
handler to draw your bitmaps correctly.
#include "memdc.h"
...
void CSkinHeaderCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
}
...
void CSkinHeaderCtrl::OnPaint()
{
CPaintDC dc(this);
CRect rect, rectItem, clientRect;
GetClientRect(&rect);
GetClientRect(&clientRect);
CMemDC memDC(&dc, rect);
CDC bitmapDC;
bitmapDC.CreateCompatibleDC(&dc);
memDC.FillSolidRect(&rect, RGB(76,85,118));
CBitmap bitmapSpan;
bitmapSpan.LoadBitmap(IDB_COLUMNHEADER_SPAN);
CBitmap* pOldBitmapSpan = bitmapDC.SelectObject(&bitmapSpan);
memDC.StretchBlt(rect.left+2, 0, nWidth, 1,
&bitmapDC, 0,0, 1, 12, SRCCOPY);
bitmapDC.SelectObject(pOldBitmapSpan);
bitmapSpan.DeleteObject();
int nItems = GetItemCount();
CBitmap bitmap;
CBitmap bitmap2;
CBitmap bitmap3;
bitmap.LoadBitmap(IDB_COLUMNHEADER_START);
bitmap2.LoadBitmap(IDB_COLUMNHEADER_SPAN);
bitmap3.LoadBitmap(IDB_COLUMNHEADER_END);
for(int i = 0; i <nItems; i++)
{
TCHAR buf1[256];
HD_ITEM hditem1;
hditem1.mask = HDI_TEXT | HDI_FORMAT | HDI_ORDER;
hditem1.pszText = buf1;
hditem1.cchTextMax = 255;
GetItem( i, &hditem1 );
GetItemRect(i, &rect);
CBitmap* pOldBitmap = NULL;
if(hditem1.iOrder==0)
{
pOldBitmap = bitmapDC.SelectObject(&bitmap);
memDC.BitBlt(rect.left,rect.top,2,12,
&bitmapDC,0,0,SRCCOPY);
}
else
{
memDC.BitBlt(rect.left-1,rect.top,2,12,
&bitmapDC,0,0,SRCCOPY);
pOldBitmap = bitmapDC.SelectObject(&bitmap2);
memDC.BitBlt(rect.left+1,rect.top,1,12,
&bitmapDC,0,0,SRCCOPY);
}
bitmapDC.SelectObject(pOldBitmap);
int nWidth = rect.Width() - 4;
CBitmap* pOldBitmap2 = bitmapDC.SelectObject(&bitmap2);
memDC.StretchBlt(rect.left+2, 0, nWidth, 1,
&bitmapDC, 0,0, 1, 12, SRCCOPY);
bitmapDC.SelectObject(pOldBitmap2);
CBitmap* pOldBitmap3 = bitmapDC.SelectObject(&bitmap3);
memDC.BitBlt((rect.right-2), 0, 2, 12,
&bitmapDC,0,0,SRCCOPY);
bitmapDC.SelectObject(pOldBitmap3);
DRAWITEMSTRUCT DrawItemStruct;
GetItemRect(i, &rectItem);
DrawItemStruct.CtlType = 100;
DrawItemStruct.hDC = dc.GetSafeHdc();
DrawItemStruct.itemAction = ODA_DRAWENTIRE;
DrawItemStruct.hwndItem = GetSafeHwnd();
DrawItemStruct.rcItem = rectItem;
DrawItemStruct.itemID = i;
DrawItem(&DrawItemStruct);
UINT uFormat = DT_SINGLELINE | DT_NOPREFIX
| DT_TOP |DT_CENTER | DT_END_ELLIPSIS ;
CFont font;
LOGFONT lf;
memset(&lf, 0, sizeof(LOGFONT));
lf.lfHeight = 8;
strcpy(lf.lfFaceName, "Sevenet 7");
font.CreateFontIndirect(&lf);
CFont* def_font = memDC.SelectObject(&font);
memDC.SetBkMode(TRANSPARENT);
rectItem.DeflateRect(2,2,2,2);
TCHAR buf[256];
HD_ITEM hditem;
hditem.mask = HDI_TEXT | HDI_FORMAT | HDI_ORDER;
hditem.pszText = buf;
hditem.cchTextMax = 255;
GetItem( DrawItemStruct.itemID, &hditem );
memDC.DrawText(buf, &rectItem, uFormat);
memDC.SelectObject(def_font);
font.DeleteObject();
}
}
That pretty much does it for the CSkinHeaderCtrl
. It was relatively easy to custom draw the CHeaderCtrl
. It surprised me how easy it was because I saw so many posts on the message boards that asked how to do this and none had any replies. To include your own graphics you will obviously have to modify this code in order to get it work properly for your design, but that is fairly straight forward now that you have the framework code right here.
How I created the CSkinVerticleScrollbar and CSkinHorizontalScrollbar controls
Creating the vertical and horizontal scrollbar controls and making them work in conjunction with the CListCtrl
was obviously the most daunting task. Basically what I did was create a scrollbar control out of bitmaps using a CStatic
and the base class. I added code to allow for the movement of the thumb control using the drag and drop and also code to handle clicks on the arrow buttons and channel area. I also added code to update the thumb position based on the ScrollPos
of the list. Doing this allowed to keep all the original functionality of a CListCtrl
in terms of the wheel mouse, pgup/pgdown, arrow, home, and end keys. The horizontal and vertical scrollbar controls are basically the same, so I am just going to show you the code for the CSkinVerticleScrollbar
control for simplicity.
I won't post all the code here because there is too much, but I will try to explain what I did in order to make the scrollbars work with the CListCtrl
, showing you the most important pieces of the code.
First I overrode the OnPaint
handler so that I could draw the scrollbar using the graphics I wanted to use.
...
public:
CListCtrl* pList;
void LimitThumbPosition();
void Draw();
void UpdateThumbPosition();
bool bMouseDownArrowUp, bMouseDownArrowDown;
bool bDragging;
bool bMouseDown;
int nThumbTop;
double dbThumbInterval;
void ScrollDown();
void ScrollUp();
void PageUp();
void PageDown();
...
...
void CSkinVerticleScrollbar::OnPaint()
{
CPaintDC dc(this);
Draw();
}
void CSkinVerticleScrollbar::Draw()
{
CClientDC dc(this);
CRect clientRect;
GetClientRect(&clientRect);
CMemDC memDC(&dc, &clientRect);
memDC.FillSolidRect(&clientRect, RGB(74,82,107));
CDC bitmapDC;
bitmapDC.CreateCompatibleDC(&dc);
CBitmap bitmap;
bitmap.LoadBitmap(IDB_VERTICLE_SCROLLBAR_TOP);
CBitmap* pOldBitmap = bitmapDC.SelectObject(&bitmap);
memDC.BitBlt(clientRect.left,clientRect.top,12,11,
&bitmapDC,0,0,SRCCOPY);
bitmapDC.SelectObject(pOldBitmap);
bitmap.DeleteObject();
pOldBitmap = NULL;
bitmap.LoadBitmap(IDB_VERTICLE_SCROLLBAR_UPARROW);
pOldBitmap = bitmapDC.SelectObject(&bitmap);
memDC.BitBlt(clientRect.left,clientRect.top+11,12,26,
&bitmapDC,0,0,SRCCOPY);
bitmapDC.SelectObject(pOldBitmap);
bitmap.DeleteObject();
pOldBitmap = NULL;
bitmap.LoadBitmap(IDB_VERTICLE_SCROLLBAR_SPAN);
pOldBitmap = bitmapDC.SelectObject(&bitmap);
int nHeight = clientRect.Height() - 37;
memDC.StretchBlt(clientRect.left, clientRect.top+37,
12,nHeight,&bitmapDC, 0,0, 12, 1, SRCCOPY);
bitmapDC.SelectObject(pOldBitmap);
bitmap.DeleteObject();
pOldBitmap = NULL;
bitmap.LoadBitmap(IDB_VERTICLE_SCROLLBAR_DOWNARROW);
pOldBitmap = bitmapDC.SelectObject(&bitmap);
memDC.BitBlt(clientRect.left,nHeight,12,26,&bitmapDC,0,0,SRCCOPY);
bitmapDC.SelectObject(pOldBitmap);
bitmap.DeleteObject();
pOldBitmap = NULL;
bitmap.LoadBitmap(IDB_VERTICLE_SCROLLBAR_BOTTOM);
pOldBitmap = bitmapDC.SelectObject(&bitmap);
memDC.BitBlt(clientRect.left+1,nHeight+26,11,11,
&bitmapDC,0,0,SRCCOPY);
bitmapDC.SelectObject(pOldBitmap);
bitmap.DeleteObject();
pOldBitmap = NULL;
bitmap.LoadBitmap(IDB_VERTICLE_SCROLLBAR_THUMB);
pOldBitmap = bitmapDC.SelectObject(&bitmap);
memDC.BitBlt(clientRect.left,clientRect.top+nThumbTop,12,26,
&bitmapDC,0,0,SRCCOPY);
bitmapDC.SelectObject(pOldBitmap);
bitmap.DeleteObject();
pOldBitmap = NULL;
}
Next I wrote a function that will update the scrollbar thumb graphic's position based on the ScrollPos
of the CListCtrl
.
void CSkinVerticleScrollbar::UpdateThumbPosition()
{
CRect clientRect;
GetClientRect(&clientRect);
double nPos = pList->GetScrollPos(SB_VERT);
double nMax = pList->GetScrollLimit(SB_VERT);
double nHeight = (clientRect.Height()-98);
double nVar = nMax;
dbThumbInterval = nHeight/nVar;
double nNewdbValue = (dbThumbInterval * nPos);
int nNewValue = (int)nNewdbValue;
nThumbTop = 36+nNewValue;
LimitThumbPosition();
Draw();
}
void CSkinVerticleScrollbar::LimitThumbPosition()
{
CRect clientRect;
GetClientRect(&clientRect);
if(nThumbTop+26 > (clientRect.Height()-37))
{
nThumbTop = clientRect.Height()-62;
}
if(nThumbTop < (clientRect.top+36))
{
nThumbTop = clientRect.top+36;
}
}
Then I wrote code to handle the mouse events for when the user drag and drops the thumb control to scroll the list and for when they click (or click and hold down) on the scrollbar arrows.
void CSkinVerticleScrollbar::PageDown()
{
pList->SendMessage(WM_VSCROLL, MAKELONG(SB_PAGEDOWN,0),NULL);
UpdateThumbPosition();
}
void CSkinVerticleScrollbar::PageUp()
{
pList->SendMessage(WM_VSCROLL, MAKELONG(SB_PAGEUP,0),NULL);
UpdateThumbPosition();
}
void CSkinVerticleScrollbar::ScrollUp()
{
pList->SendMessage(WM_VSCROLL, MAKELONG(SB_LINEUP,0),NULL);
UpdateThumbPosition();
}
void CSkinVerticleScrollbar::ScrollDown()
{
pList->SendMessage(WM_VSCROLL, MAKELONG(SB_LINEDOWN,0),NULL);
UpdateThumbPosition();
}
void CSkinVerticleScrollbar::OnLButtonDown(UINT nFlags, CPoint point)
{
SetCapture();
CRect clientRect;
GetClientRect(&clientRect);
int nHeight = clientRect.Height() - 37;
CRect rectUpArrow(0,11,12,37);
CRect rectDownArrow(0,nHeight,12,nHeight+26);
CRect rectThumb(0,nThumbTop,12,nThumbTop+26);
if(rectThumb.PtInRect(point))
{
bMouseDown = true;
}
if(rectDownArrow.PtInRect(point))
{
bMouseDownArrowDown = true;
SetTimer(2,250,NULL);
}
if(rectUpArrow.PtInRect(point))
{
bMouseDownArrowUp = true;
SetTimer(2,250,NULL);
}
CStatic::OnLButtonDown(nFlags, point);
}
void CSkinVerticleScrollbar::OnLButtonUp(UINT nFlags, CPoint point)
{
UpdateThumbPosition();
KillTimer(1);
ReleaseCapture();
bool bInChannel = true;
CRect clientRect;
GetClientRect(&clientRect);
int nHeight = clientRect.Height() - 37;
CRect rectUpArrow(0,11,12,37);
CRect rectDownArrow(0,nHeight,12,nHeight+26);
CRect rectThumb(0,nThumbTop,12,nThumbTop+26);
if(rectUpArrow.PtInRect(point) && bMouseDownArrowUp)
{
ScrollUp();
bInChannel = false;
}
if(rectDownArrow.PtInRect(point) && bMouseDownArrowDown)
{
ScrollDown();
bInChannel = false;
}
if(rectThumb.PtInRect(point))
{
bInChannel = false;
}
if(bInChannel == true && !bMouseDown)
{
if(point.y > nThumbTop)
{
PageDown();
}
else
{
PageUp();
}
}
bMouseDown = false;
bDragging = false;
bMouseDownArrowUp = false;
bMouseDownArrowDown = false;
CStatic::OnLButtonUp(nFlags, point);
}
void CSkinVerticleScrollbar::OnMouseMove(UINT nFlags, CPoint point)
{
CRect clientRect;
GetClientRect(&clientRect);
if(bMouseDown)
{
int nPreviousThumbTop = nThumbTop;
nThumbTop = point.y-13;
double nMax = pList->GetScrollLimit(SB_VERT);
int nPos = pList->GetScrollPos(SB_VERT);
double nHeight = clientRect.Height()-98;
double nVar = nMax;
dbThumbInterval = nHeight/nVar;
int nScrollTimes = (int)((nThumbTop-36)/dbThumbInterval)-nPos;
CRect itemrect;
pList->GetItemRect(0,&itemrect, LVIR_BOUNDS);
CSize size;
size.cx = 0;
size.cy = nScrollTimes*itemrect.Height();
pList->Scroll(size);
LimitThumbPosition();
Draw();
}
CStatic::OnMouseMove(nFlags, point);
}
void CSkinVerticleScrollbar::OnTimer(UINT nIDEvent)
{
if(nIDEvent == 1)
{
if(bMouseDownArrowDown)
{
ScrollDown();
}
if(bMouseDownArrowUp)
{
ScrollUp();
}
}
else if(nIDEvent == 2)
{
if(bMouseDownArrowDown)
{
KillTimer(2);
SetTimer(1, 50, NULL);
}
if(bMouseDownArrowUp)
{
KillTimer(2);
SetTimer(1, 50, NULL);
}
}
CStatic::OnTimer(nIDEvent);
}
How I customized the CListCtrl
To customize the CListCtrl
, I needed to subclass the CHeaderCtrl
using the CSkinHeaderCtrl
class that I made, as well as hide the original scrollbars and then insert the ones I made in their place.
So first I subclassed the CHeaderCtrl
by overriding PreSubclassWindow
and adding the following code.
void CSkinListCtrl::PreSubclassWindow()
{
m_SkinHeaderCtrl.SubclassWindow(GetHeaderCtrl()->m_hWnd);
CListCtrl::PreSubclassWindow();
}
Next I wrote the Init
function that creates the scrollbars at runtime and ensures that the original scrollbars are hidden. I had to add code to take into account the size of the title bar in order to place the CStatic
scrollbars in the correct position in the PositionScrollBars()
function. If the window's appearance changes, I needed to ensure the scrollbars stayed in the correct place.
#include "SkinHeaderCtrl.h"
#include "SkinHorizontalScrollbar.h"
#include "SkinVerticleScrollbar.h"
...
public:
CSkinHeaderCtrl m_SkinHeaderCtrl;
CSkinVerticleScrollbar m_SkinVerticleScrollbar;
CSkinHorizontalScrollbar m_SkinHorizontalScrollbar;
void CSkinListCtrl::Init()
{
InitializeFlatSB(m_hWnd);
FlatSB_EnableScrollBar(m_hWnd, SB_BOTH, ESB_DISABLE_BOTH);
CWnd* pParent = GetParent();
m_SkinVerticleScrollbar.Create(NULL,
WS_CHILD|SS_LEFT|SS_NOTIFY|WS_VISIBLE|WS_GROUP,
CRect(0,0,0,0), pParent);
m_SkinHorizontalScrollbar.Create(NULL,
WS_CHILD|SS_LEFT|SS_NOTIFY|WS_VISIBLE|WS_GROUP,
CRect(0,0,0,0), pParent);
m_SkinVerticleScrollbar.pList = this;
m_SkinHorizontalScrollbar.pList = this;
PositionScrollBars();
}
void CSkinListCtrl::PositionScrollBars()
{
CWnd* pParent = GetParent();
CRect windowRect;
GetWindowRect(&windowRect);
int nTitleBarHeight = 0;
if(pParent->GetStyle() & WS_CAPTION)
nTitleBarHeight = GetSystemMetrics(SM_CYSIZE);
int nDialogFrameHeight = 0;
int nDialogFrameWidth = 0;
if((pParent->GetStyle() & WS_BORDER))
{
nDialogFrameHeight = GetSystemMetrics(SM_CYDLGFRAME);
nDialogFrameWidth = GetSystemMetrics(SM_CYDLGFRAME);
}
if(pParent->GetStyle() & WS_THICKFRAME)
{
nDialogFrameHeight+=1;
nDialogFrameWidth+=1;
}
pParent->ScreenToClient(&windowRect);
windowRect.top+=nTitleBarHeight+nDialogFrameHeight;
windowRect.bottom+=nTitleBarHeight+nDialogFrameHeight;
windowRect.left +=nDialogFrameWidth;
windowRect.right+=nDialogFrameWidth;
CRect vBar(windowRect.right-nDialogFrameWidth,
windowRect.top-nTitleBarHeight-nDialogFrameHeight,
windowRect.right+12-nDialogFrameWidth,
windowRect.bottom+12-nTitleBarHeight-nDialogFrameHeight);
CRect hBar(windowRect.left-nDialogFrameWidth,
windowRect.bottom-nTitleBarHeight-nDialogFrameHeight,
windowRect.right+1-nDialogFrameWidth,
windowRect.bottom+12-nTitleBarHeight-nDialogFrameHeight);
m_SkinVerticleScrollbar.SetWindowPos(NULL,
vBar.left,vBar.top,vBar.Width(),vBar.Height(),
SWP_NOZORDER);
m_SkinHorizontalScrollbar.SetWindowPos(NULL,
hBar.left,hBar.top,hBar.Width(),hBar.Height(),
SWP_NOZORDER);
m_SkinHorizontalScrollbar.UpdateThumbPosition();
m_SkinVerticleScrollbar.UpdateThumbPosition();
}
Next I added code to ensure that the thumb position of the scrollbars position themselves properly after the list scrolls in any way (keyboard commands, mouse wheel, etc.)
BOOL CSkinListCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
m_SkinVerticleScrollbar.UpdateThumbPosition();
m_SkinHorizontalScrollbar.UpdateThumbPosition();
return CListCtrl::OnMouseWheel(nFlags, zDelta, pt);
}
void CSkinListCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
m_SkinVerticleScrollbar.UpdateThumbPosition();
m_SkinHorizontalScrollbar.UpdateThumbPosition();
CListCtrl::OnKeyDown(nChar, nRepCnt, nFlags);
}
void CSkinListCtrl::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags)
{
m_SkinVerticleScrollbar.UpdateThumbPosition();
m_SkinHorizontalScrollbar.UpdateThumbPosition();
CListCtrl::OnKeyUp(nChar, nRepCnt, nFlags);
}
Finally I added code to ensure the list does not flicker when scrolling by overriding the OnEraseBkgnd
and OnPaint
handlers. I also have code in the CSkinListCtrl
class to make sure the highlight color of the rows stays a certain color rather than taking on the system colors, but I won't show that here. You can just look at the source code.
BOOL CSkinListCtrl::OnEraseBkgnd(CDC* pDC)
{
m_SkinVerticleScrollbar.UpdateThumbPosition();
m_SkinHorizontalScrollbar.UpdateThumbPosition();
return FALSE;
}
void CSkinListCtrl::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(&rect);
CMemDC memDC(&dc, rect);
CRect headerRect;
GetDlgItem(0)->GetWindowRect(&headerRect);
ScreenToClient(&headerRect);
dc.ExcludeClipRect(&headerRect);
CRect clip;
memDC.GetClipBox(&clip);
memDC.FillSolidRect(clip, RGB(76,85,118));
SetTextBkColor(RGB(76,85,118));
m_SkinVerticleScrollbar.UpdateThumbPosition();
m_SkinHorizontalScrollbar.UpdateThumbPosition();
DefWindowProc(WM_PAINT, (WPARAM)memDC->m_hDC, (LPARAM)0);
}
Points of Interest
- The method of hiding the scrollbars in this article still allows scrolling.
- Windows display appearances should not effect the list in any way.
- It is quite easy to skin the
CHeaderCtrl
using the OnPaint
method coupled with overriding the DrawItem
handler.
- Also, be aware that the
CSkinHeaderCtrl
and CSkinScrollbar
classes are hard coded to work with the size and type of bitmaps I am using. The code in these classes will have to be updated to work with the bitmaps you want to use.
- I saw messages all over the message boards asking how to customize a
CHeaderCtrl
and how to add customized scrollbars to a CListCtrl
and none of the posts were answered. This discouraged me, but I learned not to let that get to me. Just because it hasn't been done before or the source code hasn't been posted doesn't mean you can't be the first!! Never give up!
Things to Improve
- Add code to allow vertical scrolling to work properly on
ICON
and SMALL ICON
list types (currently this code only works with lists that have a view of REPORT
or LIST
).
- The drag and drop scrolling using the thumb control on the vertical scrollbar does not scroll as smoothly as the regular scrollbar when the listctrl shows a lot of items. Need to modify the code in the mousemove handler of the vertical scrollbar class to make the scrolling smoother.
- Make the
CSkinScrollbar
and CSkinHeaderCtrl
classes work with any size of bitmap without the need for changing code in these classes.
- Somehow have the
CSkinList::Init
function run by itself so that we don't have to call m_SkinList.Init();
ourselves.
- Add support for rollover images on the scrollbar arrows, thumb control, and column headers.
History
- July 1st - Uploaded new version of source code to fix the gap that appeared when resizing column headers.
- July 7th - Uploaded new version of source code which fixed a flickering problem with the headerctrl column headers when resizing them.
- July 27th - Huge changes! The scrolling is much more efficient. The code is much easier to integrate into your projects. The source code has been updated as well as most of the article. Once all the classes are in your project, you just need to subclass a
ListCtrl
using the CSkinListCtrl
and you can apply it to all your listctrl controls. Added a horizontal scrollbar. There is much less code in the scrollbar classes and it makes much more sense. Now the scrollbars are created at runtime, so you don't have to worry about them. It's much better.
- July 29th - Commented out the code to add the background image to listctrl in order to make the scrolling of the list "flicker free". The list now scrolls up and down without any flicker. Fixed a little bug where if you clicked and dragged to select multiple items in the list and it caused a horizontal or vertical scroll, the thumb position would not update.
- August 19th - Rewrote most of the code. Added horizontal scrollbar. The code is much more clean, efficient, and easier to understand. Removed flicker that occurred when scrolling. Rewrote article to try and explain in more detail how I did this.
- November 21st- Lots of enhancements and bug fixes! Used
StretchBlt
in place of for
loop around BitBlt
in Draw()
functions found in the CSkinHeaderCtrl
, CSkinVerticleScrollbar
and CSkinHorizontalScrollbar
classes to dramatically speed up drawing of gradient channels on scrollbars and column headers (This speeds up everything a lot!). Changed source code to demonstrate how to resize the CSkinListCtrl
dynamically while keeping scrollbars in proper position. Added function to ensure the scrollbars are positioned properly no matter what dialog styles are used. Added code to grab the height of a row in the CSkinListCtrl
dynamically so that the scrolling works no matter what font size or type is used. Added code to stop CSkinListCtrl
from crashing when using a list type other than REPORT
, although the vertical scrolling does not work with ICON
or SMALL ICON
yet :(. Added code to hide the thumb controls on both the vertical and horizontal scrollbars if there is nothing to scroll. A few small little bug fixes.
Credits