A custom list view control with custom scrollbar control, using WTL
A custom list view control with custom scrollbar control, using WTL
Introduction
This article is intended to show an opportunity for customizing the list view control (report view, single-line mode) using the custom scrollbar control. I hope this article will help someone spend his/her time with much pleasure than just surfing the Net.
The main idea of control customization is not as an innovation. Usually, we need to change the appearance of the control leaving its internal behavior.
The plan of action is quite simple: create the control dynamically (or subclass the existing one), and override the required message handlers – for instance, paint messages (owner-draw, custom-draw, or WM_PAINT
). It depends on the control (if it supports the required paint schemes to make its customization easier) and how radically we would like to change it.
Let's start with the scrollbar control. As usual, we create the legacy of the standard control (CScrollBar
) and handle some of the required messages in our own way. CScrollBar
control supports neither owner-draw (WM_DRAWITEM
) nor custom-draw (NM_CUSTOMDRAW
) specifications, so we are forced to draw it totally by ourselves (see the WM_PAINT
handler). Evidently, we need to handle the standard mouse messages (WM_LBUTTONDOWN
, WM_LBUTTONUP
, and WM_MOUSEMOVE
) to update the thump position when necessary. To emulate the behavior of the standard scrollbar, we add support for the whole family of WM_VSCROLL
requests (see MSDN for details). You can notice that the standard scrollbar is able to handle one click onto its elements for handling them repeatedly. To achieve the same functionality, we just need to setup a timer when the mouse down event is proceeding. We also shouldn’t forget about mouse capturing to get mouse messages even if the mouse cursor is outside the scrollbar control.
And one more trick is connected with the handling of the SBM_SETSCROLLINFO
message. I have found that when the control receives that message, it looks like the standard control until the repaint operation is called. So, I was forced to handle this notification with the calling of our own procedure of painting when required (depending on the fRedraw
flag).
LRESULT CMyScrollbar::OnSetScrollInfo( UINT uMsg,
WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/ )
{
const BOOL fRedraw = ( BOOL ) wParam;
LRESULT res = DefWindowProc( uMsg, ( WPARAM ) FALSE, lParam );
if ( fRedraw )
{
Invalidate();
}
return res;
}
To make the custom scrollbar control more convenient, there is the SetBuddy
method that allows to setup a buddy window for sending WM_VSCROLL
-notifications (instead of the parent window, by default).
And the last think related to the scrollbar control is resources. If you study the resource, you will find out that the control uses four bitmaps to show:
- upper arrow (
IDB_SCROLL_UP
), - back rectangle where scroll thumb could be moved (
IDB_SCROLL_BACK
), - scroll thumb itself (
IDB_SCROLL_THUMB
), - down arrow (
IDB_SCROLL_DOWN
).
So, as you can see, there is nothing difficult to do.
A bit more about problems we get with the list view control. As we want to use external scrolling, we have to use the LVS_NOSCROLL
style. But as a result, the control stops handling scrolling at all. What should we do? It seems that the only way is to emulate scrolling by handling the WM_VSCROLL
message. To implement it, I decided to remember the current first visible line position and change it when scrolling is required (see the m_IndexOffset
member of CMyListCtrl
). Sounds difficult? But it was the biggest problem as the other ones can be solved easily.
To change the appearance of the control, we handle custom draw notifications. To support scrolling via keyboard, we also handle messages from the keyboard.
A couple words why I handle painting in the OnPrePaint
method. As you know, we can return the CDRF_NOTIFYITEMDRAW
value to receive additional messages for each item which has to be drawn. The problem is in the way we use the original list view control. As we refused scrolling by means of the LVS_NOSCROLL
style, we work on the first GetCountPerPage
items. And every time we get a WM_PAINT
message for the whole client area, it means these items should be drawn. The problems occur with selected items as they are redrawn by means of their absolute indices. It means that every time we get a OnItemPrePaint
message, we wouldn't recognize which item must be drawn: with relative index (see the dwItemSpec
field of the LPNMLVCUSTOMDRAW
structure) or with absolute one. So, I decided to kill both hares with one shot: don’t care about item indices, we need to draw and make painting as simple as possible (it is really simple as we paint every item by means of a cycle for counting from the first visible). To make it more effective, I exclude items which don't have an intersection with the current clipping region (GetClipBox
).
Don’t forget about using the WM_MEASUREITEM
message, if it is necessary to change the default height of the row in the list control.
Finally, WM_CTLCOLORXXX
messages are used to retrieve the brushes for controls (scrollbar, list, and radio buttons). Each control uses its own brush as they are created with their own specific rectangles (by means of the CreatePatternBrush
system call).
You will find there two different types of projects – for the desktop version of the application and the Pocket PC one. You can use the version you need more. As with using WTL, the code for both of them is the same.
I would like to thank Hyungchul Shin, the author of the article Transparent controls with custom image backgrounds on Pocket PC, as the technique of creating custom backgrounds is fully adopted from that article.
That is all for today! Be well!
Typical problems:
= I didn't get the WM_MEASUREITEM message and therefore I couldn't change the height of list's row?It probably happens when you get the existing instance of the control and do subclassing. The WM_MEASUREITEM
message is sent only once when the control is created. So, when you create the list view control by hand, you will get this message; when it is created automatically, you won't. Actually, I have a suggestion to cause the system resend a WM_MEASUREITEM
message by means of sending the WM_WINDOWPOSCHANGED
message, but I wasn't brave enough to test it. You can try it out.
Probably, you have forgotten to add the REFLECT_NOTIFICATIONS
macro to your dialog's message map, haven't you?
Yes, it is really true. The system just doesn't send this message for custom scrollbar controls (probably it is a bug). So, the only choice is emulate its sending by calling this message from the WM_PAINT
or WM_ERASEBKGND
handlers.
See the SHInitDialog
function in MSDN and the example in my code.
As the code is the same for Pocket PC and desktop versions. If you try the GetCursorPos
call on WinCE, you will certainly get the error saying "Function is not supported". So, we have to use our own method to know where the mouse cursor is. If you take a look at the code, you will see that my idea consists in remembering the cursor position in the WM_LBUTTONDOWN
handler and updating it in the WM_MOUSEMOVE
one.
Accidentally, I know :). One time I have discovered that there is no way to force the EDIT control to change its background (on WinCE) – it was always white. All my attempts to change its behavior were unsuccessful until two days of useless curses, I read a message on one of the forums. It was said that for EDIT controls, we have to act in a different way – "Read MSDN and make vice versa" :). The sample code follows:
LRESULT AppDlg::OnCtlColor( UINT uMsg, WPARAM wParam,
LPARAM lParam, BOOL& bHandled )
{
...
// Make sure you work with edit control
if ( ... )
{
HBRUSH hBrush = getBkBrush( editCtrl );
::SetWindowLong( m_hWnd, DWL_MSGRESULT, ( LONG ) hBrush );
res = TRUE;
}
...
}
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