您的位置:首页 > 其它

[MFC]ScrollDemo程序:CScrollView滚动视图的应用

2015-10-11 16:26 531 查看
1. CView的各种派生类:

1) MFC为了方便,还从CView派生出一些具有特殊功能的视图供程序员使用;

2) 其中最常用的有CScrollView、CListView、CTreeView、CHtmlView这四个,分别用来建立可滚动的视图、基于列表的视图、属性视图(资源管理器就是典型的一种)和浏览器视图(浏览器软件常用的一种视图);

3) 这里我们介绍CScrollView滚动视图;

2. CScrollView简介:

1) CScrollView为CView添加了基本的滚动功能;

2) 它可以自动处理跟滚动相关的事物而无需程序员维护,但是用户也可以调用一些成员函数来指定滚条的位置、检索滚条当前位置等;

3) 在没有使用CScrollView时,重画客户区需要让视图的内容伴随着滚条的位置而变化,这使得重画非常不方便,而现在这些琐碎的事物都可以由CScrollView来自动完成,OnDraw函数的覆盖就跟普通的CView覆盖OnDraw一模一样,无需加入复杂的和滚条同步的逻辑,除非希望优化滚条操作才会用到一些特殊的和滚条有关的逻辑;

3. 视图的创建和初始化:

1) 创建滚动视图的基本步骤:

i. 使用Wizzard创建MFC应用程序的最后一步(第6步)中选中视图类,并且选中其继承的基类为CScrollView即可;

ii. 覆盖视图类的OnInitUpdate函数,在该函数中使用SetScrollSizes来指定视图的尺寸,如果使用Wizzard来创建则会自动给出默认的OnInitUpdate的实现,里面也会自动调用SetScrollSizes,并将视图的宽和高默认设置为100像素;

iii. 将视图当做普通视图来实现OnDraw函数即可;

2) 术语——逻辑视图和物理视图:

i. 物理视图:是指实际占据屏幕的视图窗口和空间;

ii. 逻辑视图:可以使用滚条看到的完整的虚拟视图窗口和空间,一般逻辑视图≥物理视图;

!!当物理视图尺寸大于逻辑视图尺寸时滚条就会自动隐藏,并且滚条可以自动根据物理和逻辑视图的相对比例来调整自身大小,这些细节都不需要用户关心,全部都自动实现;

3) SetScrollSizes:

i. 在OnInitUpdate中调用,用来初始化视图的逻辑尺寸、页尺寸和行尺寸;

!页尺寸:单击滚轴时将滚动一页的大小;

!行尺寸:单击滚动箭头时将滚动一行的大小;

!!这两个尺寸可以用户自由设定,但一般情况下页尺寸要远大于行尺寸,不能瞎定义;

ii. 原型:

void CScrollView::SetScrollSizes(
int nMapMode, // 映射模式,后面的尺寸将基于该映射模式
SIZE sizeTotal, // 逻辑视图的宽和高
const SIZE& sizePage = sizeDefault, // 页尺寸
const SIZE& sizeLine = sizeDefault // 行尺寸
);
!!对于sizePage和sizeLine,第一个分量为水平滚条的变化单位,第二个分量表示垂直方向的变化单位;

!!nMapMode不能指定MM_ISOTROPIC和MM_ANISOTROPIC!!即两个自定义的映射模式不能指定,其余的都能指定;

iii. SetScrollSizes非常智能的一点:就是在该函数中指定了映射模式之后就不需要再在OnDraw函数中再指定一遍映射模式了,OnDraw函数中已经自动默认使用SetScrollSizes中设定的映射模式了,这为用户减轻了不少负担!!

4. 在OnDraw以外绘制输出的若干注意事项:

1) 在OnDraw以外绘制输出如果还是按照普通视图的方式执行则滚动的功能无法展现,就还是跟普通视图没两样,即输出时不会考虑映射模式和滚动的位置;

2) 在OnDraw以外绘制输出必须先使用CScrollView的OnPrepareDC做好滚动输出的准备,准备完成之后再按照普通视图那样进行输出,这样就会自动展现出滚动效果了:

i. 原型:

virtual void CView::OnPrepareDC(
CDC* pDC,
CPrintInfo* pInfo = NULL // 打印信息,暂不用管
);
!!MFC在CScrollView继承CView过程中覆盖了该函数,在该函数调用了SetMapMode和SetViewportOrg将映射模式设置成SetScrollSizes中指定的映射模式,并可以将传进去的设备描述表赋予ScrollView滚动的特性;

ii. 我们可以看一下CScrollView的OnPaint的源码:

...
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
...
!!所以,由于在调用OnDraw之前已经掉用过了OnPrepareDC所以你可以放心的按照普通方式在OnDraw中输出,只不过这些细节都隐藏在了MFC的源码中了;

!!!但是,如果你要在OnDraw之外绘图就只能自己动手完成上述步骤了:

...
CClientDC dc(this);
OnPrepareDC(&dc);
...
!否则不仅映射模式是不对的,同时也不具有滚动效果,显示就一下子混乱了!!

3) 还要注意的一点就是命中测试(命中测试也是在OnDraw之外才会遇到的),因为命中测试传递给用户的都是设备坐标,而在滚动视图中只能使用逻辑坐标,因此就不得不用DPtoLP将设备坐标转化成逻辑坐标,但是你想正确得使用逻辑坐标又必须得调用OnPrepareDC:

void CMyView::OnLButtonDown(UINT nFlags, CPoint point)
{
CPoint pos = point;
CClientDC dc(this);
OnPrepareDC(&dc);
dc.DPtoLP(&pos);
...
}


5. 滚动视图的基本操作:

1) GetScrollPosition获取滚条的位置(逻辑坐标):CPoint CScrollView::GetScrollPosition() const; // CPoint的第一个分量是水品滑块的位置,第二个是垂直滑块的位置

2) ScrollToPosition将滑块定位到指定位置(逻辑坐标):void CScrollView::ScrollToPosition(POINT pt); // 如果映射模式不是MM_TEXT则用坐标应该传负值

3) GetTotalSize获取整个逻辑视图的尺寸(逻辑坐标):void CScrollView::GetTotalSize() const; // 返回宽和高

6. 使用CDC的GetClipBox来优化滚动操作:

1) 虽然可以像普通视图那样来对待CScrollView的OnDraw函数,但是能使用并不能代表能高效使用,大多数情况下滚动会重绘很多不必要重绘的区域而导致CPU时间的大量浪费,这会使得滚动效果极差,出现画面闪烁等不良现象;

2) 使用CDC的GetClipBox来获取滚动时将被剪切的区域,因为只有被剪切的区域才需要重绘,而在剪切区域中根据应用程序的具体需要有些部分需要重绘有些部分不需要重绘,我们可以只重绘剪切区域中需要重绘的部分来大大降低滚动重绘所消耗的计算机资源以达到优化的目的;

3) GetClipBox的原型:virtual int CDC::GetClipBox(LPRECT lpRect) const;

!!lpRect中将返回剪切区域(矩形)的逻辑尺寸和位置,头两个数是逻辑宽和高,后两个是矩形左上角的逻辑坐标;

7. 将普通视图转化成滚动视图:

1) 先将所有.cpp和.h中的CView改成CScrollView,但是那些位于函数参数列表中的CView*不要修改;

2) 覆盖OnInitUpdate函数,并插入SetScrollSizes的调用,如果你忘了这一步MFC就会在执行时产生断言警告,警告视图的逻辑尺寸未知;

8. 接下来将演示滚动视图的应用程序ScrollDemo:

1) 该程序展示了一张简单的表格,共99行26列,如图所示:



!被选中的单元格用青蓝色显示;

2) .h文件:

// ScrollDemoView.h : interface of the CScrollDemoView class
//
/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_SCROLLDEMOVIEW_H__370251ED_659A_419B_948C_2E3ABABED94A__INCLUDED_)
#define AFX_SCROLLDEMOVIEW_H__370251ED_659A_419B_948C_2E3ABABED94A__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CScrollDemoView : public CScrollView
{
protected: // create from serialization only
CScrollDemoView();
DECLARE_DYNCREATE(CScrollDemoView)

// Attributes
public:
CScrollDemoDoc* GetDocument();

// Operations
public:

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CScrollDemoView)
public:
virtual void OnDraw(CDC* pDC);  // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual void OnInitialUpdate(); // called first time after construct
//}}AFX_VIRTUAL

// Implementation
public:
virtual ~CScrollDemoView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// Generated message map functions
protected:
// 绘制单元格中的内容,包括是否高亮显示和单元格中的文字
// 绘制文字使用DrawAddress
void DrawCell(CDC* pDC, int row, int col, BOOL bHighlight);

void DrawAddress(CDC* pDC, int row, int col); // 填写单元格中的文字
void GetCellRect(int row, int col, LPRECT pRect); // 获取(row, col)位置的矩形的逻辑坐标

int m_nRibbonWidth; // 左边界指示行号的那一列的宽度

// 单元格的高和宽
int m_nCellHeight;
int m_nCellWidth;

// 当前选中的单元格的列和行
int m_nCurrentCol;
int m_nCurrentRow;

CFont m_font; // 单元格中使用的字体

//{{AFX_MSG(CScrollDemoView)
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

#ifndef _DEBUG  // debug version in ScrollDemoView.cpp
inline CScrollDemoDoc* CScrollDemoView::GetDocument()
{ return (CScrollDemoDoc*)m_pDocument; }
#endif

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_SCROLLDEMOVIEW_H__370251ED_659A_419B_948C_2E3ABABED94A__INCLUDED_)
3) .cpp:

// ScrollDemoView.cpp : implementation of the CScrollDemoView class
//

#include "stdafx.h"
#include "ScrollDemo.h"

#include "ScrollDemoDoc.h"
#include "ScrollDemoView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CScrollDemoView

IMPLEMENT_DYNCREATE(CScrollDemoView, CScrollView)

BEGIN_MESSAGE_MAP(CScrollDemoView, CScrollView)
//{{AFX_MSG_MAP(CScrollDemoView)
ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CScrollDemoView construction/destruction

CScrollDemoView::CScrollDemoView()
{
// TODO: add construction code here
m_font.CreatePointFont(80, _T("MS Sans Serif")); // 视图中显示的字体一旦确定便不会再改动
// 所以在构造函数中初始化
}

CScrollDemoView::~CScrollDemoView()
{
}

BOOL CScrollDemoView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
//  the CREATESTRUCT cs

return CScrollView::PreCreateWindow(cs);
}

/////////////////////////////////////////////////////////////////////////////
// CScrollDemoView drawing

void CScrollDemoView::OnDraw(CDC* pDC)
{
CScrollDemoDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
int i, j;

CSize size = GetTotalSize();

// 整个表格的框架(各个单元格的边框)
// 都用1像素宽的灰色实线绘制
CPen pen(PS_SOLID, 0, RGB(192, 192, 192));
CPen* pOldPen = pDC->SelectObject(&pen); // 备份

for (i = 0; i < 99; i++) { // 先画所有的横线
int y = m_nCellHeight + i * m_nCellHeight;
pDC->MoveTo(0, y);
pDC->LineTo(size.cx, y);
}
for (j = 0; j < 26; j++) { // 再画所有的竖线
int x = m_nRibbonWidth + j * m_nCellWidth;
pDC->MoveTo(x, 0);
pDC->LineTo(x, size.cy);
}

CBrush brush; // 先用浅灰色填充顶栏和侧边栏(列号和行号指示栏)
brush.CreateStockObject(LTGRAY_BRUSH); // 注意!填充之后原来顶栏和侧栏的边框线会被覆盖掉
CRect rcTop(0, 0, size.cx, m_nCellHeight);
CRect rcLeft(0, 0, m_nRibbonWidth, size.cy);
pDC->FillRect(&rcTop, &brush);
pDC->FillRect(&rcLeft, &brush);

pDC->SelectObject(pOldPen); // 用原来的黑线作为顶栏和侧栏的边框线和单元格线
// 先画靠近内侧的两条
pDC->MoveTo(0, m_nCellHeight);
pDC->LineTo(size.cx, m_nCellHeight);
pDC->MoveTo(m_nRibbonWidth, 0);
pDC->LineTo(m_nRibbonWidth, size.cy);

// 接下来要绘制顶栏和侧栏中的文字内容,因此文字背景要先调成透明的
int nOldBkMode = pDC->SetBkMode(TRANSPARENT);

// 接着画顶栏和侧栏的单元格线并填充里面的文字

// 侧栏行指标
for (i = 0; i < 99; i++) {
int y = m_nCellHeight + i * m_nCellHeight;
pDC->MoveTo(0, y);
pDC->LineTo(m_nRibbonWidth, y);

CString strFormat;
strFormat.Format(_T("%d"), i + 1);
CRect rect(0, y, m_nRibbonWidth, y + m_nCellHeight);
pDC->DrawText(strFormat, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

rect.top++; // 由于顶边和上一个矩形接壤,所以顶边让一个像素,顶边让了底边就不让了
// 该函数用于绘制具有3D效果的矩形边框
// 第二个参数是顶边和左边的颜色,最后一个参数是右边和底边的颜色
// 这样可以使得矩形边框颜色具有层次感
// 一般顶边和左边的颜色选为白色,然后右边和底边的颜色选为深色
// 这样就仿佛从侧面(从左侧)阳光照射,左边亮右边暗,立体效果十足
pDC->Draw3dRect(&rect, RGB(255, 255, 255), RGB(128, 128, 128));
}

// 顶栏列指标
for (j = 0; j < 26; j++) {
int x = m_nRibbonWidth + j * m_nCellWidth;
pDC->MoveTo(x, 0);
pDC->LineTo(x, m_nCellHeight);

CString strFormat;
strFormat.Format(_T("%c"), j + _T('A'));

CRect rect(x, 0, x + m_nCellWidth, m_nCellHeight);
pDC->DrawText(strFormat, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

rect.left++; // 这次是左侧相接,所以左侧让出一个像素
pDC->Draw3dRect(&rect, RGB(255, 255, 255), RGB(128, 128, 128));
}

pDC->SetBkMode(nOldBkMode);

/* ===================以上所有内容都是无论如何都要重画的===================== */
/* 接下来中间的单元格中的内容则需要选择性重画,只画剪切的内容以优化滚动OnDraw */

// 由于顶栏和侧边栏无论如何都要重绘,所以不用考虑这两栏,只考虑中间单元格中的内容即可
CRect rcClip;
pDC->GetClipBox(&rcClip);
int nStartRow = max(0, (rcClip.top - m_nCellHeight) / m_nCellHeight); // 顶测超出部分不得不画
int nEndRow = min(98, rcClip.bottom / m_nCellHeight); // 多画一两行没关系,不必太精确
int nStartCol = max(0, (rcClip.left - m_nRibbonWidth) / m_nCellWidth);
int nEndCol = min(25, (rcClip.right - m_nRibbonWidth + m_nCellWidth) / m_nCellWidth); // 右侧超出部分不得不画

for (i = nStartRow; i <= nEndRow; i++)
for (j = nStartCol; j <= nEndCol; j++)
DrawAddress(pDC, i, j);

DrawCell(pDC, m_nCurrentRow, m_nCurrentCol, TRUE); // 最后再画选中的单元格
// 不能跟前面的步骤对调,如果选中的单元格刚好就位于剪裁区,那么选中效果就会被覆盖
}

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

CSize sizeTotal;
// TODO: calculate the total size of this view
// sizeTotal.cx = sizeTotal.cy = 100; // 这是MFC默认添加的代码,可以看到逻辑尺寸默认为100×100

m_nCurrentRow = 0;
m_nCurrentCol = 0;

CClientDC dc(this);

// 以下指定的是逻辑单位英寸,在任何显示设备中显示的长度都是相同的
m_nCellWidth = dc.GetDeviceCaps(LOGPIXELSX); // 系统逻辑单位英寸
m_nCellHeight = dc.GetDeviceCaps(LOGPIXELSY) / 4; // 将高设成宽的1/4
m_nRibbonWidth = m_nCellWidth / 2; // 左边界指示行号的那一列的宽度是单元格宽度的1/2

// 总共有26×99个单元格
int nWidth = m_nRibbonWidth + 26 * m_nCellWidth; // 最左边是行号指示列
int nHeight = m_nCellHeight + 99 * m_nCellHeight; // 最顶上是列号指示行
SetScrollSizes(MM_TEXT, CSize(nWidth, nHeight)); // 设定滚动视图的映射模式和逻辑尺寸

// SetScrollSizes(MM_TEXT, sizeTotal); // MFC默认添加的代码
}

/////////////////////////////////////////////////////////////////////////////
// CScrollDemoView diagnostics

#ifdef _DEBUG
void CScrollDemoView::AssertValid() const
{
CScrollView::AssertValid();
}

void CScrollDemoView::Dump(CDumpContext& dc) const
{
CScrollView::Dump(dc);
}

CScrollDemoDoc* CScrollDemoView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CScrollDemoDoc)));
return (CScrollDemoDoc*)m_pDocument;
}
#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////
// CScrollDemoView message handlers

void CScrollDemoView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default

CScrollView::OnLButtonDown(nFlags, point);

CPoint pos = point;
CClientDC dc(this);
OnPrepareDC(&dc); // 准备映射模式和滚动
dc.DPtoLP(&pos); // 将设备坐标转化成逻辑坐标

CSize size = GetTotalSize();
if (m_nRibbonWidth < pos.x && pos.x < size.cx &&
m_nCellHeight < pos.y && pos.y < size.cy) {

int row = (pos.y - m_nCellHeight) / m_nCellHeight;
int col = (pos.x - m_nRibbonWidth) / m_nCellWidth;
// ASSERT(0 <= row && row < 99 && 0 <= col && col < 26);

if (row != m_nCurrentRow || col != m_nCurrentCol) { // 如果选中的单元格和上次的不一样才能改变
DrawCell(&dc, m_nCurrentRow, m_nCurrentCol, FALSE); // 先去掉原先选中的单元格的高亮
m_nCurrentRow = row; // 更新选中的行和列
m_nCurrentCol = col;
DrawCell(&dc, m_nCurrentRow, m_nCurrentCol, TRUE); // 更新高亮
}
}
}

void CScrollDemoView::GetCellRect(int row, int col, LPRECT pRect)
{
// 矩形左边和顶边缩小一个像素的宽度,那1个像素的宽度用来展现3D凹陷效果
pRect->left = m_nRibbonWidth + col * m_nCellWidth + 1;
pRect->top = m_nCellHeight + row * m_nCellHeight + 1;
pRect->right = pRect->left + m_nCellWidth - 1;
pRect->bottom = pRect->top + m_nCellHeight - 1;
}

void CScrollDemoView::DrawAddress(CDC *pDC, int row, int col)
{
CRect rect;
GetCellRect(row, col, &rect);

CString strFormat; // 文字显示为列号和行号
strFormat.Format(_T("%c%d"), col + _T('A'), row + 1);

int nOldBkMode = pDC->GetBkMode();
pDC->SetBkMode(TRANSPARENT); // 文字为透明背景填充
CFont* pOldFont = pDC->SelectObject(&m_font);
pDC->DrawText(strFormat, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); // 居中单行对其
// 还原背景模式和字体
pDC->SetBkMode(nOldBkMode);
pDC->SelectObject(pOldFont);
}

void CScrollDemoView::DrawCell(CDC *pDC, int row, int col, BOOL bHighlight)
{
CRect rect;
GetCellRect(row, col, &rect);

// 高亮使用青色,默认使用Windows底色
CBrush brush(bHighlight ? RGB(0, 255, 255) : ::GetSysColor(COLOR_WINDOW));
pDC->FillRect(&rect, &brush);

DrawAddress(pDC, row, col); // 底色填充完之后绘制单元格文字内容
}
!!其它类均无需改动;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: