Click here to Skip to main content
15,860,943 members
Articles / Desktop Programming / MFC
Article

Add Scrolling to a CWnd or CDialog using a C++ Helper Class

Rate me:
Please Sign up or sign in to vote.
4.90/5 (67 votes)
20 Sep 2005CPOL7 min read 520.3K   14.4K   109   119
An article on adding scrolling to a CWnd or CDialog using a C++ helper class.

Introduction

Implementing standard scrolling behavior for a custom CWnd or CDialog-derived class using MFC is fairly non-trivial. There are at least 3 or 4 message handlers to write as well as a number of scrolling parameters for each scrollbar that need to be adjusted whenever your window or dialog is resized. In this article, I present a C++ helper class that can be used to add both horizontal and/or vertical scrolling to any CWnd or CDialog class. Unlike other implementations you may have seen, there is no scrollable dialog base class that you need to derive from. No change in your inheritance hierarchy is required to use the helper class (CScrollHelper).

Background

Before going into the details regarding how to use the helper class, I want to cite a couple of references which explain the basics of implementing scrolling very well. The first reference is MSDN article ID 262954, "How to create a resizable dialog box with scroll bars in Visual C++". This article gives an example of how to add vertical scrollbar support to a CDialog class. The second reference is from the book, "Programming Windows with MFC, 2nd Edition" by Jeff Prosise (Microsoft Press). Chapter 2 of this book explains the details of implementing scrolling in a CWnd-derived class and also gives example code for a simple spreadsheet application that supports both horizontal and vertical scrolling.

For a CWnd-derived class, the first step in implementing scrolling is to make sure the window is created with the window styles, WS_HSCROLL (if you want a horizontal scrollbar) and/or WS_VSCROLL (if you want a vertical, right-attached scrollbar).

Create(NULL, "CScrollWnd",
       WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL,
       CRect(0,0,0,0), parentWnd, 0, NULL);

In the case of a dialog, window styles are typically set through the resource editor. In VS 2003, I usually make sure the following properties are set for any dialog that I want to be scrollable:

  1. Border = "Resizing" if you have a popup dialog. If your dialog is a child window embedded within a container parent, you can choose another style such as "None" (the Demo project has an example of this).
  2. Clip Children = "True". This setting can help to minimize display flickering as the dialog is being resized.
  3. Horizontal Scrollbar = "True". This is equivalent to adding the window style, WS_HSCROLL.
  4. Style = "Child" if your dialog is a child window embedded within a container parent.
  5. Vertical Scrollbar = "True". This is equivalent to adding the window style, WS_VSCROLL.
  6. Visible = "True". Visual Studio defaults to "False" in some cases, so you want to check this setting.

Using the code

The CScrollHelper class is implemented in two source files: ScrollHelper.h and ScrollHelper.cpp. The public interface of the class is shown below:

class CScrollHelper
{
public:
    CScrollHelper();
    ~CScrollHelper();

    // Attach/detach a CWnd or CDialog.
    void   AttachWnd(CWnd* pWnd);
    void   DetachWnd();

    // Set/get the virtual display size.
    // When the dialog or window
    // size is smaller than the display
    // size, then that is when
    // scrollbars will appear. Set either
    // the display width or display
    // height to zero if you don't want to
    // enable the scrollbar in the
    // corresponding direction.
    void   SetDisplaySize(int displayWidth,
                           int displayHeight);
    const CSize& GetDisplaySize() const;

    // Get current scroll position.
    // This is needed if you are scrolling
    // a custom CWnd which implements its
    // own drawing in OnPaint().
    const CSize& GetScrollPos() const;

    // Get current page size. Useful
    // for debugging purposes.
    const CSize& GetPageSize() const;

    // Scroll back to top, left, or
    // top-left corner of the window.
    void   ScrollToOrigin(bool scrollLeft,
                              bool scrollTop);

    // Message handling.
    void   OnHScroll(UINT nSBCode, UINT nPos,
                        CScrollBar* pScrollBar);
    void   OnVScroll(UINT nSBCode, UINT nPos,
                        CScrollBar* pScrollBar);
    BOOL   OnMouseWheel(UINT nFlags,
                       short zDelta, CPoint pt);
    void   OnSize(UINT nType, int cx, int cy);
};

To add scrolling support to your CDialog-derived class (such as CScrollDlg), we begin by adding a private member to the dialog's class definition (include file):

class CScrollHelper;    // Forward class declaration.

class CScrollDlg : public CDialog
{
...

private:
    CScrollHelper* m_scrollHelper;
};

Next, we add four message handlers to the class definition (include file) that are scrolling-related:

// Generated message map functions.
//{{AFX_MSG(CScrollDlg)
...
afx_msg void OnHScroll(UINT nSBCode,
        UINT nPos, CScrollBar* pScrollBar);
afx_msg void OnVScroll(UINT nSBCode,
        UINT nPos, CScrollBar* pScrollBar);
afx_msg BOOL OnMouseWheel(UINT nFlags,
                  short zDelta, CPoint pt);
afx_msg void OnSize(UINT nType, int cx,
                                   int cy);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()

In the dialog source file, include the ScrollHelper.h file and also add the message map entries for the four message handlers:

#include "ScrollHelper.h"
...
BEGIN_MESSAGE_MAP(CScrollDlg, CDialog)
    //{{AFX_MSG_MAP(CScrollDlg)
    ...
    ON_WM_HSCROLL()
    ON_WM_VSCROLL()
    ON_WM_MOUSEWHEEL()
    ON_WM_SIZE()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

Then create an instance of the helper class in the dialog constructor and attach the dialog to the instance:

CScrollDlg::CScrollDlg(CWnd* pParent)
    : CDialog(IDD_SCROLL_DLG, pParent)
{
    // Create the scroll helper
    // and attach it to this dialog.
    m_scrollHelper = new CScrollHelper;
    m_scrollHelper->AttachWnd(this);
}

Remember to delete the helper class instance in the dialog destructor:

CScrollDlg::~CScrollDlg()
{
    delete m_scrollHelper;
}

Next, implement the four message handlers by simply delegating to the helper class, which has methods with the exact same signature as the message handlers:

void CScrollDlg::OnHScroll(UINT nSBCode,
                UINT nPos, CScrollBar* pScrollBar)
{
    m_scrollHelper->OnHScroll(nSBCode,
                          nPos, pScrollBar);
}

void CScrollDlg::OnVScroll(UINT nSBCode,
         UINT nPos, CScrollBar* pScrollBar)
{
    m_scrollHelper->OnVScroll(nSBCode,
                          nPos, pScrollBar);
}

BOOL CScrollDlg::OnMouseWheel(UINT nFlags,
                    short zDelta, CPoint pt)
{
    BOOL wasScrolled =
        m_scrollHelper->OnMouseWheel(nFlags,
                                      zDelta, pt);
    return wasScrolled;
}

void CScrollDlg::OnSize(UINT nType, int cx, int cy)
{
    CDialog::OnSize(nType, cx, cy);
    m_scrollHelper->OnSize(nType, cx, cy);
}

One final step to completing scrolling support is to set the "display size" in the helper class. This represents the virtual display area of your dialog and is a value that you can predetermine and fix in the code itself, or compute dynamically during run-time. If you are familiar with Windows Forms programming in C#, the concept of display size is similar to the DisplayRectangle property in scrollable controls. The display size represents the precise threshold or point where scrollbars either appear or disappear. For example, if the user resizes the dialog until it is smaller than the display size, then scrollbars will appear, allowing you to scroll the dialog to access the entire virtual display surface. If the user resizes the dialog larger than the display size, then the scrollbars will disappear. The display size is typically a value that is fixed after you set it the first time:

CScrollDlg::CScrollDlg(CWnd* pParent)
    : CDialog(IDD_SCROLL_DLG, pParent)
{
    // Create the scroll helper and
    // attach it to this dialog.
    m_scrollHelper = new CScrollHelper;
    m_scrollHelper->AttachWnd(this);

    // We also set the display size
    // equal to the dialog size.
    // This is the size of the dialog
    // in pixels as laid out
    // in the resource editor.
    m_scrollHelper->SetDisplaySize(701, 375);

    // Create the dialog.
    Create(IDD_SCROLL_DLG, pParent);
}

In the interface of the CScrollHelper class, you may have noticed the GetPageSize() method. The page size is another important scrolling parameter that is managed internally by the helper class. Basically, the page size is the same as your dialog size (or more precisely, the size of the client area of your dialog). This is similar to the ClientRectangle property in Win Forms. The ratio of the page size to the display size is used by Windows to determine how big the "thumb" portion of the scrollbar should be. The size of the thumb gives you an indication of how much of the virtual display surface you are viewing (and the position of the thumb tells you what part of the virtual display surface you are looking at). For example, let's say you are viewing a very long document in Microsoft Word (100 pages). You will see that the thumb of the vertical scrollbar is very small. On the other hand, if you are editing a document that is only a bit longer than a single page, you will see that the thumb on the scrollbar is close to the maximum size it can have.

The TestScroll application

The demo project (TestScroll) illustrates the use of the helper class. It's a MDI application that I created from scratch using Visual Studio. To generate the project, I defaulted all of the VS wizard options except for the Document/View support checkbox, which I unchecked. I then wrote two new classes, CScrollDlg and CScrollWnd. CScrollDlg is a dialog class that uses the helper class to implement scrolling (as shown in the code sections above). It simply displays four buttons, one at each corner of the virtual display surface, along with a CListBox that shows the current scrolling parameters as the dialog is being resized. CScrollWnd is a custom CWnd-derived class that shows you how to add scrolling support to non-dialog classes. What's interesting about the implementation here is that this class paints a rectangle representing the fixed display size. So as you resize the window, you can see precisely when a scrollbar will appear or disappear.

The generated MDI application provides a class called CChildView, which is contained within the MDI child frame window. CChildView is really the starting point of integration with my two new classes above. Instead of CChildView providing its own content, I modified it to either create a CScrollDlg or a CScrollWnd instance that covers its entire client area.

int CChildView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if ( CWnd::OnCreate(lpCreateStruct) == -1 )
        return -1;

    // We either create a CScrollWnd or a CScrollDlg.
    // We alternate using a counter.
    static int counter = 0;

    if ( counter % 2 == 0 )
        m_scrollWin = new CScrollWnd(this);
    else
        m_scrollWin = new CScrollDlg(this);
    ++counter;

    return 0;
}

To test the demo application, just use the File | New menu item to open MDI child Windows. The first time you will get a CScrollWnd instance. The second time, a CScrollDlg will be created. Each time you select "New", it will alternate between the two types of examples.

The snapshot below shows scrolling in a CWnd-derived class. The top MDI child window shows scrollbars since the window size is smaller than the display size. The bottom MDI child has no scrollbars and you can clearly see that the window size is larger than the fixed display size (represented by the blue rectangle). Also, in the bottom MDI child, notice that the page size is reported as 0 x 0. This is how the helper class is able to hide the scrollbars - by setting internal scrolling parameters such as scroll position and page size to zero values.

Image 1

The snapshot below shows an example of scrolling in a CDialog. Notice that the caption bar of the bottom MDI child window displays the current scroll position, and that the dialog is scrolled all the way to the bottom-right. When scrolled to the maximum position, an interesting thing to note is that the scroll position (222, 230) added to the page size (479, 145) equals the display size (701, 375).

Image 2

To summarize, the CScrollHelper class makes it easy to add scrolling support to your CWnd or CDialog classes, as it takes care of implementing all of the necessary message handlers. The key to using the helper class is to be able to set the display size properly. In the case of CWnd classes, you may also need to look further into GDI mapping modes and conversions between logical and device coordinates.

History

  • July 5th, 2005
    • Initial revision.
  • July 6th, 2005
    • Added Get32BitScrollPos() function as per MSDN article ID 152252, "How to Get 32-bit Scroll Position During Scroll Messages".
    • Handles SB_THUMBPOSITION in OnHScroll/OnVScroll.
    • Added VC++ 6.0 demo project. (Thanks to PJ Arends for the suggested fixes.)
  • September 8th, 2005
    • Added GetClientRectSB() helper function.
    • Uses memory DC drawing in demo project (CScrollWnd class) to avoid flickering on resize.

License

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


Written By
Web Developer
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionGood One Pin
MANISH RASTOGI21-Jul-11 1:39
MANISH RASTOGI21-Jul-11 1:39 

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.