YOU CAN CODE!

 

With The Case Of UCanCode.net  Release The Power OF  Visual C++ !   HomeProducts | PurchaseSupport | Downloads  
Download Evaluation
Pricing & Purchase?
E-XD++Visual C++/ MFC Products
Overview
Features Tour 
Electronic Form Solution
Visualization & HMI Solution
Power system HMI Solution
CAD Drawing and Printing Solution

Bar code labeling Solution
Workflow Solution

Coal industry HMI Solution
Instrumentation Gauge Solution

Report Printing Solution
Graphical modeling Solution
GIS mapping solution

Visio graphics solution
Industrial control SCADA &HMI Solution
BPM business process Solution

Industrial monitoring Solution
Flowchart and diagramming Solution
Organization Diagram Solution

Graphic editor Source Code
UML drawing editor Source Code
Map Diagramming Solution

Architectural Graphic Drawing Solution
Request Evaluation
Purchase
ActiveX COM Products
Overview
Download
Purchase
Technical Support
  General Q & A
Discussion Board
Contact Us

Links

Get Ready to Unleash the Power of UCanCode .NET


UCanCode Software focuses on general application software development. We provide complete solution for developers. No matter you want to develop a simple database workflow application, or an large flow/diagram based system, our product will provide a complete solution for you. Our product had been used by hundreds of top companies around the world!

"100% source code provided! Free you from not daring to use components because of unable to master the key technology of components!"


Visual C++ MFC Programming: Skin CListCtrl with InsertColumn SetItemText and ListView_SetExtendedListViewStyle

 
 

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 tucancode.net 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 tucancode.net 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.

Collapse Copy Code
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.

Collapse Copy Code
//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.

Collapse Copy Code
//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.mucancode.net = 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.mucancode.net = 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 ucancode.neted 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 tucancode.net. 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.

Collapse Copy Code
//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();
    
    ...
Collapse Copy Code
//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.

Collapse Copy Code
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.

Collapse Copy Code
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.

Collapse Copy 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.

Collapse Copy Code
//CSkinListCtrl.h file
#include "SkinHeaderCtrl.h"
#include "SkinHorizontalScrollbar.h"
#include "SkinVerticleScrollbar.h"

...

public:
    CSkinHeaderCtrl m_SkinHeaderCtrl;
    CSkinVerticleScrollbar m_SkinVerticleScrollbar;
    CSkinHorizontalScrollbar m_SkinHorizontalScrollbar;
Collapse Copy Code
//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.)

Collapse Copy Code
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.

Collapse Copy 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 ucancode.neting 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.

 

 

Copyright ?1998-2024 UCanCode.Net Software , all rights reserved.
Other product and company names herein may be the trademarks of their respective owners.

Please direct your questions or comments to webmaster@ucancode.net