Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / MFC

Roll Your Own Scroll Bar

Rate me:
Please Sign up or sign in to vote.
4.92/5 (20 votes)
26 Oct 2010CPOL10 min read 82.4K   8.6K   56   42
XeScrollBar - Custom scroll bar, a replacement for Windows scroll bar

Introduction

Last year (2009), I needed a 'owner draw' scroll bar for UltiMate Grid I was using in my project. My first thought was to subclass CScrollBar and do my own painting, this turned out to be impossible. My next option was to search the internet for a custom scroll bar, but soon found that no one had bothered to create it, at least not for free, all the scroll bars I could find could not be used to replace the standard windows scroll bars because they did not behave in the way a standard scroll bar does. That is why I created this scroll bar I present here in this article.

My mission was simple, to create a custom scroll bar control that behaved exactly like a standard windows scroll bar but having a 'custom' look. I mean how hard can it be? As it turns out, not very hard but a lot of work, far more than I originally intended.

XeScrollBar image

Background

Many rarely used features of the standard Windows scroll bar are implemented in this scroll bar, including the context menu. I have taken great care to implement every little thing the standard scroll bar does. If you see something I've omitted or done incorrectly, please let me know. I've also implemented some features not found in the standard Windows scroll bar, e.g. mouse wheel support.

Using the Code

It should be noted that CXeScrollBar cannot be used to replace scroll bars in windows created with WS_HSCROLL and/or WS_VSCROLL window styles, the reason is that those scroll bars are part of that window non-client area, i.e., the scroll bars are not child windows but rather a part of the parent window.

CXeScrollBar can be used in place of the standard windows scroll bar (CScrollBar) in any situation except as noted above. Keep in mind though that CXeScrollBar is dependant on MFC.

Using this code in your own project is very simple. Just add XeScrollBar.h, XeScrollBar.cpp, XeScrollBarBase.h, XeScrollBar.cpp and XeScrollBar.rc to your project, copy the eight bitmap files (XSB_?_???.bmp) to the 'res' folder of your project. In your code, create CXeScrollBar object in same way you would create CScrollBar. The following code is an example of how the Create member is used.

C++
// Create CXeScrollBar scroll bars.
m_sbH.Create( WS_CHILD | WS_VISIBLE | SBS_HORZ, CRect(11,265,400,282), this, IDC_SB_H );
m_sbV.Create( WS_CHILD | WS_VISIBLE | SBS_VERT, CRect(419,11,436,260), this, IDC_SB_V );
// Note - m_sbH and m_sbV are of CXeScrollBar type in header file.

Another common use of CXeScrollBar is on a form or in a dialog box, in that case, the scroll bar(s) already exist and need to be replaced. The following code shows how you can replace existing scroll bars with CXeScrollBar. Typically you would do that in OnInitialUpdate() for a form view or in OnInitDialog() for a dialog. It's best to use the CreateFromExisting member to do this because that function takes care of all the boring details involved when creating a window to replace another; Window style, Control ID, size, position, scroll range, scroll position and scroll page size are copied. Z-order (Tab order) is also preserved, that is very important if the existing scroll bar has a WS_TABSTOP style set because when a new child window is created, it is placed last in the Z-order of child windows (of parent window).

C++
// Replace existing scroll bars on form or in dialog
// with CXeScrollBar scroll bars.
m_sbH.CreateFromExisting( this, IDC_XE_SB_H );
m_sbV.CreateFromExisting( this, IDC_XE_SB_V );
// Note - m_sbH and m_sbV are of CXeScrollBar type in header file.

Using CXeScrollbar in UltiMate Grid is also fairly simple. It's a simple matter of modifying the CUGHScroll and CUGVScroll classes a little bit: change base class from CScrollBar to CXeScrollBar. Please read 'Using CXeScrollBar in Ultimate grid.txt' I've included with the downloads above, for details on how to modify the source files and how to build and run the UltiMate Grid samples.

Please note that CXeScrollBar implements a full custom control, for that reason it is not possible to simply subclass a standard scroll bar, CXeScrollBar must be created using the Create member or the CreateFromExisting member.

How It Works

CXeScrollBar is implemented as two classes, CXeScrollBar and CXeScrollBarBase. CXeScrollBar does all the painting and CXeScrollBarBase implements all the 'business' logic needed. I decided early in the development to split the 'business' logic and the painting into two classes to make it easy to change the look of the scroll bar in the future.

Here is where the article title finally starts to make sense. If you are feeling artistic, you can create a scroll bar that looks just the way you want it to very simply. Either by creating the eight bitmaps needed for CXeScrollBar or deriving your own class from CXeScrollBarBase and do the painting needed any way you like.

CXeScrollBar uses four bitmaps for horizontal scroll bar and four bitmaps for vertical scroll bar. One for 'up' state, one for 'hot' state, one for 'down' state and one for 'disabled' state.

XeScrollBar UP image

XeScrollBar HOT image

XeScrollBar DOWN image

XeScrollBar DISABLED image

If you do decide to create bitmaps to replace the ones used by CXeScrollBar - read comments regarding how the code uses the bitmaps in XeScrollBar.cpp before you do. I've included the PhotoShop document I used to create the bitmaps in the download section above. Tip - create horizontal bitmaps first, then open those in PhotoShop and do rotate canvas 90 degrees and flip vertical to create the bitmaps for the vertical scroll bar.

The bitmaps are divided into logical sections: Left button, Left shaft, Thumb, Right shaft and Right button for horizontal scroll bars, Top button, Top shaft, Thumb, Bottom shaft and Bottom button for vertical scroll bars. The offsets in pixels into the bitmaps for each section are hard-coded in CXeScrollBar. For that reason, it is important be very careful when creating the bitmaps. Unless of course you are prepared to change the code in CXeScrollBar to suit your bitmaps.

The 'business' logic in CXeScrollBarBase provides the 'state' and size information of each scroll bar section for CXeScrollBar to use when painting. The following code shows what steps are needed when painting the scroll bar.

C++
XSB_EDRAWELEM eState;
const CRect *prcElem = 0;
stXSB_AREA stArea;
// loop through all UI elements.
for( int nElem = eTLbutton; nElem <= eThumb; nElem++ )
{
    stArea.eArea = (eXSB_AREA)nElem;
    
    // Get bounding rect of UI element to draw (client coords.)
    prcElem = GetUIelementDrawState( stArea.eArea, eState );
    if( !prcElem || eState == eNotDrawn )    // Rect empty or area not drawn?
        continue;
    
    // stArea.eArea identifies UI element to draw:
    //     eTLbutton or eTLchannel or eThumb or eBRchannel or eBRbutton.
    
    // eState identifes in what state the UI element is drawn:
    //     eDisabled or eNormal or eDown or eHot.
    
    // m_bHorizontal is TRUE if 'this' is a horizontal scroll bar.
    
    // Draw UI element to memory DC. (using prcElem rect).
    // Note - use m_bDrawGripper to determine if 'gripper' is drawn on thumb box.
    //        This is used to implement 'blinking' to show scroll bar has input
    //        focus. (every 500mS).
}

The DrawScrollBar( CDC* pDC ) member in CXeScrollBar uses the eState enum to select the bitmap to use for painting each section of the scroll bar.

The constructor in CXeScrollBar loads all bitmaps into memory from resources when called for the first time. The resource name of each bitmap is hard-coded. A reference counter is used to keep track of how many objects have been created. The bitmaps remain in memory until the last object is destroyed.

Points of Interest

I've taken great care to faithfully implement this scroll bar so it behaves exactly like the standard windows scroll bar does. There are a few minor (IMHO) areas I deviated from the standard.

The SBM_ENABLE_ARROWS message handler behaves a little bit differently that the standard scroll bar does. During testing, the standard scroll bar behaved strangely when ESB_DISABLE_LEFT, ESB_DISABLE_RIGHT, ESB_DISABLE_UP or ESB_DISABLE_DOWN flags are used, for that reason I've only implemented support for the ESB_ENABLE_BOTH and ESB_DISABLE_BOTH flags.

The SBM_GETSCROLLBARINFO message handler is also different from the standard. I've implemented better support for the STATE_SYSTEM_INVISIBLE and STATE_SYSTEM_PRESSED flags. I've implemented this as per Microsoft documentation, not as Microsoft implemented the standard scroll bar.

There are a few other message handlers that I think deserve special mention.

The WM_LBUTTONDOWN message handler is interesting because here is where many of the other scroll bars I looked at before creating this one got it wrong. A scroll bar MUST not 'take' focus unless it has the WM_TABSTOP windows style. This is also the only place in the code where we 'take' focus. The dialog manager will set focus to us in all other cases. This may seem trivial but keep in mind that users will find it very annoying if input focus changes unexpectedly.

The WM_GETDLGCODE message handler is very interesting because here is where we interact with the dialog manager. Tab order and input focus management is implemented here. The dialog manager will ask if we want input focus when the user presses the Tab key, we return DLGC_WANTTAB or DLGC_STATIC depending on the presense or absense of the WM_TABSTOP style. The dialog manager will also send a WM_GETDLGCODE message for every keystroke, we need to tell the dialog manager if we will process the keyboard message by returning DLGC_WANTMESSAGE. We do so only if 'this' window has WM_TABSTOP style set. There is an exception to this, the standard windows scroll bar will process Up/Down keys for a vertical scroll bar and Left/Right keys for a horizontal scroll bar even if the window does not have WM_TABSTOP style set. I've also implemented this behaviour just to remain true to the standard.

The WM_MOUSEWHEEL message handler is also noteworthy because we will never get those messages unless we have input focus. This is normal windows behaviour. The parent window will usually provide mouse wheel message handling. E.g. UltiMate grid is implemented like that. I should also mention that the standard windows scroll bar does not implement support for mouse wheel messages.

The WM_CONTEXTMENU message handler is worth mentioning because the context menu is loaded from user32.dll if possible and because of that should be in the 'local' language. If the load from user32.dll failed, a menu in English is created instead. Note - I have not been able to test if this method works for other languages. I would be very interested to know.

WM_KEYDOWN and WM_KEYUP message handlers are interesting in that those messages are not sent to us unless we have input focus. Also when those messages are processed, it's important to return 0 to indicate the message was processed even if we ignored the key, otherwise the dialog manager will look for another child window to process the message and we lose input focus.

The WM_SETFOCUS and WM_KILLFOCUS message handlers implement the 'blinking' input focus state of the scroll bar. The class derived from CXeScrollBarBase needs to show that the scroll bar has input focus somehow, in CXeScrollBar I've implemented this by painting the 'gripper' section of the thumb box only when m_bDrawGripper is TRUE, the base class uses a timer to 'blink' every 500mS. Most users will never see this because it's rather unusual for a scroll bar to receive focus :)

The demo project included with this article demonstrates how CXeScrollBar compares to the standard windows scroll bar.

I used this extensively when developing the CXeScrollBar.

Demo app form view

The 'Tab stops' check box - sets/clears the WM_TABSTOP style on all scroll bars.

Demo app info dialog

The scroll bar information dialog shows information from all the scroll bars on the main form view. The 'Lock scroll bars' locks/unlocks together the horizontal and vertical scroll bars. The 'Set scroll info' button will set scroll range, page size and position to all scroll bars. The 'Set range' button will set scroll range only. The 'Set POS' button will set scroll position only. The edit boxes show information we get from the scroll bars when we send SBM_GETSCROLLINFO and SBM_GETSCROLLBARINFO messages. The lower left corner shows the state of the STATE_SYSTEM_XXX flags for each section of the scroll bars.

The XeScrollBar.cpp file includes a list of research sources I used during the development of this scroll bar. Of particular interest is SkinControls 1.1 - A journey in automating the skinning of Windows controls article by .dan.g. here on CodeProject. If you read it, you will understand why subclassing a Windows scroll bar and do your own painting does not work.

History

Version 1.1 - 2010 October 21

  • Changed base class to CScrollBar (was CWnd)
  • Fixed many issues to make this scroll bar behave (almost) the same as windows scroll bar

Version 1.0 - 2009

  • Never released

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Kvikna.com
Iceland Iceland
I started programming back in 1980, briefly in Basic, switched to Assembler (Z80 CPU) running CP/M OS. Started to program for MS-DOS in Assembler (8088 CPU) when the IBM PC made it's apperance. Switched to C when I started to write programs for Windows in 1991. In 2002 switched to C++ and MFC. In 2011 started developing in C# .NET for Sharepoint 2010. In 2016 started developing WPF applications.

Comments and Discussions

 
GeneralMy vote of 5 Pin
David O'Neil12-May-20 5:45
professionalDavid O'Neil12-May-20 5:45 
GeneralRe: My vote of 5 Pin
phoenicyan7-Mar-21 4:01
phoenicyan7-Mar-21 4:01 
QuestionOne other change Pin
Dan256025-Apr-19 18:19
Dan256025-Apr-19 18:19 
QuestionScrollbar not marking mousewheel events as processed Pin
Dan256024-Apr-19 19:02
Dan256024-Apr-19 19:02 
QuestionCan i use in cedit or crichedit control? Pin
Jason.LYJ21-Mar-16 15:51
professionalJason.LYJ21-Mar-16 15:51 
AnswerRe: Can i use in cedit or crichedit control? Pin
Snorri Kristjansson22-Mar-16 11:08
professionalSnorri Kristjansson22-Mar-16 11:08 
QuestionCompling Error Pin
johnjitu4-Oct-12 22:14
johnjitu4-Oct-12 22:14 
AnswerRe: Compling Error Pin
Snorri Kristjansson4-Oct-12 23:24
professionalSnorri Kristjansson4-Oct-12 23:24 
GeneralRe: Compling Error Pin
johnjitu5-Oct-12 0:46
johnjitu5-Oct-12 0:46 
GeneralRe: Compling Error Pin
Snorri Kristjansson5-Oct-12 1:01
professionalSnorri Kristjansson5-Oct-12 1:01 
GeneralRe: Compling Error Pin
johnjitu5-Oct-12 3:04
johnjitu5-Oct-12 3:04 
QuestionRe: Compling Error Pin
johnjitu7-Oct-12 23:36
johnjitu7-Oct-12 23:36 
AnswerRe: Compling Error Pin
Snorri Kristjansson8-Oct-12 0:00
professionalSnorri Kristjansson8-Oct-12 0:00 
GeneralRe: Compling Error Pin
johnjitu8-Oct-12 0:37
johnjitu8-Oct-12 0:37 
Thank you very much for your response, i have been strugling with this problem from last week,

I have found following in my winuser.h

#if(_WIN32_WINNT >= 0x0400) every where.

-I put #define _WIN32_WINNT 0x0501

But it does not effects, i got the same errores
GeneralRe: Compling Error Pin
Snorri Kristjansson8-Oct-12 3:52
professionalSnorri Kristjansson8-Oct-12 3:52 
GeneralRe: Compling Error Pin
johnjitu8-Oct-12 4:11
johnjitu8-Oct-12 4:11 
GeneralRe: Compling Error Pin
Snorri Kristjansson8-Oct-12 23:18
professionalSnorri Kristjansson8-Oct-12 23:18 
GeneralRe: Compling Error Pin
johnjitu8-Oct-12 23:41
johnjitu8-Oct-12 23:41 
GeneralRe: Compling Error Pin
Snorri Kristjansson8-Oct-12 23:48
professionalSnorri Kristjansson8-Oct-12 23:48 
GeneralRe: Compling Error Pin
johnjitu8-Oct-12 23:51
johnjitu8-Oct-12 23:51 
GeneralRe: Compling Error Pin
Snorri Kristjansson9-Oct-12 0:04
professionalSnorri Kristjansson9-Oct-12 0:04 
GeneralRe: Compling Error Pin
johnjitu9-Oct-12 0:11
johnjitu9-Oct-12 0:11 
GeneralMessage Closed Pin
9-Oct-12 0:24
professionalSnorri Kristjansson9-Oct-12 0:24 
GeneralRe: Compling Error Pin
johnjitu9-Oct-12 0:45
johnjitu9-Oct-12 0:45 
GeneralRe: Compling Error Pin
Snorri Kristjansson9-Oct-12 2:59
professionalSnorri Kristjansson9-Oct-12 2:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.