Home

MFC Document/View Architecture:
The Scrolling View

 

Introduction

As you may have found out from the Windows controls we reviewed in previous lessons, the contents of a window may hide some of its parts. To show such parts, the control can be equipped with a scroll bar. This characteristic also applies to a view. Here is an example:

CView, the parent of all MFC view classes, doesn’t provide a default functionality that allows the user to scroll from one side of a view to another. If you use a CView view and want this functionality, you can implement it yourself, using the appropriate methods of that class. Because scrolling is very important and should be anticipated in many applications, the MFC library provides the CScrollView class.

Scroll View Creation

If you know for sure that the documents of an application you are creating will require scrolling, either vertically, horizontally or both, you should create it based on the CScrollView class. This class provides all the default scrolling functionalities that a view would need. It is equipped to scroll left and right or up and down. There are two main ways you can create a CScrollView-based application.

If you are creating an application using the MFC Application wizard, you can select CScrollView as the base class. If you are manually creating your application, derive your view class on CScrollView.

CScrollView is based on the CView class where it inherits a good part of its functionality. For example, if you want to draw on a CScrollView object, you can call the use the CView::OnDraw event. This event is automatically generated for you if you use the MFC Application wizard to create your project. If you want to print the contents of a CScrollView object, you can use the event of the CView class as we reviewed them when studying printing.

Here is an example of using the OnDraw event of a scroll view as if it were a regular CView object:

void CExerciseView::OnDraw(CDC* pDC)
{
	CExerciseDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: add draw code for native data here
	CBitmap bmpCar;
	CDC mdcCar;

	bmpCar.LoadBitmapW(IDB_CAR);
	mdcCar.CreateCompatibleDC(pDC);
	CBitmap *bmpOld = mdcCar.SelectObject(&bmpCar);

	pDC->BitBlt(0, 0, 385, 215, &mdcCar, 0, 0, SRCCOPY);
	pDC->SelectObject(bmpOld);
}

Practical Learning Practical Learning: Creating a Scroll View-Based Application

  1. To start a new application, on the main menu, click File -> New Project…
  2. In the Templates section, select MFC Application.
    In the Name box, replace the string with CommonProfessions and click OK
  3. Set the Application Type to Single Document
  4. In the Advenced Features, remove the check box of Printing and Print Preview
  5. In the Generated Classes, set the Base Class to CScrollView
  6. Click Finish
  7. In the Resource View, expand the String Table node and access the String Table
  8. For the IDR_MAINFRAME string, change the first section to read Common Professions
  9. In the Class View, clic the CmainFrame node and double-click PreCreateWindow
  10. Make the following changes
     
    int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
    	if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
    		return -1;
    
    	if (!m_wndToolBar.CreateEx(this,
    			 TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
    			| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | 
    			CBRS_SIZE_DYNAMIC) ||
    		!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
    	{
    		TRACE0("Failed to create toolbar\n");
    		return -1; // fail to create
    	}
    
    	m_wndToolBar.SetWindowTextW(TEXT("Standard Toolbar")); 
    
    	if (!m_wndStatusBar.Create(this) ||
    		!m_wndStatusBar.SetIndicators(indicators,
    		sizeof(indicators)/sizeof(UINT)))
    	{
    		TRACE0("Failed to create status bar\n");
    		return -1; // fail to create
    	}
    
    	// TODO: Delete these three lines if you don't 
    	// want the toolbar to be dockable
    	m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
    	EnableDocking(CBRS_ALIGN_ANY);
    	DockControlBar(&m_wndToolBar);
    
    	CenterWindow();
    
    	return 0;
    }
    
    BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
    {
    	if( !CFrameWnd::PreCreateWindow(cs) )
    		return FALSE;
    	// TODO: Modify the Window class or styles here by modifying
    	// the CREATESTRUCT cs
    	cs.cx = 512;
    	cs.cy = 712;
    	cs.style &= ~FWS_ADDTOTITLE;
    
    	return TRUE;
    }
     
  11. Copy Consulting.bmp and paste it in the res folder of the current project
  12. Do the same for HomeBased.bmp, Medical.bmp, and Music.bmp
  13. In the Resource View of MSVC, right-click the name of the project and click Add Resource…
  14. Click Import…
  15. From the res folder of the current project, select the four pictures you added and click Open
  16. In the Properties window, change their IDs to IDB_CONSULTING, IDB_HOMEBASED, IDB_MEDICAL, and IDB_MUSIC respectively
  17. Still in the Resource View, expand the Menu node and double-click IDR_MAINFRAME
  18. In the View category, create the following menu items:
     
    Caption ID
    &Home-Based ID_VIEW_HOME_BASED
    &Consulting ID_VIEW_CONSULTING
    &Medical ID_VIEW_MEDICAL
    M&usic ID_VIEW_MUSIC
  19. In the Class View, double-click CcommonProfessionsView and change it as follows:
     
    // CommonProfessionsView.h : interface of the CCommonProfessionsView class
    //
    
    #pragma once
    
    // This enumerator will be used to monitor menu item selection
    // and the radio button functionality
    enum COMMONPROFESSIONS
    {
    	cpHomeBased,
    	cpConsulting,
    	cpMedical,
    	cpMusic
    };
    
    class CCommonProfessionsView : public CScrollView
    {
    protected: // create from serialization only
    	CCommonProfessionsView();
    	DECLARE_DYNCREATE(CCommonProfessionsView)
    
    // Attributes
    public:
    	CCommonProfessionsDoc* GetDocument() const;
    
    // Operations
    public:
    	COMMONPROFESSIONS m_CommonProfessions;
    
    // Overrides
    public:
    	. . . 
    
    #endif
  20. In the Resource View, access the IDR_MAINFRAME menu
  21. Right-click Consulting and click Add Event Handler…
  22. In the Message Type list, make sure COMMAND is selected.
    In the Class List, click the view class
     
  23. Click Add End Edit
  24. Return to the IDR_MAINFRAME menu. Right-click Consulting and click Add Event Handler…
  25. In the Class List, click the view class
  26. In the Message Type list, click UPDATE_COMMAND_UI
     
  27. Click Add And Edit
  28. Perform these two actions (COMMAND and UPDATE_COMMAND_UI associated with the view class) for the other three menu items
  29. To take care of the radio effect of the menu items, implement the events as follows:
     
    // CommonProfessionsView.cpp : implementation of the 
    // CCommonProfessionsView class
    //
    
    #include "stdafx.h"
    #include "CommonProfessions.h"
    
    #include "CommonProfessionsDoc.h"
    #include "CommonProfessionsView.h"
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #endif
    
    // CCommonProfessionsView
    
    IMPLEMENT_DYNCREATE(CCommonProfessionsView, CScrollView)
    
    . . .
    
    // CCommonProfessionsView construction/destruction
    
    CCommonProfessionsView::CCommonProfessionsView()
    {
    	// TODO: add construction code here
    	this->m_CommonProfessions = cpHomeBased;
    }
    
    . . .
    
    // CCommonProfessionsView message handlers
    
    void CCommonProfessionsView::OnViewConsulting()
    {
    	// TODO: Add your command handler code here
    	this->m_CommonProfessions = cpConsulting;
    }
    
    void CCommonProfessionsView::OnUpdateViewConsulting(CCmdUI *pCmdUI)
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetRadio(this->m_CommonProfessions == cpConsulting);
    	Invalidate();
    }
    
    void CCommonProfessionsView::OnViewHomeBased()
    {
    	// TODO: Add your command handler code here
    	this->m_CommonProfessions = cpHomeBased;
    }
    
    void CCommonProfessionsView::OnUpdateViewHomeBased(CCmdUI *pCmdUI)
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetRadio(this->m_CommonProfessions == cpHomeBased);
    	Invalidate();
    }
    
    void CCommonProfessionsView::OnViewMedical()
    {
    	// TODO: Add your command handler code here
    	this->m_CommonProfessions = cpMedical;
    }
    
    void CCommonProfessionsView::OnUpdateViewMedical(CCmdUI *pCmdUI)
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetRadio(this->m_CommonProfessions == cpMedical);
    	Invalidate();
    }
    
    void CCommonProfessionsView::OnViewMusic()
    {
    	// TODO: Add your command handler code here
    	this->m_CommonProfessions = cpMusic;
    }
    
    void CCommonProfessionsView::OnUpdateViewMusic(CCmdUI *pCmdUI)
    {
    	// TODO: Add your command update UI handler code here
    	pCmdUI->SetRadio(this->m_CommonProfessions == cpMusic);
    	Invalidate();
    }
  30. Save all

The Size of the Scrolling Region

A scroll view is meant to show scroll bars only when necessary. To make this decision, the view should be aware of the dimensions of the document.

When you have just created a CScrollView application, the view doesn’t know what dimensions it would use in the scrolling region. This characteristic can be specified using the CScrollView::SetScrollSizes() method. Its syntax is:

void SetScrollSizes(int nMapMode,
		    SIZE sizeTotal,
		    const SIZE& sizePage = sizeDefault,
		    const SIZE& sizeLine = sizeDefault);

Ths nMapMode argument holds a mapping mode that can be MM_TEXT, MM_HIMETRIC, MM_TWIPS, MM_HIENGLISH, MM_LOMETRIC, or MM_LOENGLISH.

The sizeTotal argument is the width and the height values that will be set as the maximum dimensions of the scrolling area.

The optional sizePage argument is the value by which the view would be scrolled for a page, that is, when the user clicks inside the scrolling region of the bar.

The optional sizeLine argument is the value by which the view would scroll when the user clicks once in one of the arrow buttons of the scroll bar.

If you want to specify the default dimensions of the view for your application, you can use the CView::OnInitialUpdate event that the CScrollView class inherits. If you create a CScrollView-based project using the MFC Application, the wizard would generate an OnInitialUpdate event for you and would write a default SetScrollSizes code that, in most cases, you should modify. Using the sizeTotal, you can specify only the width, only the height, or both.

As mentioned above, the view doesn’t always show the scroll bars. For example, if the application itself or the user increases the size of the frame so it would be greater than the dimensions stored in the SIZE value of the SetScrollSizes() method, the scroll bars would disappear. Here is an example:

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
	if( !CFrameWnd::PreCreateWindow(cs) )
		return FALSE;
	// TODO: Modify the Window class or styles here by modifying
	// the CREATESTRUCT cs

	cs.cx = 410;
	cs.cy = 310;
	cs.style &= ~FWS_ADDTOTITLE;

	return TRUE;
}

void CExerciseView::OnInitialUpdate()
{
	CScrollView::OnInitialUpdate();

	CSize sizeTotal;
	// TODO: calculate the total size of this view
	sizeTotal.cx = 385;
	sizeTotal.cy = 215;
	SetScrollSizes(MM_TEXT, sizeTotal);
}

In the same way, when you pass constant values as the sizeTotal of the SetScrollSizes() method, you should have a good idea of the dimensions of the contents of the view. An alternative is to get the dimensions of the contents and store them in a value. For example, if you are using the view to display a picture, it may be a good idea to know the dimensions of that picture so the view would fit it.

To know the current size of the scrolling region and even the mapping mode applied to it, you can call the GetDeviceScrollSizes(). Its syntax is:

void GetDeviceScrollSizes(int& nMapMode,
			  SIZE& sizeTotal,
			  SIZE& sizePage,
			  SIZE& sizeLine 
			 ) const;

At any time, to get the size of the scrolling region, you can call the GetTotalSize() method. Its syntax is:

CSize GetTotalSize( ) const;

This method returns the width, as a CSize::cx value, and the height, as a CSize::cy value, of the scroll view. 

Practical Learning Practical Learning: Setting the Dimensions of the View

  1. To change the size of the view, change the OnDraw event of the view class as follows:
     
    void CCommonProfessionsView::OnDraw(CDC* pDC)
    {
    	CCommonProfessionsDoc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    	if (!pDoc)
    		return;
    
    	// TODO: add draw code for native data here
    	CBitmap bmpProfession;
    	CDC memDCProfession;
    	CSize szeBitmap;
    
    	if( m_CommonProfessions == cpConsulting )
    	{
    		bmpProfession.LoadBitmap(IDB_CONSULTING);
    		szeBitmap.cx = 1276;
    		szeBitmap.cy = 1024;
    	}
    	else if( m_CommonProfessions == cpHomeBased )
    	{
    		bmpProfession.LoadBitmap(IDB_HOMEBASED);
    		szeBitmap.cx = 683;
    		szeBitmap.cy = 748;
    	}
    	else if( m_CommonProfessions == cpMedical )
    	{
    		bmpProfession.LoadBitmap(IDB_MEDICAL);
    		szeBitmap.cx = 683;
    		szeBitmap.cy = 1024;
    	}
    	else if( m_CommonProfessions == cpMusic )
    	{
    		bmpProfession.LoadBitmap(IDB_MUSIC);
    		szeBitmap.cx = 1280;
    		szeBitmap.cy = 853;
    	}
    
    	memDCProfession.CreateCompatibleDC(pDC);
    CBitmap *bmpPrevious = memDCProfession.SelectObject(&bmpProfession);
    
    	if( m_CommonProfessions == cpConsulting )
    	pDC->BitBlt(0, 0, 1276, 1024, &memDCProfession, 0, 0, SRCCOPY);
    	else if( m_CommonProfessions == cpHomeBased )
    	pDC->BitBlt(0, 0, 683, 748, &memDCProfession, 0, 0, SRCCOPY);
    	else if( m_CommonProfessions == cpMedical )
    	pDC->BitBlt(0, 0, 683, 1024, &memDCProfession, 0, 0, SRCCOPY);
    	else if( m_CommonProfessions == cpMusic )
    	pDC->BitBlt(0, 0, 1280, 853, &memDCProfession, 0, 0, SRCCOPY);
    
    	pDC->SelectObject(bmpPrevious);
    
    	SetScrollSizes(MM_TEXT, szeBitmap);
    }
  2. Execute the application to test it
     
  3. Close it

Checking the Existence of Scroll Bars

Remember that, depending on the size of the content relative to the frame of the window, the view may or may not display the scroll bar(s). In some cases, before performing an operation on the view or its contents, you may need to know whether the view is currently equipped with one or both scroll bars. To get this information, you can call the CheckScrollBars() method. Its syntax is:

void CheckScrollBars(BOOL& bHasHorzBar, BOOL& bHasVertBar) const;

Notice that each argument is passed by reference. This means that each would return a value. After calling this method, if the frame is displaying a horizontal scroll bar, the first argument would return with a TRUE value. If the frame is displaying a vertical scroll bar, the second argument would return with a TRUE value.  

Checking the Positions of Scroll Boxes


If the frame is equipped with one or both scroll bars, the user can scroll. If this happens and you want to know the position of the scroll boxes, you can call the GetDeviceScrollPosition() method. Its syntax is:

CPoint GetDeviceScrollPosition( ) const;

This method returns the position, as a CPoint coordinate, of the scroll boxes.  

Scaling the Document to Fit the View

Imagine you have specified the contents of the document. For example, suppose you are using the view to display a picture and allow the user to scroll to get to hidden sections. If the user resizes the window to a width longer than the contents, the horizontal scroll bar would disappear. Consider the following example:

In the same way, if the user resizes the window to a height taller than the contents, the vertical scroll bar would disappear. As mentioned already, if the user resizes the whole window wider and taller than the contents, both scroll bars would disappear:

If you want, and if your application is appropriate for it, you can use a stretching effect so that the contents of the document would extend to the resizing dimensions of the view when the user changes the size of the window. Consider the following two screenshots:

If you want the contents of the view to resize itself to fit the view when the user resizes the window, instead of calling SetScrollSizes, call the CScrollView::SetScaleToFitsSize() method. Its syntax is:

void SetScaleToFitSize(SIZE sizeTotal);

The argument specifies the dimensions of the view. After calling this method, the view doesn’t have scroll bars anymore and its contents would assume any size the view gets resized to. Here is an example of calling this method:

void CExerciseView::OnInitialUpdate()
{
	CScrollView::OnInitialUpdate();

	CSize sizeTotal;
	// TODO: calculate the total size of this view
	sizeTotal.cx = 385;
	sizeTotal.cy = 215;
	SetScaleToFitSize(sizeTotal);
}
 

Home