CScrollView
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.
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: Creating a Scroll
View-Based Application
|
|
- To start a new
application, on the main menu, click File
-> New Project…
- In the
Templates section, select MFC Application.
In the Name box, replace the string with
CommonProfessions and click OK
- Set the
Application Type to Single Document
- In the
Advenced Features, remove the check box of
Printing and Print Preview
- In the
Generated Classes, set the Base Class to
CScrollView
- Click Finish
- In the
Resource View, expand the String Table node
and access the String Table
- For the
IDR_MAINFRAME string, change the first section
to read Common Professions
- In the Class
View, clic the CmainFrame node and
double-click PreCreateWindow
- 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;
}
|
- Copy Consulting.bmp
and paste it in the
res folder of the current project
- Do the same
for HomeBased.bmp,
Medical.bmp,
and Music.bmp
- In the
Resource View of MSVC, right-click the name of
the project and click Add Resource…
- Click
Import…
- From the res
folder of the current project, select the four
pictures you added and click Open
- In the
Properties window, change their IDs to IDB_CONSULTING,
IDB_HOMEBASED, IDB_MEDICAL, and IDB_MUSIC
respectively
- Still in the
Resource View, expand the Menu node and
double-click IDR_MAINFRAME
- 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 |
|
- 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
|
- In the
Resource View, access the IDR_MAINFRAME menu
- Right-click
Consulting and click Add Event Handler…
- In the Message
Type list, make sure COMMAND is selected.
In the Class List, click the view class
- Click Add End
Edit
- Return to the
IDR_MAINFRAME menu. Right-click Consulting and
click Add Event Handler…
- In the Class
List, click the view class
- In the Message
Type list, click UPDATE_COMMAND_UI
- Click Add And
Edit
- Perform these
two actions (COMMAND and UPDATE_COMMAND_UI
associated with the view class) for the other
three menu items
- 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();
}
|
- 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: Setting the Dimensions
of the View
|
|
- 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);
}
|
- Execute the
application to test it
- 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);
}
|