Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

How to skin CListCtrl including scrollbars and column headers

0.00/5 (No votes)
22 Nov 2003 18  
This article will show you how to skin a CListCtrl including the CHeaderCtrl and the scrollbars. You will be able to totally customize the look and feel of almost all the aspects of a CListCtrl.

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.

  1. 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.
  2. 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.
  3. 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.
  4. 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 CListCtrls, 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()
{
    ...

    //Important! You must call this first

    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.

//Add this line of code in the CSkinHeaderCtrl.h

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.

//add an include for the CMemDC class

#include "memdc.h"


...

//I added the following function. (This function does not do anything,

//but we need it so that we can get the properties of each column

//header in the OnPaint function.) If we don't have this function,

//our code in the OnPaint handler will crash.

void CSkinHeaderCtrl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{

}

...

//First I did an overrode the OnPaint function

void CSkinHeaderCtrl::OnPaint() 
{
    CPaintDC dc(this); // device context for painting

    
    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;
        
        //make sure we draw the start piece

        //on the first item so it has a left border


        //For the following items we will just use the

        //right border of the previous items as the left

        //border

        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);
        
        //span the bitmap for the width of the column header item

        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);
        
        
        //draw the end piece of the column header

        CBitmap* pOldBitmap3 = bitmapDC.SelectObject(&bitmap3);
        memDC.BitBlt((rect.right-2), 0, 2, 12, 
                             &bitmapDC,0,0,SRCCOPY);
        bitmapDC.SelectObject(pOldBitmap3);
        
        //Get all the info for the current

        //item so we can draw the text to it

        //in the desired font and style

        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.

//CSkinVerticleScrollbar.h file


...

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();
    
    ...
//CSkinVerticleScrollbar.cpp file


...

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;
    
    //draw the background (span)

    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;
    
    //draw the down arrow of the scrollbar

    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;

        //draw the down arrow of the scrollbar

    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;

    //draw the thumb control

    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; //-13 so mouse is in middle of thumb

        
        double nMax = pList->GetScrollLimit(SB_VERT);
        int nPos = pList->GetScrollPos(SB_VERT);

        double nHeight = clientRect.Height()-98;
        double nVar = nMax;
        dbThumbInterval = nHeight/nVar;

        //figure out how many times to scroll total from top

        //then minus the current position from it

        
        int nScrollTimes = (int)((nThumbTop-36)/dbThumbInterval)-nPos;
        
        //grab the row height dynamically

        //so if the font size or type changes

        //our scroll will still work properly

        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() 
{
    //use our custom CHeaderCtrl

    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.

//CSkinListCtrl.h file

#include "SkinHeaderCtrl.h"

#include "SkinHorizontalScrollbar.h"

#include "SkinVerticleScrollbar.h"


...

public:
    CSkinHeaderCtrl m_SkinHeaderCtrl;
    CSkinVerticleScrollbar m_SkinVerticleScrollbar;
    CSkinHorizontalScrollbar m_SkinHorizontalScrollbar;
//CSkinListCtrl.cpp file

void CSkinListCtrl::Init()
{
    //another way to hide scrollbars

    InitializeFlatSB(m_hWnd);
    FlatSB_EnableScrollBar(m_hWnd, SB_BOTH, ESB_DISABLE_BOTH);

    CWnd* pParent = GetParent();

    //Create scrollbars at runtime

    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;

    //call this to position the scrollbars properly

    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;
    //return CListCtrl::OnEraseBkgnd(pDC);

}


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

  1. 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).
  2. 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.
  3. Make the CSkinScrollbar and CSkinHeaderCtrl classes work with any size of bitmap without the need for changing code in these classes.
  4. Somehow have the CSkinList::Init function run by itself so that we don't have to call m_SkinList.Init(); ourselves.
  5. 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

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