A custom list view control with custom scrollbar control, using WTL

A custom list view control with custom scrollbar control, using WTL

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_LBUTTONDOWNWM_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.

= My list control didn't get custom draw messages?

Probably, you have forgotten to add the REFLECT_NOTIFICATIONS macro to your dialog's message map, haven't you?

= I didn't get the WM_CTLCOLORSCROLLBAR message on WinCE?

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.

= How to show the full-screen dialog on Pocket PC?

See the SHInitDialog function in MSDN and the example in my code.

= Why don't you use the GetCursorPos function?

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, didn't you know how to override the background for EDIT control on the WinCE platform?

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

posted @ 2022-12-13 15:22  小风风的博客  阅读(27)  评论(0编辑  收藏  举报