第五章 Windows Message Mapping Windows 消息映射 In Chapter 3, you saw how the MFC library application framework calls the view class’s virtual OnDraw function. In the online help for the MFC library, where it documents the Cview class and its base class, CWnd, you’ll see several hundred member functions. Functions whose names begin with On—such as OnKeyDown and OnLButtonUp—are member functions that the application framework calls in response to various Windows “events” such as keystrokes and mouse clicks. 在第三章,你看到了MFC库应用框架是怎样调用视图类的虚函数OnDraw的.在MFC的在线帮助中,在Cview类和它的基类CWnd中,你将看到几百个成员函数.每个函数都是以On单词开头的,例如OnKeyDown和OnLButtonUp成员函数,它门都是应用框架调用响应的不同的Windows”事件”例如键盘敲击和鼠标按键事件. Most of these functions that are called by the application framework aren’t virtual functions and thus require more programming steps. This chapter explains how to use the Microsoft Visual C++.NET Class View’s Properties window to set up the message map structure necessary for connecting the application framework to your functions’ code. 这么多的函数不可能要应用框架都通过虚函数来调用那样会需要更多的程序步骤.这章介绍怎么样使用Microsoft VC++.NET类视图属性窗口来设置消息映射结构来完成应用框架到你程序功能代码的连接. This chapter includes sample applications of message map functions. The first two applications user an ordinary Cview class. The Ex05a example shows the interaction between user-driven events and the OnDraw function. The Ex05b example shows the effects of different Windows mapping modes. 这章包括的应用程序消息映射的例子.第一,二个应用程序使用的是普通的Cview类.EX05a例子展示的是用户使用事件驱动和OnDraw函数之间的交互作用.Ex05b展示的是不同效果的Windows映射模式. More often than not, however, you’ll want a scrolling view. The last example, Ex05c,users CscrollView in place of the Cview base class. This allows the MFC library application framework to insert scroll bars and connect them to the view. 经常,你也需要具有滑动条的视图.最后一个例子,Ex05c中,在CVIEW基类中使用CscrollView.这是允许MFC库应用框架可以插入一个滑动条到它们的视图中. Getting User Input: Message Map Functions The Ex03a application from Chapter 3 does not accept user input(other than the standard Microsoft Windows resizing and window close commands). The window contains menus and a toolbar, but these are not “connected” to the view code. I won’t discuss the menus and the toolbar until Part III of this book because they depend on the frame class, but plenty of other Windows input sources will keep you busy until then. However, before you can process any Windows event, even a mouse click, you must learn how to use the MFC library message map system. 获得用户输入:消息映射函数 第三章的Ex03a应用程序是不能接受用户输入的(不同与其他的标准的Microsoft Windows调整大小和窗口关闭命令).窗口包括菜单和工具栏,但是这里没有”连接”到视图代码.在本中由于菜单和工具栏依靠框架类所以在第三部分以前不会介绍它们,但是许多其他的Windows输入将使你忙的不可开交.然而,在你能处理任何Windows事件,每个鼠标单击事件之前,你必须学习怎么样使用MFC库的消息映射系统. The Message Map When the user clicks the left mouse button in a view window, Windows sends a message—specifically, WM_LBUTTONDOWN—to that widow. If your program needs to take action in response to WM_LBUTTONDOWN, your view class must have a member function that looks like this: Void CmyView::OnLButtonDown(UINT nFlags, Cpoint point) { //event processing code here } Your class header file must also have the corresponding prototype: Afx_msg void OnLButtonDown(UINT nFlags, Cpoint point); The afx_msg notation is a “no-op” that alerts you that this is a prototype for message map function. Next, your code file needs a message maps macro that connects your OnLButtonDown function to the application framework: BEGIN_MEAAGE_MAP(CmyView,Cview) ON_WM_LBUTTONDOWN() //entry specifically for OnLButtonDown //other message map entries END_MESSAGE_MAP() Finally, your class header file needs this statement: DECLARE_MESSAGE_MAP() How do you know which function goes with which Windows message? Chapter Appendix A (and the MFC library online documentation) includes a table that lists all standard Windows message and corresponding member function prototypes. You can manually code the message-handling functions—indeed, you still have to do that for certain messages. But fortunately, the code wizards available from Class View’s Properties window automate the coding of most message map functions. 消息映射 当用户鼠标左键单击视图窗口时,Windows会发送特定的消息---WM_LBUTTONDOWN—给窗口.如果你的程序需要响应这个消息,你的视图类必须要有以下的成员函数: Void CmyView::OnLButtonDown(UINT nFlags, Cpoint point) { //event processing code here } 你的类头文件中也必须有相应的原型: Afx_msg void OnLButtonDown(UINT nFlags, Cpoint point); 这个afx_msg符号是 “什么也不做的”它提醒这个原型是个消息映射函数. 接下来,你的代码文件需要消息映射宏来连接到你的OnLButtonDown函数到应用程序框架: BEGIN_MEAAGE_MAP(CmyView,Cview) ON_WM_LBUTTONDOWN() //entry specifically for OnLButtonDown //other message map entries END_MESSAGE_MAP() 最后,你的类的头文件需要这个声明: DECLARE_MESSAGE_MAP() 你怎么知道那些函数要用那些Windows消息呢?附表A(还有MFC在线文档)所列的表里包含了所有标准的Windows消息和相应的成员函数原型.你可以手动处理这些消息函数,但你仍需要确认消息.幸运的是,你可利用类视图属性窗口中的代码向导自动的增加许多消息映射函数. Saving the View’s State: Class Data Members If your program accepts input, you’ll want the user to get some visual feedback. The view’s OnDraw function draws an image based on the view’s current state, and user actions can alter that stat. In a full-blown MFC library application, the document object holds the state of the application, but you’re not to that point yet. For now, we’ll use two view class data members, m_rectEllipse and m_nColor. The first is an object of class CRect, which holds the current bounding rectangle of an ellipse, and the second is an integer that holds the current ellipse color value. 保存视图的状态:类数据成员 如果你的程序接受输入,你想要给用户一些反馈信息.视图的OnDraw函数绘制的基于视图当前状态和用户活动后的状态的图像.在成熟的MFC应用程序中,文档对象保存着应用程序的状态,但是你仍不能指出.暂时,我们使用两个视图类的数据成员, m_rectEllipse和m_nColor.第一个是个CRect对象,它保存着当前矩形范围的椭圆形,第二个是个整形,它保存着当前椭圆的颜色值. Note By convention, MFC library nonstatic class data member names begin with m_. 注意:按惯例,MFC库非静态的类成员名称都以m_开始. We’ll make a message-mapped member function toggle the ellipse color (the view’s state) between gray and white. (The toggle is activated by a click of the left mouse button.) The initial values of m_rectEllipse and m_nColor are set in the view’s constructor, and the color is changed in the OnLButtonDown member function. 我们安排消息映射函数来使椭圆颜色(视图状态)在灰色和白色之间改变.(通过鼠标左键来改变). m_rectEllipse 和 m_nColor将在视图构造器中初始化值,颜色的改变在OnLButtonDown函数中完成. Note Why not use a global variable for the view’s state? Because if you do, you’ll be in trouble if your application has multiple views. Besides, encapsulating data in objects is a big part of what object-oriented programming is all about. 注意:为什么我们不用公告变量来保存视图状态? 因为你这么做,假如你的应用程序有多个视图你将会遇到麻烦.除此之外,大部分面向对象的程序设计的实质就是将数据压缩到对象中. Initializing a View Class Data Member The most efficient place to initialize a Class data member is in the constructor, as shown here: CmyView::CmyView() : m_rectEllipse(0,0,200,200){…} You can initialize m_nColor with the same syntax. We’re using a built-in type(integer), so the generated code is the same if you use an assignment statement in the constructor body. 初始化视图类数据成员 最有效的初始化类数据成员的地方是在构造器中,如下所示: CmyView::CmyView() : m_rectEllipse(0,0,200,200){…} 你也能用同样的语法来初始化m_nColor变量.我们使用内建的类型(整型),如果你在构造器中使用本声明,产生的代码是一样的. Invalid Rectangle Theory The OnLButtonDown function can toggle the value of m_nColor all day, but if that’s all it did, the OnDraw function wouldn’t get called(unless, for example, the user resized the view window). The OnLButtonDown function must call the InvalidateRect function (a member function that the view class inherits from CWnd). InvalidateRect triggers a Windows WM_PAINT message, which is mapped in the Cview class to call to the virtual OnDraw function. If necessary, OnDraw can access the “invalid rectangle” parameter that was passed to InvalidateRect. You can optimize painting in Windows in two ways. First, you must be aware that Windows updates only those pixels that are inside the invalid rectangle. Thus, the smaller you make the invalid rectangle (in the OnLButtonDown handler, for instance), the more quickly it can be repainted. Second, it’s a waste of time to execute drawing instructions outside the invalid rectangle. Your OnDraw function can call the CDC member function GetClipBox to determine the invalid rectangle, and then it can avoid drawing objects outside it. Remember that OnDraw is being called not only in response to your InvalidateRect call but also when the user resizes or exposes the window. Thus, OnDraw is responsible for all drawing in a window, and it has to adapt to whatever invalid rectangle it gets. 无效区域理论 OnLButtonDown函数能整天改变m_nColor的值,但是如果它那么做,OnDraw函数将不能获得调用(除非,例如,用户重新调整视图窗口).OnLButtonDown函数必须调用InvalidateRect函数(此成员函数继承自CWnd类).InvalidateRectc触发Windows的WM_PAINT消息,通过Cview类的映射调用虚函数OnDraw.如必要的话,OnDraw能通过InvalidateRect来访问”无效区域”参数. 你在Windows中有两中方法提高绘图速度.第一种方法,你必须明白Windows只更新那些在无效区域里的象素.因而,你只构造小的无效区域(例如,在OnLButtonDown处理),它重绘的速度将很快.第二种方法, 你要浪费时间去执行在无效区域外的绘图指令.你的OnDraw函数能调用CDC成员函数的GetClipBox来确定无效区域,并且它能避免绘制到其他对象上去.记得,OnDraw不仅仅在被你的InvalidateRect调用时响应,在当用户重绘和重现窗口时也会响应.因而,OnDraw是绘制全部窗口的根本原因所在,并且它能在任何一个无效区域里获得. For Win32 Progrmmers The MFC library makes it easy to attach your own state variables to a window through C++ class data members. In Win32 programming, the WNDCALSS members cbClsExtra and cbWndExtra are available for this purpose, but the code for using this mechanism is so complex that developers tend to use global variables instead. 给Win32的程序员 MFC可以很容易的通过C++类数据成员绑定到你自己的状态变量到窗口上.在Win32的程序设计中,WNDCALSS成员的cbClsExtra和cbWndExtra是用于这个目的,但是这个机制的代码是很复杂的,所以开发人员已趋向于用共用变量来代替它. The Window’s Client Area A window has a rectangular client area that excludes the border, caption bar, menu bar, and any docking toolbars. The CWnd member function GetClientRect supplies you with the client-area dimensions. Normally, you’re not allowed to draw outside the client area, and most mouse message are received only when the cursor is in the client area. 窗口客户区域 一个窗口由边界以内的矩形客户区域,标签栏,菜单栏,和任何可停靠的工具栏组成.CWnd成员函数的GetClientRect可以给你客户区域的尺寸.通常,你是不能绘制客户区域以外的,并且当你的光标在客户区域里时仅接收多数鼠标信息. CRect, Cpoint,and Csize Arithmetic The CRect, Cpoint, and Csize classes are derived from the Windows RECT, POINT, and SIZE structures, and thus they inherit public integer data members, as follows: CRect left, top, right, bottom Cpoint x,y Csize cx,cy If you look in the MFC Library Reference, you’ll see that these three classes have a number of overloaded operators. You can, among other things, do the following: Add a Csize object to a Cpoint object Subtract a Csize object from a Cpoint object Subtract one Cpoint object from another, yielding a Cszie object Add a Cpoint or Csize object to a CRect object Subtract a Cpoint or Csize object from a CRect object The CRect class has member functions that relate to the Csize and Cpoint classes. For example, the TopLeft member function returns a Cpoint object, and the Size member function returns a Csize object. From this, you can begin to see that a Csize object is the “difference between two Cpoint object” and that you can “bias” a CRect object by a Cpoint object. CRect, Cpoint,和Csize的算法 CRect, Cpoint和Csize都是来自于Windows的RECT,POINT和SIZE结构,并且他们都继承自公共变量整型数据成员,如下所示: CRect left, top, right, bottom Cpoint x,y Csize cx,cy 如果你看过MFC库参考书,你将看到这三个类都是可重载的操作数.你能从它们之中,做如下操作: 将CSIZE对象和Cpoint对象相加 从Cpoint对象中减去Csize对象 从另个一个Cpoint对象减去一个Cpoint,Csize对象也将受影响. 将Cpoint对象或Csize对象与CRect对象相加 从CRect对象中减去Cpoint对象或Csize对象 CRect类中的成员函数涉及到Csize和Cpoint类.例如,TopLeft成员函数返回Cpoint对象,而SIZE函数返回Csize对象.从这,你能开始看到Csize对象是”在两个Cpoint对象中是不同的”并且你能通过Cpoint对象”偏爱”CRect对象 Determining Whether a Point Is Inside a Rectangle The CRect class has a member function, PtInRect, that tests a point to see whether is falls inside a rectangle. The second OnLButtonDown parameter, point, is an object of class Cpoint that represents the cursor location in the client area of the window. If you want to know whether that point is inside the m_rectEllipse rectangle , you can use PtInRect in this way: If (m_rectEllipse.PtInRect(point)){ //point is inside rectangle } As you’ll soon see, however, this simple logic applies only if you’re working in device coordinates(which you are at this stage). 决定一个点是否在一个长方形里 CRect类的函数,PtInRect,它是测试一个点是否在一个矩形里面.OnLButtonDown第二个参数,point,是Cpoint对象表示的光标所在窗口客户区的位置.如果你想要知道一个点是否在m_rectEllipse的矩形中,你可以通过PtInRect这个方法: If (m_rectEllipse.PtInRect(point)){ //point is inside rectangle } 同样如你看到的,然而,如果你的工作在设备坐标里,这个只是个简单的逻辑应用(暂时性的) The CRect LPCRECT Operator If you read the MFC Library Reference carefully, you’ll notice that CWnd::InvalidateRect takes an LPCRECT parameter(a pointer to a RECT structure), not a CRect parameter. A CRect parameter is allowed because the CRect class defines an overloaded operator, LPCRECT(),that returns the address of a CRect object, which is equivalent to the address of a RECT object. Thus, the compiler converts CRect arguments to LPCRECT arguments when necessary. You call functions as if they have CRect reference parameters. The following view member functions code retrieves the client rectangle coordinates and stores them in rectClient: CRect rectClient; GetClientRect(rectClient); CRect的 LPCRECT操作符 如果你认真的读过MFC参考书,你将注意到CWnd::InvalidateRect安排了个LPCRECT的参数(一个指向CRECT结构的指针),没有CRECT参数.CRect类定义了允许CRect参数被LPRECT()重载,它返回的地址是CRect对象,那是个跟RECT对象相等的地址.因此,编译器在必需的时候回转化CRect参数到LPCRECT参数的.你调用功能是好象引用了CRECT参数. 视图成员函数代码在重新得到客户相关矩形坐标之后就保存在rectClient中: CRect rectClient; GetClientRect(rectClient); Determining Whether a Point Is Inside an Ellipse The Ex05a code determines whether the mouse hit is inside the rectangle. If you want to make a better test. You can find out whether the hit is inside the ellipse. To do this, you construct an object of class CRgn that corresponds to the ellipse and then use the PtInRegion function instead of PtInRect. Here’s the code: CRgn rgn; Rgn.CreateEllipticRgnIndirect(m_rectEllipse); If(rgn.PtInRegion(poing)){ // point is inside ellipse } Note that the CreateEllipticRgnIndirct function is another function that takes an LPCRECT parameter. It builds a special region structure within Windows that represents an elliptical region inside a window. That structure is then attached to the C++ CRgn object in your program. (The same type of structure can also represent a polygon.) 确定一个点是否在椭圆形内部 Ex05a的例子确定鼠标是否点击在矩形内部.如果你要更好的测试.你能找出是否点击在椭圆里面.这么做,你构造一个符合椭圆形的CRgn对象并使用PtInRegion函数代替PtInRect函数. CRgn rgn; Rgn.CreateEllipticRgnIndirect(m_rectEllipse); If(rgn.PtInRegion(poing)){ // point is inside ellipse } 注意另外一个CreateEllipticRgnIndirct函数的安排了个LPCRECT的参数.它在Windows中构造一个特别的结构并在窗口中描述一个椭圆形的区域.这个结构在你的程序中是附在C++ CRgn对象上的.(同样的结构也能描绘一个多边形.) The Ex05a Example In the Ex05a example, an ellipse (which happens to be a circle) changes color when the user clicks the left mouse button while the mouse cursor is inside the rectangle that bounds the ellipse. You’ll use the view class data members to hold the view’s state, and you’ll use the InvalidateRect function to cause the view to be redrawn. In the Chapter 3 example, drawing in the window depends on only one function, OnDraw. The Ex05a example requires three customized functions(including the constructor) and two data members. The complete Cex05aView header and source code files are shown below.(The steps for creating the program are listed after the code.) All changes to the original MFC Application Wizard output and OnLButtonDown are shown is boldface. Ex05a例子 在Ex05a的例子中,当用户的鼠标光标从矩形移到椭圆中并用鼠标左键单击椭圆(圆也是椭圆)会改变颜色.你将用视图类数据成员掌握视图状态,并且你将用InvalidateRect函数来引发视图重绘. 在第三章的例子中,绘制窗口仅依靠一个函数OnDraw. Ex05a中需要三个自定义的函数(包括构造器)和两个成员函数.在完整的Cex05aView头文件和源代码文件中显示.(这些步骤是在建立程序的代码之后列出的.)在最初的MFC应用向导输入的和OnLButtonDown中黑体字代码表示改变的代码. //代码省略 Using Class View with Ex05a Look at the following Ex05aView.h source code: Afx_msg void OnLButtonDown(UINT nFlags, Cpoint point); Now look at the following Ex05aView.cpp source code: ON_LBUTTONDOWN() The MFC Application Wizard used to generate comment lines for the benefit of the Class Wizard. Fortunately, these comments are no longer needed. Visual C++.NET Keeps track of the entire state of your code at all times, including mapping functions and maps to specific lines in your code. The code wizards available from the Class View’s Properties window add message handler prototypes based on this internal information. In addition, the code wizards generate a skeleton OnLButtonDown member function in Ex05aView.cpp, complete with the correct parameter declarations and return type. Notice how the combination of the MFC Application Wizard and code wizards is different from a conventional code generator. You run a conventional code generator only once and then edit the resulting code. You run the MFC Application Wizard to generate the application only once, but you can run the code wizards as many times as necessary, and you can edit the code at any time. 使用Ex05a中的类视图 考虑以下Ex05aView.h的源代码: afx_msg void OnLButtonDown(UINT nFlags, Cpoing point); 现在考虑下列Ex05aView.cpp的源代码: ON_WM_LBUTTONDOWN() MFC应用程序向导的代码向导常产生有益的注释行.幸运的是,这些注释不在被需要.VC++.NET一直在跟踪你代码的全部状态,包括映射函数和映射你代码中特定的行.代码向导可基于内部信息利用类视图属性窗口增加消息句柄原型.另外,代码向导还在Ex05aView.cpp中产生OnLButtonDown成员函数的框架,连同产生正确的参数定义和返回类型. 注意到MFC应用程序向导和代码向导的结合是异于传统的代码生成器的.你只能运行一次传统的代码产生器来编辑最终的代码.你运行一次MFC应用程序向导产生应用程序,但是你必须运行很多次代码向导并且你能在任何时间编辑你的代码. Using the MFC Application Wizard and the Code Wizard Together The following steps show how you use the MFC Application Wizard and the code wizards available from Class View’s Properties window to create this application: 1. Run the MFC Application Wizard to create Ex05a. Use the wizard to generate an SDI project named Ex05a in the /vcppnet subdirectory. The default class names are shown here. 2. Add the m_rectEllipse and m_nColor date members to Cex05aView. Choose Class View from the View menu in Visual C++.NET and right-click the Cex05aView class. Choose Add Variable and then insert the following two data members: private: CRect m_rectEllipse; Int m_nColor; If you prefer, you can type the above code inside the class declaration in the file ex05aView.h. 3. Use the Class View’s Properties window to add a Cex05aView class message handler. Select the Cex05aView class within Class View, as shown in the following illustration. Next, right-click on Cex05aView and choose Properties. Click the Message button on the Properties window’s toolbar. Scroll down and click on the WM_LBUTTONDOWN entry. You’ll see a drop-down combo box appear next to the entry. Select OnLButtonDown. The OnLButtonDown function will be written into the code and will appear inside the Code Editor. 同时使用MFC应用程序向导和代码向导 以下步骤显示的是教你怎么样使用MFC应用程序向导和类视图属性窗口的代码向导来建立这个应用程序: 1. 运行MFC应用程序向导建立Ex05a. 使用向导在/vcppnet 子目录下产生一个名为Ex05a的SDI项目.缺省的类名如下图所示. 2. 增加m_rectEllipe和m_nColor数据成员到Cex05aView中.从VC++.NET的视图菜单中选择类视图并右键单击Cex05aView类.选择增加变量并插入以下两个数据成员: private: CRect m_rectEllipse; Int m_nColor; 如果你高兴,你能将以上代码插入到ex05aView.h文件的类定义中. 3. 使用类视图属性窗口增加Cex05aView类消息句柄. 在类视图中选择Cex05aView类,如下图所示.接下来,右键单击Cex05aView并选择属性.单击属性窗口工具栏上的消息按钮.向下滚动并单击WM_LBUTTONDOWN入口点.选择<增加>OnLButtonDown. OnLButtonDown函数将被写进代码并在代码编辑器中被公布出来. 4. Edit the OnLButtonDown code in Ex05aView.cpp.Once you add the message handler, the file Ex05aView.cpp will open in the Code Editor and the cursor will be positioned to the newly generated OnLButtonDown member function. The following boldface code (that you type in) replaces the previous code: Void Cex05aView::OnLButtonDown(UINT nFlags, Cpoint point) { if (m_rectEllipse.PtInRect(point)){ if (m_nColor==GRAY_BRUSH){ m_nColor=WHITE_BRUSH; } else { m_nColor=GRAY_BRUSH; } InvalidateRct(m_rectEllipse); } } 5. Edit the constructor and the OnDraw function in Ex05aView.cpp. The following boldface code (that you type in) replaces the previous code: Cex05aView::Cex05aView():m_rectEllipse(0,0,200,200) { m_nColor=GRAY_BRUSH; } . . . void Cex05aView::OnDraw(CDC *pDC) { pDC->SelectStockObject(m_nColor); pDC->Ellipse(m_rectEllipse); } 6. Build and run the Ex05a program. Choose Build from the Build menu or, on the Build toolbar, click the button shown here. Next, choose Start Without Debugging from the Debug menu. The resulting program will respond to clicks of the left mouse button by changing the color of the circle in the view widow. (Don’t click the mouse’s left button quickly in succession; Windows will interpret this as a double-click rather than two single clicks.) 4.在Ex05aView.cpp文件中编辑OnLButtonDown的代码.你增加了一个消息句柄,Ex05aView.cpp将打开代码编辑器并将光标定位到最新产生的OnLButtonDown成员函数中.用下面黑体字代码(你也可以手动打入)替换先前的代码: Void Cex05aView::OnLButtonDown(UINT nFlags, Cpoint point) { if (m_rectEllipse.PtInRect(point)){ if (m_nColor==GRAY_BRUSH){ m_nColor=WHITE_BRUSH; } else { m_nColor=GRAY_BRUSH; } InvalidateRct(m_rectEllipse); } } 5.编辑Ex05aView.cpp的构造器和OnDraw函数. 用下面黑体字代码(你也可以手动打入)替换先前的代码: Cex05aView::Cex05aView():m_rectEllipse(0,0,200,200) { m_nColor=GRAY_BRUSH; } . . . void Cex05aView::OnDraw(CDC *pDC) { pDC->SelectStockObject(m_nColor); pDC->Ellipse(m_rectEllipse); } 6.生成并运行Ex05a程序.从生成菜单中选择生成,或单击工具栏上的生成按钮,如下所示: 示图略 接下来,选择调试菜单中的开始执行(不调试).结果程序的窗口的圆将通过鼠标左键单击来改变颜色.(不要连续快速的点击鼠标,与俩次单击相比Windows将认为这是双击) For Win32 Programmers A conventional Windows-based application registers a series of window classes (not the same as C++ classes) and, in the process, assigns a unique function, known as a window procedure, to each class. Each time the application calls CreateWindow to create a window, it specifies a window class as parameter and thus links the newly created window to a window procedure function. This function, which is called each time Windows sends a message to the window, tests the message code that is passed as a parameter and then executes the appropriate code to handle the message. The MFC application framework has a single window class and window procedure function for most window types. This window procedure function looks up the window handle (passed as a parameter) in the MFC handle map to get the corresponding C++ window object pointer. The window procedure function then uses the MFC runtime class system to determine the C++ class of the window object. Next, it locates the handler function in static tables created by the dispatch map functions, and finally it calls the handler function with the correct window object selected. 给Win32的程序员 一个传统的基于 Windows的应用程序注册一系列的窗口类 ( 不相同于 C++类) 而且,在这过程中,分配唯一的函数,大家知道的window procedure(窗口过程)给每个类,每次应用程序调用CreateWindow建立窗口,它指定窗口类作为参数并连接到新建立的窗口到窗口过程函数.这个函数,每次被Windows调用时就发送一个消息给窗口,测试通过的消息被当作一个参数并执行适当的代码来处理消息. MFC应用程序框架有一个简单的窗口类和处理很多窗口类型的窗口函数.这个窗口过程函数在MFC句柄映射中被看做是个窗口句柄(被当作参数传递),是获得响应C++窗口对象的指针.这个窗口过程函数在当使用MFC运行时类系统时才能确定C++类为一个窗口对象.接下来,它在静态表中通过分派映射函数来定位到句柄函数,同时,最终它调用的句柄函数是根据当前选择的窗口对象来决定的. Using Windows Mapping Modes Up to now, your drawing units have been display pixels, also know as device coordinates. The Ex05a drawing units are pixels because the device context has the default mapping mode, MM_TEXT, assigned to it. The following statement draws a square of 200 by 200 pixels, with its top left corner at the top left of the window’s client area. (Positive y values increase as you move down the window.) pDC->Rectangle(CRect(0,0,200,200)); This square will look smaller on a high-resolution display of 1024-by-768 pixels than on a standard VGA display that is 640-by-480 pixels, and it will look tiny if printed on a laser printer with 600-dpi resolution. (Try Ex05a’s Print Preview feature to see for yourself.) 使用Windows映射模式 到目前为止,你们使用的绘图单位是显示器的像素,同样你也知道了设备坐标.Ex05a的绘图单位是像素因为这是设备上下文的确省映射模式,将MM_TEXT赋值给它.在接下来的声明中绘制了一个200X200像素的正方形,在窗口客户区域最高左边的左上角.(随着y值的增大你可以向下移动窗口.) pDC->Rectangle(CRect(0,0,200,200)); 这个正方形在1024x768像素的高解析度上比在标准VGA的640X480像素上看起来有小,并且如果在600DPI解析度的激光打印机上打印出来的看起来很微小.(你自己试一下Ex05A的打印预览功能.) What if you want the square to be 4-by-4 centimeters(cm),regardless of the display device? Windows provides a number of other mapping modes, or coordinate systems, that you can associate with the device context. Coordinates in the current mapping mode are called logical coordinates. If you assign the MM_HIMETRIC mapping mode, for example, a logical unit is 1/100 millimeter(mm) instead of 1 pixel. In the MM_HIMETRIC mapping mode, the y axis runs in the opposite direction to that in the MM_TEXT mode: y values decrease as you move down. Thus, a 4-by-4-cm square is drawn in logical coordinates this way: pDC->Rectangle((CRect(0,0,4000,-4000)); Looks easy, doesn’t it? Well, it isn’t, because you can’t work only in logical coordinates. Your program is always switching between device coordinates and logical coordinates, and you need to know when to convert between them. This section gives you a few rules that can make your programming life easier. First, you need to know what mapping modes Windows gives you. 什么如果你想要个4X4厘米的正方形,而不管什么显示设备吗? Windows提供了许多其它的映射模式,或者坐标系统,你可以联合设备上下文.坐标在当前映射模式中叫做”逻辑坐标”.如果你分配MM_HIMETRIC映射模式,例如,一个逻辑单位是1/100毫米代替1像素.在MM_HIMETRIC映射模式中,Y轴在MM_TEXT模式中运行在相反的方向:随着Y值的减少同样你在往下移.因而,绘制4X4厘米正方形逻辑坐标的方法如下: pDC->Rectangle((CRect(0,0,4000,-4000)); 看起来容易,但它不是?好吧,它不是,因为你不能仅工作在逻辑坐标中.你的程序已经在设备坐标和逻辑坐标中做了转换,同时你需要知道它们在什么时候转换.这节给你些规则使你能更容易的规划你的程序.首先,你需要知道Windows给你些什么映射模式. The MM_TEXT Mapping Mode At first glance, MM_TEXT appears to be no mapping mode at all, but rather another name for device coordinates. Almost. In MM_TEXT, coordinates map to pixels, values of x increase as you move right, and values of y increase as you move down, but you’re allowed to change the origin through calls to the CDC functions SetViewportOrg and SetWindowOrg. Here’s some code that sets the window origin to (100,100) in logical coordinate space and then draws a 200-by-200-pixel square offset by (100,100).(An illustration of the output is shown in Figure 5-1.) The logical point (100,100) maps to the device point(0,0). A scrolling window uses this kind of transformation. Void CmyView::OnDraw(CDC *pDC) { pDC->SetMapMode(MM_TEXT); pDC->SetWindowOrg(Cpoint(100,100)); pDC->Rectangle(CRect(100,100,300,300)); } MM_TEXT映射模式 第一次看上去,MM_TEXT根本不是映射模式,到更像另一个名字—设备坐标.差不多.在MM_TEXT里,坐标映射到像素,X值增大你就右移,Y值增大你就下移,但是你要允许通过调用CDC函数SetViewportOry 和SetWindowOrg 来改变原点. 这儿有些源代码在逻辑坐标空间设置窗口原点到(100,100)并绘制一个位置(100,100)偏移点的200X200像素的正方形.(这输出显示在图例5-1中.) 这个逻辑点(100,100)映射到设备点(0,0).使用这种转换来卷动一个窗口. Void CmyView::OnDraw(CDC *pDC) { pDC->SetMapMode(MM_TEXT); pDC->SetWindowOrg(Cpoint(100,100)); pDC->Rectangle(CRect(100,100,300,300)); } The Fixed-Scale Mapping Modes One important group of Windows mapping modes provides fixed scaling. You’ve already seen that, in the MM_HIMETRIC mapping mode, x values increase as you move right and y values decrease as you move down. All fixed mapping modes follow this convention, and you can’t change it. The only difference among the fixed mapping modes is the actual scale factor, as shown in Table 5-1. Table 5-1. The Scale Factor for Mapping Modes --------------------------------------------------------------------------- Mapping Mode Logical Unit --------------------------------------------------------------------------- MM_LOENGLISH 0.01 inch MM_HIENGLISH 0.001 inch MM_LOMETRIC 0.1 mm MM_HIMETRIC 0.01 mm MM_TWIPS 1/1440 inch The last mapping mode, MM_TWIPS, is most often used with printers. One twip is 1/20 point. (A point is a type measurement unit that equals exactly 1/72 inch in Windows.) If the mapping mode is MM_TWIPS and you want, for example, 12-point type, you set the character height to 12x20 or 240, twips. 固定比例的映射模式 Windows映射模式提供的固定缩放比例是重要的组成部分.你已经看到过了,MM_HIMETRIC映射模式,x值的增大就右移,Y值的减少就下移.所有的固定映射模式都遵循这个协定,并且你不能改变它.这仅仅不同的是在固定比例映射模式的实际比例因子之中.如下表5-1所示 表5-1 映射模式中的比例因子 --------------------------------------------------------------------------- 映射模式 逻辑单位 --------------------------------------------------------------------------- MM_LOENGLISH 0.01 英寸 MM_HIENGLISH 0.001英寸 MM_LOMETRIC 0.1 厘米 MM_HIMETRIC 0.01厘米 MM_TWIPS 1/1440英寸 最后一个映射模式,MM_TWIPS,经常使用在打印机上.一缇是1/12点.(点是中测量单位在Windows中相当于1/72英寸.) 如果你想要的映射模式是MM_TWIPS,举个例子,12点类型,你要设置字符的高度为12x20,或者240缇. The Variable-Scale Mapping Modes Windows provides two mapping modes, MM_ISOTROPIC and MM_ANISOTROPIC, that allow you to change the scale factor as well as the origin. With these mapping modes, your drawing can change size as the user changes the size of the window. Also, if you invert the scale of one axis, you can “flip” an image about the other axis and you can define your own arbitrary fixed-scale factors. With the MM_ISOTROPIC mode, a 1:1 aspect ration is always preserved. In other words, a circle is always a circle as the scale factor changes. With the MM_ANISOTROPIC mode, the x and y scale factors can change independently. Circles can be squished into ellipses. Here’s an OnDraw function that draws an ellipse that fits exactly in its window: Void CmyView::OnDraw(CDC* pDC) { CRect rectClient; GetClientRect(rectClient); PDC->SetMapMode(MM_ANISOTROPIC); PDC->SetWindowExt(1000,1000); PDC->SetViewportExt(rectClient.right,-rectClient.bottom); Pdc->SetViewportOrg(rectClient.right/2,rectClient.bottom/2); Pdc->Ellipse(CRECT(-500,-500,500,500)); } 可变比例的映射模式 Windows提供两种映射模式,MM_ISOTROPIC和MM_ANISOTROPIC.它允许你们改变比例因子和原点.当用户改变窗口大小时你绘图也能改变大小.同样的,如果你转变了一个轴的比例,你可以”倒转”另一个轴的图象并且你能定义你自己拥有任意固定比例因子. 用MM_ISOTROPIC模式,始终是保持1:1比例的配置.换句话说,当比例改变,一个圆始终是个圆.使用MM_ANISOTROPIC模式,x和y的比例关系是可以独立改变的.圆可以变形为一个椭圆. 在OnDraw函数中为窗口绘制一个正确的椭圆: Void CmyView::OnDraw(CDC* pDC) { CRect rectClient; GetClientRect(rectClient); PDC->SetMapMode(MM_ANISOTROPIC); PDC->SetWindowExt(1000,1000); PDC->SetViewportExt(rectClient.right,-rectClient.bottom); Pdc->SetViewportOrg(rectClient.right/2,rectClient.bottom/2); Pdc->Ellipse(CRECT(-500,-500,500,500)); } What’s going on here? The function SetWindowExt and SetViewportExt work together to set the scale, based on the window’s current client rectangle returned by the GetClientRect function. The resulting window size is exactly 1000-by-1000 logical units. The SetViewportOrg function sets the origin to the center of the window. Thus, a centered ellipse with a radius of 500 logical units fills the window exactly, as illustrated in Figure 5-2. Here are the formulas for converting logical units to device units: X scale factor=x viewport extent /x window extent Y scale factor=y viewport extent /y window extent Device x=logical x X x scale factor + x origin offset Device y=logical y X y scale factor + y origin offset Suppose the window is 448 pixels wide (rectClient.right). The right edge of the ellipse’s client rectangle is 500 logical units from the origin. The x scale factor is 448/1000, and the x origin offset is 448/2 device units. If you use the formulas shown above, the right edge of the ellipse’s client rectangle comes out to 448 device units, the right edge of the window. The x scale factor is expressed as a ration (viewport extent/window extent) because Windows device coordinates are integers, not floating-point values. The extent values are meaningless by themselves. If you substitute MM_ISOTROPIC for MM_ANISOTROPIC in the preceding example, the “ellipse” is always a circle, as shown in Figure 5-3. It expands to fit the smallest dimension of the window rectangle. 这里还有什么?SetWindowExt和SetViewportExt函数的都是设置比例,它们是基于当前窗口的客区域,该区域是由GetClientRect函数返回的.其结果是窗口大小是精确的1000x1000个逻辑单位.SetViewportOrg函数设置原点在窗口的中心.因而,一个有圆心的椭圆以半径500个逻辑单元精确的添满窗口,如插图5-2所示: 通过这些的公式能转换逻辑单位到设备单位: x比例=视口x轴区域/窗口x轴区域 y比例=视口y轴区域/窗口y轴区域 设备 x=逻辑x乘以x比例加上x原点的偏移 设备 y=逻辑y乘以y比例加上y原点的偏移 假设窗口宽度为448像素(rectClient.right).其椭圆客户区域的正确边缘从原点开始500个逻辑单位.x比例是448/1000,并且x原点偏移是448/2个设备单位.如果你用公式来显示以上问题,椭圆的客户区域出来448个设备单位是正确的边缘也是窗口正确的边缘.因为Windows 设备坐标是整数,而不是浮点数的,所以 x比例被表示成一个定量 (视口延伸区/窗囗延伸区).这个延伸区域对他们自己是毫无意义的. 如果在前面的例子中你用MM_ISOTROPIC来代替MM_ANISOTROPIC,这个’椭圆将始终是个圆,如图5-3所示,它将在窗口区域扩张为适合大小圆. Coordinate Conversion Once you set the mapping mode (plus the origin) of a device context, you can use logical coordinate parameters for most CDC member functions. If you get the mouse cursor coordinates from a Windows mouse message ( the point parameter in OnLButtonDown), for example, you’re dealing with device coordinates. Many other MFC library functions, particularly the member functions of class CRect, work correctly only with device coordinates. Note The CRect arithmetic functions use the underlying Win32 RECT arithmetic functions, which assume that right is greater than left and bottom is greater than top. A rectangle (0,0,1000,-1000) in MM_HIMETRIC coordinates, for example, has bottom less than top and cannot be processed by functions such as CRect::PtlnRect unless your program first calls CRect::NormalizeRect, which changes the rectangle’s data members to(0,-1000,1000,0). 坐标转换 一旦你设置了设备上下文的映射模式(加上原点),你就可以使用逻辑坐标参数给很多CDC成员函数了.如果你想从Windows鼠标消息里获得鼠标光标坐标的(OnLButtonDown的point参数),例如,你正在处理的设备坐标.许多其它的MFC函数,典型的如CRect类成员函数,仅适合工作在设备坐标中. 注意 CRect算术函数使用的是Win32下面的RECT算术函数,假设右边比左边大同时底比顶大.一个长方形(0,0,1000,-1000)在MM_HIMETRIC坐标里,例如,有个底小于顶,而且不能被函数处理例如CRect::PtlnRect除非你的程序首先调用了CRect::NormalizeRect,哪改变这长方形的数据成员为(0,-1000,1000,0). Furthermore, you’re likely to need a third set of coordinates that we’ll call physical coordinates. Why do you need another set? Suppose you’re using the MM_LOENGLISH mapping mode in which a logical unit is 0.01 inch, but an inch on the screen represents a foot (12 inches ) in the real world. Now suppose the user works in inches and decimal fractions. A measurement of 26.75 inches translates to 223 logical units, which must ultimately be translated to device point numbers or scaled long integers to avoid rounding-off errors. For the physical-to-logical translation, you’re on your own, but the Windows GDI takes care of the logical-to-device translation for you. The CDC functions LptoDP and DptoLP translate between the two systems as long as the device context mapping mode and associated parameters have already been set. Your job is to decide when to use each system. Here are a few rules of thumb: ■ Assume that the CDC member functions take logical coordinate parameters. ■ Assume that the Cwind member functions take device coordinate parameters. ■ Do all hit-test operations in device coordinates. Define regions in device coordinates. Functions such as Crect::PtInRect work best with device coordinates. ■ Store long-term values in logical or physical coordinates. If you store a point in device coordinates and the user scrolls through a window, that point is no longer valid. 然而,你可能需要第三种坐标系我们叫做物理坐标.为什么你需要另外的设置呢?假设你使用MM_LOENGLISH映射模式在一个逻辑单位是0.01英寸,但是在现实世界里一英寸在屏幕上表现为一英尺.现在假设用户使用的是英寸并带有小数.要测量由26.75英寸转化为223逻辑单位,最后必须转换到设备点数目或比例长整数以避免舍入错误. 从物理到逻辑转换,要你自己动手,但是Windows GDI为你完成逻辑到设备的转换.CDC函数的LptoDP和DptoLP可以在设备上下文映射模式和关联的参数已经被设置的时候在俩个系统之间转换.你的工作是当使用每个系统时作出判断.这儿有些经验方法: ■ CDC成员函数接收逻辑坐标参数 ■ Cwind成员函数接收设备坐标参数 ■ 在设备坐标中做所有的碰撞测试操作. 在设备坐标中定义区域.函数如:Crect::PtInRect工作的最佳方式是使用设备坐标 ■ 在逻辑或物理坐标中储存长期数值.如果你在设备坐标中储存一个点并使用通过窗口来滚动,那么点将不会是长期有效的. Suppose you need to know whether the mouse cursor is inside a rectangle when the user clicks the left mouse button. The code is shown here: //m_rect is CRect data member of the derived view class with MM_LOENGLISH //logical coordinates void CmyView::OnLButtonDown(UINT nFlags, Cpoint point) { CRect rect=m_rect;//rect is a temporary copy of m_rect. CclientDC dc(this); //This is how we get a deice context //for SetMapMode and LptoDP //--more in next chapter dc.SetMapMode(MM_LOENGLISH); dc.LptoDP(rect); //rect is now in device coordinates if (rect.PtInRect(point)){ TRACE(“Mouse cursor is inside the rectangle./n”); } } Notice the use of the TRACE macro(covered in Chapter 2). Note As you’ll soon see, it’s better to set the mapping mode in the virtual Cview function OnPrepareDC instead of in the OnDraw function. 假如你需要知道当用户单击鼠标左键按钮时鼠标光标是否在长方形内.其代码如下所示: //m_rect是来源自视图类的CRect成员函数使用MM_LOENGLISH逻辑坐标 void CmyView::OnLButtonDown(UINT nFlags, Cpoint point) { CRect rect=m_rect; //rect临时复制到m_rect. CclientDC dc(this); //这我们怎么获得设备上下文 //从SetMapMode到LptoDP //--更多在下一章 dc.SetMapMode(MM_LOENGLISH); dc.LptoDP(rect); //rect is now in device coordinates if (rect.PtInRect(point)){ TRACE(“Mouse cursor is inside the rectangle./n”); } } 注意这里用到TRACE宏(隐藏在第二章). 注意.当你将很快看到,它在虚拟的Cview函数OnPrepareDC能很好的设置映射模式并 代替OnDraw函数. The Ex05b Example: Converting to the MM_HIMETRIC Mapping Mode Ex05b is Ex05a converted to MM_HIMETRIC coordinates. The Ex05b project on the companion CD uses new class names and filenames, but the following instructions take you through modifying the Ex05a code. Like Ex05a, Ex05b performs a hit-test so that the ellipse changes color only when you click inside the bounding rectangle. 1. Use the Class View’s Properties window to override the virtual OnPrepareDC function. You can override virtual functions for selected MFC library base classes, including Cview in the Properties window. The code wizards available from the Properties window generate the correct function prototype in the class’s header file and a skeleton function in the CPP file. Select the class name Cex05aView in the Class View, right-click on it, and then choose Properties. Click the Overrides button on the Properties window toolbar and select the OnPrepareDC function in the list. Add the function. Visual C++.NET will load the implementation file so you can edit the function as shown here: Void Cex05aView::OnPrepareDC(CDC* pDC, Cprintinfo* pInfo) { pDC->SetMapMode(MM_HIMETRIC); CView::OnPrepareDC(Pdc, pInfo); } The application framework calls the virtual OnPrepareDC function just before it calls OnDraw. 例题Ex05b:转换到MM_HIMETRIC映射模式 Ex05b是Ex05a转换为MM_HIMETRIC坐标的例子.Ex05b项目在随书的CD里使用了新的类名和文件名,但是你可以通过下面的指示来修改Ex05a的代码.如同Ex05a,当你在长方形中单击椭圆时颜色会改变,所以Ex05b也执行碰撞测试. 1. 使用类视图的属性窗口来重载虚函数OnPrepareDC 你可以从属性窗口中选择MFC基类并重载虚函数,包括Cview.来自于属性窗口的代码向导为类头文件产生适当的函数原型和为CPP文件产生函数的骨干.选择类名为Cex05aView的类视图,右击并选择属性.单击属性窗口工具栏上的重写按钮并在列表中选择OnPrepareDC函数.增加此函数.VC++.NET将加载执行文件因而你可以编辑函数,如下所示: Void Cex05aView::OnPrepareDC(CDC* pDC, Cprintinfo* pInfo) { pDC->SetMapMode(MM_HIMETRIC); CView::OnPrepareDC(Pdc, pInfo); } 应用程序框架将在调用OnDraw函数之前调用虚函数OnPrepareDC函数. 2. Edit the view class constructor. You must change the coordinate value for the ellipse rectangle. That rectangle is now 4-by-4 centimeter instead of 200-by-200 pixels. Note that the y value must be negative; otherwise, the ellipse will be drawn on the “virtual screen” right above your monitor! Change the values as shown here: Cex05aView::Cex05aView() :m_rectEllipse(0,0,4000,-4000) { m_nColor=GRAY_BRUSH; } 3. Edit the OnLButttonDown function. This function must convert the ellipse rectangle to device coordinates in order to do the hit-test. Change the function as shown in the following code: Void Cex05aView::OnLButtonDown(UNIT nFlags, Cpoint point) { CCLientDC dc(this); OnPrepareDC(&dc); CRect rectDevice=m_rectEllipse; dc.LptoDP(rectDevice); if (retDevice.PtInRect(point)){ if (m_nColor==GRAY_BRUSH){ m_nColor=WHITE_BRUSH; } else { m_nColor=GARY_BRUSH; } InvalidateRect(rectDevice); } } 4. Build and run the Ex05b program. The output should look similar to the output from Ex05a, except that the ellipse size will be different. If you try using Print Preview again, the ellipse should appear much larger than it did in Ex05a. 2. 编辑视图类的构造器. 你必须为椭圆范围改变坐标值.这个范围现在是4x4厘米代 替200x200像素.注意y值必须是负数,否则,这个圆将绘制在你显示器的一个”虚拟 屏幕”的右上面.改变这个值的代码如下: Cex05aView::Cex05aView() :m_rectEllipse(0,0,4000,-4000) { m_nColor=GRAY_BRUSH; } 3. 编辑OnLButtonDown函数. 这个函数必须转换椭圆形为设备坐标以便进行碰撞测试.改变这函数的代码如下所示: Void Cex05aView::OnLButtonDown(UNIT nFlags, Cpoint point) { CCLientDC dc(this); OnPrepareDC(&dc); CRect rectDevice=m_rectEllipse; dc.LptoDP(rectDevice); if (retDevice.PtInRect(point)){ if (m_nColor==GRAY_BRUSH){ m_nColor=WHITE_BRUSH; } else { m_nColor=GARY_BRUSH; } InvalidateRect(rectDevice); } } 4. 生成和运行Ex05b程序. 除了椭圆的大小不同之外,你将看到类似于Ex05a的输出.如果你试着使用打印预览,看到的椭圆将比Ex05a中的要大很多. Creating a Scrolling View Window As the lack of scroll bars in Ex05a and Ex05b indicates, the MFC library Cview class, the base class of Cex05bView, doesn’t directly support scrolling. Another MFC library class, CscrollView, does support scrolling. CscrollView is derived from Cview. We’ll create a new program, Ex05c, that uses CscrollView in place of Cview. All the coordinate conversion code you added in Ex05b sets you up for scrolling. The CscrollView class supports scrolling from the scroll bars but not from the keyboard. It’s easy enough to add keyboard scrolling, so we’ll do it 建立滚动视图窗口 Ex05a和Ex05b都缺少滚动支持,由MFC基类Cview生成的Cex05bView是不直接支持滚动的另一个MFC类,CscrollView可以支持滚动.CscrollView是源自于Cview的.我们将建立一个新的程序Ex05c,在Cview里使用CscrollView.所有的坐标转换码在Ex05b中添加设置你的卷动. CscrollView类支持卷动是因为卷动条而不是键盘.它容易通过增加键盘卷动,因而我们将这么做. A Window Is Larger Than What You See If you use the mouse to shrink the size of an ordinary window, the contents of the window will remain anchored at the top left of the window and items at the bottom and/or on the right of the window will disappear. When you expand the window, the items will reappear. You can correctly conclude that a window is larger than the viewport that you see on the screen. The viewport doesn’t have to be anchored at the top left of the window area, however. Through the use of the CWnd functions ScrollWindow and SetWindowOrg, the CscrollView class allows you to move the viewport anywhere within the window, including areas above and to the left of the origin. Scroll Bars Windows makes it easy to display scroll bars at the edges of a window, but Windows by itself doesn’t make any attempt to connect those scroll bars to their window. That’s where the CscrollView class fits in. CscrollView member functions process the WM_HSCROLL and WM_VSCROLL messages sent by the scroll bars to the view. Those functions move the viewport within the window and do all the necessary housekeeping. 一个窗口比你看到的大很多 如果你用鼠标收缩一个普通的窗口,窗口的最左顶部的内容将保留而窗口的底和/或右边的将消失.当你扩大这个窗口,其成员将再出现.你可以正确的判断出在窗口比你在屏幕上的视口要大的多. 然而,视口不能在窗口的最左顶部被停泊.通过使用CWnd函数的ScrollWindow和SetWindowOrg,CscrollView类允许你移动视口到窗口的任何地方,包括上面的提到的区域和最左边的原点. 滚动条 Windows可以很容易的给窗口设置和显示滚动条,但是,Windows单独不做任何连接到它们窗口滚动条的尝试.那是在CscrollView类里准备好的.CscrollView成员函数处理通过滚动条发给视图的消息WM_HSCROLL和WM_VSCROLL来设置滚动条.那些函数移动窗口里的视口并做全部的必须的家务管理.