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 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
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.
Collapse
Copy Code
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.
Collapse
Copy Code
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
#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.mucancode.net = 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.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
...
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
...
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
.
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;
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.
Collapse
Copy 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.
Collapse
Copy Code
#include "SkinHeaderCtrl.h"
#include "SkinHorizontalScrollbar.h"
#include "SkinVerticleScrollbar.h"
...
public:
CSkinHeaderCtrl m_SkinHeaderCtrl;
CSkinVerticleScrollbar m_SkinVerticleScrollbar;
CSkinHorizontalScrollbar m_SkinHorizontalScrollbar;
Collapse
Copy Code
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.)
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;
}
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
- 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.