6.2 新的Win32控件
   从Windows 95和Windows NT 3.51版开始,Windows提供了一些先进的Win32控件.这些新控件弥补了传统控件的某些不足之处,并使Windows的界面丰富多彩且更加友好.MFC的新控件类封装了这些控件,新控件及其对应的控件类如表6.21所示. 
 表6.21 新的Win32控件及其控件类 
| 
  控件名    | 
  功能    | 
  对应的控件类    | 
| 
  动画(Animate)    | 
  可播放avi文件.    | 
  CAnimateCtrl    | 
| 
  热键(Hot Key)    | 
  使用户能选择热键组合.    | 
  CHotKeyCtrl    | 
| 
  列表视图(List View)    | 
  能够以列表、小图标、大图标或报告格式显示数据.    | 
  CListCtrl    | 
| 
  进度条(Progress Bar)    | 
  用于指示进度.    | 
  CProgressCtrl    | 
| 
  滑尺(Slider)    | 
  也叫轨道条(Trackbar),用户可以移动滑尺来在某一范围中进行选择.    | 
  CSliderCtrl    | 
| 
  旋转按钮(Spin Button)    | 
  有时被称为上下控件.有一对箭头按钮,用来调节某一值的大小.    | 
  CSpinButtonCtrl    | 
| 
  标签(Tab)    | 
  用来作为标签使用.    | 
  CTabCtrl    | 
| 
  树形视图(Tree View)    | 
  以树状结构显示数据.    | 
  CTreeCtrl    | 
 本节将主要介绍列表视图、树形视图、进度条、旋转按钮和滑尺控件,动画控件将在第十二章介绍. 
 6.2.1 Win32控件的通知消息 
   较之传统的Windows 3.x控件,新的Win32控件更加复杂和先进.在新控件发送通知消息的同时,往往还需要附加一些数据来描述控件的状态.传统的WM_COMMAND消息通知机制显然不能完成这一任务,因为WM_COMMAND消息的wParam和lParam已经被占满了(见6.1.1),无法容纳新的数据. 
   在Win32中,采用新的WM_NOTIFY消息来实现新控件的消息通知机制.在该消息的wParam中含有控件的ID,lParam中则有一个指针,这个指针指向一个结构.这个结构要么是NMHDR结构,要么是一个以NMHDR结构作为第一个成员的扩充结构.通过NMHDR结构及其扩充结构可以传递附加数据.从理论上讲,可以通过扩充结构传送任意多的数据.需要指出的是,由于NMHDR结构是扩充结构的第一个成员,因此lParam中的指针即可以认为是指向NMHDR结构的,也可以认为指向包含NMHDR结构的扩充结构的. 
 NMHDR结构如下所示: 
 typedef struct tagNMHDR { 
 HWND hwndFrom; //控件窗口的句柄 
 UINT idFrom; //控件的ID 
 UINT code; //控件的通知消息码 
 } NMHDR; 
 一个典型的扩充结构如下所示,该结构用于列表视图控件的LVN_KEYDOWN通知消息. 
 typedef struct tagLV_KEYDOWN { 
 NMHDR hdr; //NMHDR结构作为第一个成员 
 WORD wVKey;  
 UINT flags;  
 } LV_KEYDOWN; 
 有些控件通知消息是所有Win32控件共有的,这些消息在表6.22中列出. 
   
 表6.22 Win32控件共有的通知消息 
| 
  通知消息码    | 
  含义    | 
| 
  NM_CLICK    | 
  用户在控件上单击鼠标左键.    | 
| 
  NM_DBLCLK    | 
  用户在控件上双击鼠标左键.    | 
| 
  NM_RCLICK    | 
  用户在控件上单击鼠标右键.    | 
| 
  NM_RDBLCLK    | 
  用户在控件上双击鼠标右键.    | 
| 
  NM_RETURN    | 
  用户在控件上按回车键.    | 
| 
  NM_SETFOCUS    | 
  控件获得输入焦点.    | 
| 
  NM_KILLFOCUS    | 
  控件失去输入焦点.    | 
| 
  NM_OUTOFMEMORY    | 
  内存不够.    | 
   
   
 WM_NOTIFY的消息映射由宏ON_NOTIFY负责,该消息映射宏具有如下形式: 
 ON_NOTIFY( wNotifyCode, id, memberFxn ) 
 参数wNotifyCode说明了通知消息码,参数id是控件的ID,第三个参数则是消息处理函数名.消息处理函数应该按下面的形式声明,其中参数pNotifyStruct指向NMHDR及其扩充结构,参数result指向一个处理结果. 
 afx_msg void memberFxn( NMHDR * pNotifyStruct, LRESULT * result ); 
 利用ClassWizard可以很方便地加入WM_NOTIFY消息映射及其处理函数,一个典型的WM_NOTIFY消息映射如下所示,其中LVN_KEYDOWN是IDC_LIST1列表视图控件发出的通知消息. 
 ON_NOTIFY( LVN_KEYDOWN, IDC_LIST1, OnKeydownList1 ) 
 消息处理函数OnKeydownList1的定义如下面所示.在函数中ClassWizard自动把pNHHDR指针强制转换成LV_KEYDOWN型并赋给pLVKeyDow指针,这样,在函数中可通过这两个指针访问LV_KEYDOWN扩充结构及其所含的NMHDR结构.另外,在函数返回时,ClassWizard自动将处理结果赋0值. 
 void CMyDlg::OnKeydownList1(NMHDR* pNMHDR, LRESULT* pResult) 
 { 
 LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNMHDR; 
 // TODO: Add your control notification handler 
 // code here 
 *pResult = 0; 
 } 
 可以利用ON_NOTIFY_RANGE宏把多个ID连续的控件发出的相同消息映射到同一个处理函数上,具体形式如下,其中参数id和idLast分别说明明了一组连续的控件ID中的头一个和最后一个ID. 
 ON_NOTIFY_RANGE( wNotifyCode, id, idLast, memberFxn ) 
 相应的消息处理函数应按下面的形式声明,与普通的WM_NOTIFY消息处理函数相比,该函数多了一个参数id用来说明发送通知消息的控件ID. 
 afx_msg void memberFxn( UINT id, NMHDR * pNotifyStruct, LRESULT * result ); 
 ClassWizard不支持ON_NOTIFY_RANGE宏,所以需要手工建立消息映射和消息处理函数. 
 6.2.2 旋转按钮控件 
 旋转按钮(Spin Button)有时也被称为上下控件(Up-Down Control).Windows 95控制面板中的日期/时间程序中就有两个典型的旋转按钮,如图6.2所示.旋转按钮由两个箭头按钮组成,用户在箭头按钮上单击鼠标可以在某一范围内增加或减少某一个值.旋转按钮一般不会单独存在,而是和编辑框或静态正文组成一个多部件控件来共同显示和控制某一个值,用户可以用旋转按钮修改编辑框中的数字,也可以直接在编辑框中修改.例如,在图6.2中,在旋转按钮的左测有一个编辑框,用户可以在编辑框中直接输入新的年份,也可以用旋转按钮来增减编辑框中的年份.通常,把与旋转按钮在一块的编辑框或静态正文称为"伙伴"(buddy). 
 
 
 图6.2 日期/时间程序中的旋转按钮 
 MFC的CSpinButtonCtrl类封装了旋转按钮的功能.CSpinButtonCtrl的成员函数Create负责创建控件,该函数的声明为 
 BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 
   
 参数dwStyle是如表6.23所示的各种控件风格的组合. 
   
 表6.23 旋转按钮控件的风格 
| 
  控件风格    | 
  含义    | 
| 
  UDS_HORZ    | 
  指定一个水平旋转按钮.若不指定该风格则创建一个垂直的旋转按钮.    | 
| 
  UDS_WRAP    | 
  当旋转按钮增大到超过最大值时,自动重置为最小值,当减小至低于最小值时,自动重置为最大值.    | 
| 
  UDS_ARROWKEYS    | 
  当用户按下向下或向上箭头键时,旋转按钮值递增或递减.    | 
| 
  UDS_SETBUDDYINT    | 
  旋转按钮将自动更新伙伴控件中显示的数值,如果伙伴控件能接受输入,则可在伙伴控件中输入新的旋转按钮值.    | 
| 
  UDS_NOTHOUSANDS    | 
  伙伴控件中显示的数值每隔三位没有千位分隔符.    | 
| 
  UDS_AUTOBUDDY    | 
  自动使旋转按钮拥有一个伙伴控件.    | 
| 
  UDS_ALIGNRIGHT    | 
  旋转按钮在伙伴控件的右侧.    | 
| 
  UDS_ALIGNLEFT    | 
  旋转按钮在伙伴控件的左侧.    | 
   
   
 除上表的风格外,一般还要为旋转按钮指定WS_CHILD和WS_VISIBLE风格.创建一个有伙伴的垂直旋转按钮控件,一般应指定的风格为WS_CHILD|WS_VISIBLE|UDS_AUTOBUDDY| UDS_SETBUDDYINT.对于用对话框模板创建的旋转按钮控件,可以在控件的属性对话框中指定上表中列出的控件风格。例如,在属性对话框中选择Auto buddy,相当于指定了UDS_AUTOBUDDY风格. 
 在对话框模板中,可以方便地为旋转按钮指定一个伙伴控件.首先,应该在旋转按钮控件的属性对话框中选择Auto buddy和Set buddy integer属性,并在Alignment栏中选择Left或Right,然后就可以确定伙伴控件了.需要指出的是,旋转按钮并不是把离它最近的控件作为伙伴的.伙伴的选择是以tab顺序为参照的,伙伴控件的tab顺序必需紧挨着按钮控件且比它小.例如,如果某一控件的tab顺序是3,而旋转按钮的tab顺序是4,则不论这两个控件距离有多远,在程序运行时,旋转按钮都会自动与该控件结合在一起,形成伙伴关系. 
|  提示:在本章的开头说过,用ClassWizard无法为Win32控件创建数据变量.但我们可以为旋转按钮的伙伴控件(如编辑框)创建一个数据变量,该变量可看成是旋转按钮的数据变量.  | 
 
 通过CSpinButtonCtrl的成员函数,可以对旋转按钮进行查询和设置: 
 用GetRange和SetRange来查询和设置旋转按钮值的范围,缺省时值的范围是1-100.这两个函数的声明为
void GetRange( int &lower, int& upper ) const;
void SetRange( int nLower, int nUpper );
第一个参数是最小值,该值不能小于UD_MINVAL,第二个参数是最大值,该值不能大于UD_MAXVAL.值的范围不能超过UD_MAXVAL. 
 用GetPos和SetPos来查询和设置旋转按钮的当前值.函数的声明为
int GetPos( ) const;
int SetPos( int nPos ); 
 用GetBase和SetBase来查询和设置旋转按钮值的计数制.函数的声明为
UINT GetBase( ) const;
int SetBase( int nBase );
如果参数nBase是10,则伙伴控件中显示的数值是十进制的,如果nBase是16,则是十六进制的. 
 用GetBuddy和SetBuddy来查询和设置旋转按钮的伙伴.上面已讲了在对话框模板中设置伙伴控件的方法,如果是用Create手工创建旋转按钮,则可以用SetBuddy来设置伙伴.函数的声明为
CWnd* GetBuddy( ) const;
CWnd* SetBuddy( CWnd* pWndBuddy );
参数pWndBuddy是指向伙伴控件对象的CWnd型指针. 
 可以用GetAccel和SetAccel来查询和设置旋转按钮的加速值.在平时,在旋转按钮上按一下只会增/减一个单位,而当按住按钮超过一定时间时,递增或递减的幅度将会加大到指定的加速值,从而加快了增减的速度.如果对缺省的加速值不满意,可以用SetAccel设置新的加速值.可以有一套以上的加速值.函数的声明为
UINT GetAccel( int nAccel, UDACCEL* pAccel ) const;
BOOL SetAccel( int nAccel, UDACCEL* pAccel );
参数nAccel指定了UDACCEL结构数组的大小.参数pAccel指向一个UDACCEL结构数组.UDACCEL结构含有加速值的信息,其定义如下
typedef struct { 
 int nSec; //加速值生效需要的时间(以秒为单位) 
 int nInc; //加速值 
 } UDACCEL; 
 旋转按钮常被认为是一个简化的滚动条.除了表6.22列出的通知消息外,旋转按钮特有的滚动通知消息是通过WM_HSCROLL和WM_VSCROLL消息发出的.消息处理函数OnHScroll或OnVScroll分别用来处理水平或垂直旋转按钮的事件通知.由于伙伴控件中的内容会自动随旋转按钮变化,所以旋转按钮的通知消息意义不大.如果非要处理通知消息,一个典型的OnVscroll函数如下所示: 
 void CMyDialog::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) 
 { 
 CSpinButtonCtrl* pSpin=(CSpinButtonCtrl*)pScrollBar; 
 int nPosition; 
 if(pSpin= =&m_Spin) //判断是否是该旋转按钮发来的消息 
 { 
 nPosition=m_Spin.GetPos( ); //获取旋转按钮的当前值 
 . . . . . . 
 } 
 . . . . . . 
 }
 
 6.2.3 滑尺控件 
 滑尺(Slider)有时也被称作轨道条(Trackbar),在轨道条中有一个滑尺,在轨道条上通常会标有刻度,用户通过移动滑尺,可以在一个指定的范围内选择一个不精确的值.轨道条可用来调节一个模拟量,也可以用来在一些离散值中进行选择.在Windows 95中,大量使用了轨道条控件,例如,在控制面板中的键盘和鼠标设置程序中就使用了轨道条控件,如图6.3所示.轨道条不仅接受鼠标输入,也可以接受象左右箭头键、PgUp和PgDown这样的键盘输入. 
 
 
 图6.3 鼠标设置程序中的轨道条控件 
 与选择按钮不同,轨道条是一种模糊型的输入控件,用户不需要进行精确的选择,只要大致调整一下大小就行了.轨道条的这种特性非常符合人的行为习惯,因而在有些情况下是很有用,例如,对于音量的调节,显然用轨道条比用旋转按钮更符合人的日常习惯. 
 轨道条的滑尺的移动具有离散性.例如,如果指定轨道条的范围是5,那么滑尺只能在包括轨道条两端在内的6个均匀的位置上移动.当然,如果范围很大,则用户就感觉不出是离散的了. 
 轨道条控件与传统的滚动条控件有很多相似之处,实际上,前者是对后者的一种改进.除了表6.22列出的通知消息外,轨道条控件是依靠WM_HSCROLL和WM_VSCROLL来发送与滑尺有关的通知消息的,并且通知消息与滚动条极为相似.通知消息包括TB_BOTTOM、TB_LINEDOWN、TB_LINEUP、TB_TOP、TB_PAGEDOWN、TB_PAGEUP、TB_ENDTRACK、TB_THUMBPOSITION、TB_THUMBTRACK.对照滚动条的通知消息,读者不难明白这些消息码的含义.其中前四个消息只有在用键盘移动滑尺时才会发出,最后两个消息只有在用鼠标拖动滑尺时才会发出.与滚动条不同的是,Windows会自动把滑尺移动到新位置上. 
 MFC的CSliderCtrl类封装了轨道条.CSliderCtrl类的Create成员函数负责控件的创建,该函数的声明为 
 BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 
   
 参数参数dwStyle是如表6.24所示的各种控件风格的组合. 
   
 表6.24 轨道条控件的风格 
| 
  控件风格    | 
  含义    | 
| 
  TBS_HORZ    | 
  指定一个水平轨道条.该风格是默认的.    | 
| 
  TBS_VERT    | 
  指定一个垂直轨道条.    | 
| 
  TBS_AUTOTICKS    | 
  在范围设定后,自动为轨道条加上刻度.    | 
| 
  TBS_NOTICKS    | 
  轨道条无刻度.    | 
| 
  TBS_BOTTOM    | 
  在水平轨道条的底部显示刻度,可与TBS_TOP一起使用.    | 
| 
  TBS_TOP    | 
  在水平轨道条的顶部显示刻度,可与TBS_BOTTOM一起使用.    | 
| 
  TBS_RIGHT    | 
  在垂直轨道条的右侧显示刻度,可与TBS_LEFT一起使用.    | 
| 
  TBS_LEFT    | 
  在垂直轨道条的左侧显示刻度,可与TBS_RIGHT一起使用.    | 
| 
  TBS_BOTH    | 
  在轨道条的上下部或左右两侧都显示刻度.    | 
| 
  TBS_ENABLESELRANGE    | 
  在轨道条中显示一个选择范围.    | 
   
   
 除上表的风格外,一般还要为轨道条指定WS_CHILD和WS_VISIBLE风格.要创建一个具有刻度的水平轨道条,一般应指定风格为WS_CHILD|WS_VISIBLE|TBS_HORZ| TBS_AUTOTICKS.对于用对话框模板创建的轨道条控件,可以在控件的属性对话框中指定上表中列出的控件风格。例如,在属性对话框中选择Autoticks,相当于指定了TBS_AUTOTICKS风格. 
 通过调用CSliderCtrl类的成员函数,可以对轨道条进行查询和设置: 
 用GetRange和SetRange来查询和设置轨道条的范围,缺省的范围是0-100.函数的声明为
void GetRange( int& nMin, int& nMax ) const;
void SetRange( int nMin, int nMax, BOOL bRedraw = FALSE );
参数nMin和nMax分别是最小和最大值,参数bRedraw为TRUE时将重绘控件. 
 用GetPos和SetPos来查询和设置轨道条的当前值.函数的声明为
int GetPos( ) const;
void SetPos( int nPos ); 
 用GetLineSize和SetLineSize来查询和设置在按一下左箭头键或右箭头键时滑尺的移动量,该移动量的缺省值是1个单位.函数的声明为
int GetLineSize( ) const;
int SetLineSize( int nSize ); 
 用GetPageSize和SetPageSize来查询和设置滑尺的块移动量,块移动量是指当按下PgUp或PgDown键时滑尺的移动量.函数的声明为
int GetPageSize( ) const;
int SetPageSize( int nSize ); 
 用SetTicFreq设置轨道条的刻度的频度.缺省的频度是每个单位都有一个刻度,在范围较大时,为了使刻度不至于过密,需要调用该函数设置一个合理的频度.函数的声明为
void SetTicFreq( int nFreq );
参数nFreq说明了两个刻度之间的间隔. 
 用函数SetTic来在指定位置设置刻度.Windows自动显示的刻度是均匀的,利用该函数可以人为设置不均匀的刻度,该函数的声明为
BOOL SetTic( int nTic ); 
 用函数ClearTics来清除所有的刻度.该函数的声明为
void ClearTics( BOOL bRedraw = FALSE ); 
   
 6.2.4 进度条控件 
 进度条(Progress Bar)的用途是向用户显示程序的进度.进度条是Win32控件中最简单的控件,只需少数设置即可.Windows 95中使用进度条的一个例子是磁盘扫描(ScanDisk)程序,如图6.4所示.进度条显示的数据是不精确的,它是一种模糊型的输出控件. 
 
 
 图6.4 磁盘扫描程序中的进度条 
 MFC的CProgressCtrl类封装了进度条控件.该类的Create成员函数负责创建控件,该函数的声明为 
 BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 
   
 进度条没有专门的风格,所以参数dwStyle只能指定普通的窗口风格,一般只需指定WS_CHILD|WS_VISIBLE就可以了. 
 CProgressCtrl类提供了少量的成员函数用来设置进度条: 
 void SetRange( int nLower, int nUpper );
该函数用来设置进度条的范围.参数nLow和nUpper分别指定了最小和最大值,缺省时进度条的范围是0-100. 
 int SetPos( int nPos );
用来设置进度条的当前进度.函数返回的是进度条的前一个进度. 
 int StepIt( );
使进度增加一个步长,步长值是由SetStep函数设置的,缺省的步长值是10.函数返回进度条的前一个进度. 
 int SetStep( int nStep );
用来设置步长值.函数返回原来的步长值. 
   
 6.2.5 树形视图控件 
 树形视图(Tree View)是一种特殊的列表,它能以树形分层结构显示数据.在Windows 95的资源管理器(Windows Explorer)的左侧窗口中就有一个用于显示目录的典型的树形视图,如图6.5所示.在树形视图中,每个表项显示一个标题(Label),有时还会显示一幅图象,图象和标题分别提供了对数据的形象和抽象描述.通过图6.5可以看出,树形视图可以很清楚的显示出数据的分支和层次关系.由此可见,树形视图非常适合显示象目录,网络结构等这样的复杂数据.传统的列表框不能分层显示数据,因此树形视图可以看作是对列表框的一种重要改进. 
 
 
 图6.5 资源管理器中的树形视图和列表视图 
 
树形视图是一种复杂的控件,它的复杂性体现在数据项之间具有分支和层次关系.例如,如果要向树形视图中加入新的项,则必需描述出该项与树形视图中已有项的相互关系,而不可能象往列表框中加入新项那样,调用一下AddString就完事了.另外,树形视图可以在每一项标题的左边显示一幅图象,这使控件显得更加形象生动,但同时也增加了控件的复杂程度. 
 在讨论如何使用树形视图控件以前,有必要先介绍一下与该控件有关的一些数据类型: 
 HTREEITEM型句柄.Windows用HTREEITEM型句柄来代表树形视图的一项,程序通过HTREEITEM句柄来区分和访问树形视图的各个项. 
 TV_ITEM结构.该结构用来描述一个表项,它包含了表项的各种属性,其定义如下 
 typedef struct _TV_ITEM  
 { tvi  
 UINT  mask; /*包含一些屏蔽位(下面的括号中列出)的组合,用来表明结构的哪些成员是有效的*/ 
 HTREEITEM hItem; //表项的句柄(TVIF_HANDLE) 
 UINT state; //表项的状态(TVIF_STATE) 
 UINT stateMask; //状态的屏蔽组合(TVIF_STATE) 
 LPSTR pszText; //表项的标题正文(TVIF_TEXT) 
 int cchTextMax; //正文缓冲区的大小(TVIF_TEXT) 
 int iImage; //表项的图象索引(TVIF_IMAGE) 
 int iSelectedImage; //选中的项的图象索引(TVIF_SELECTEDIMAGE) 
 int cChildren; /*表明项是否有子项(TVIF_CHILDREN),为1则有,为0则没有*/ 
 LPARAM lParam; //一个32位的附加数据(TVIF_PARAM) 
 } TV_ITEM, FAR *LPTV_ITEM; 
 如果要使树形视图的表项显示图象,需要为树形视图建立一个位图序列,这时,iImage说明表项显示的图象在位图序列中的索引,iSelectedImage则说明了选中的表项应显示的图象,在绘制图标时,树形视图可以根据这两个参数提供的索引在位图序列中找到对应的位图.lParam可用来放置与表项相关的数据,这常常是很有用的.state和stateMask的常用值在表6.25中列出,其中stateMask用来说明要获取或设置哪些状态. 
   
 表6.25 树形视图表项项的常用状态 
| 
  状态    | 
  对应的状态屏蔽    | 
  含义    | 
| 
  TVIS_SELECTED    | 
  同左    | 
  项被选中.    | 
| 
  TVIS_EXPANDED    | 
  同左    | 
  项的子项被展开.    | 
| 
  TVIS_EXPANDEDONCE    | 
  同左    | 
  项的子项曾经被展开过.    | 
| 
  TVIS_CUT    | 
  同左    | 
  项被选择用来进行剪切和粘贴操作.    | 
| 
  TVIS_FOCUSED    | 
  同左    | 
  项具有输入焦点.    | 
| 
  TVIS_DROPHILITED    | 
  同左    | 
  项成为拖动操作的目标.    | 
   
   
 TV_INSERTSTRUCT结构.在向树形视图中插入新项时要用到该结构,其定义为 
 typedef struct _TV_INSERTSTRUCT { 
 HTREEITEM hParent; //父项的句柄  
 HTREEITEM hInsertAfter; //说明应插入到同层中哪一项的后面 
 TV_ITEM item;  
 } TV_INSERTSTRUCT; 
 如果hParent的值为TVI_ROOT或NULL,那么新项将被插入到树形视图的最高层(根位置).hInsertAfter的值可以是TVI_FIRST、TVI_LAST或TVI_SORT,其含义分别是将新项插入到同一层中的开头、最后或排序插入. 
 NM_TREEVIEW结构.树形视图的大部分通知消息都会附带指向该结构的指针以提供一些必要的信息.该结构的定义为 
 typedef struct _NM_TREEVIEW { nmtv  
 NMHDR hdr; //标准的NMHDR结构 
 UINT action; //表明是用户的什么行为触发了该通知消息 
 TV_ITEM itemOld; //旧项的信息 
 TV_ITEM itemNew; //新项的信息 
 POINT ptDrag; //事件发生时鼠标的客户区坐标 
 } NM_TREEVIEW;  
 TV_KEYDOWN结构.提供与键盘事件有关的信息.该结构的定义为 
 pedef struct _TV_KEYDOWN { tvkd  
 NMHDR hdr; //标准的NMHDR结构 
 WORD wVKey; //虚拟键盘码 
 UINT flags; //为0 
 } TV_KEYDOWN; 
 TV_DISPINFO结构.提供与表项的显示有关的信息.该结构的定义为 
 typedef struct _TV_DISPINFO { tvdi  
 NMHDR hdr;  
 TV_ITEM item;  
 } TV_DISPINFO; 
 MFC的CTreeCtrl类封装了树形视图.该类的Create成员函数负责控件的创建,该函数的声明为 
 BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 
   
 其中参数dwStyle是如表6.26所示的控件风格的组合. 
   
 表6.26 树形视图的风格 
| 
  控件风格    | 
  含义    | 
| 
  TVS_HASLINES    | 
  在父项与子项间连线以清楚地显示结构.    | 
| 
  TVS_LINESATROOT    | 
  只在根部画线.    | 
| 
  TVS_HASBUTTONS    | 
  显示带有"+"或"-"的小方框来表示某项能否被展开或已展开.    | 
| 
  TVS_EDITLABELS    | 
  用户可以编辑表项的标题.    | 
| 
  TVS_SHOWSELALWAYS    | 
  即使控件失去输入焦点,仍显示出项的选择状态.    | 
| 
  TVS_DISABLEDRAGDROP    | 
  不支持拖动操作.    | 
   
   
 除上表的风格外,一般还要指定WS_CHILD和WS_VISIBLE窗口风格.对于用对话框模板创建的树形视图控件,可以在控件的属性对话框中指定上表中列出的控件风格。例如,在属性对话框中选择Has buttons,相当于指定了TVS_HASBUTTONS风格. 
 CTreeCtrl类提供了大量的成员函数.对于常用的函数,这里结合实际应用的需要,介绍如下: 
 向树形视图中插入新的表项.首先应提供一个TV_INSERTSTRUCT结构并在该结构中对插入项进行描述.如果要在树形视图中显示图象,则应该先创建一个CImageList对象并使该对象包含一个位图序列,然后调用SetImageList为树形视图设置位图序列.然后调用InsertItem函数把新项插入到树形视图中.函数的声明为 
 CImageList* SetImageList( CImageList * pImageList, int nImageListType );
参数pImageList指向一个CImageList对象,参数nImageListType一般应为TVSIL_NORMAL. 
 HTREEITEM InsertItem( LPTV_INSERTSTRUCT lpInsertStruct );
参数lpInsertStruct指向一个TV_INSERTSTRUCT结构.函数返回新插入项的句柄. 
 用DeleteItem来删除指定项,用DeleteAllItems删除所有项.函数的声明为
BOOL DeleteItem( HTREEITEM hItem );
BOOL DeleteAllItems( );
操作成功则函数返回TRUE,否则返回FALSE. 
 树形视图控件会根据用户的输入自动展开或折叠子项.但有时需要在程序中展开或折叠指定项,则应该调用Expand,该函数的声明为
BOOL Expand( HTREEITEM hItem, UINT nCode );
参数hItem指定了要展开或折叠的项.参数nCode是一个标志,指定了函数应执行的操作,它可以是TVE_COLLAPSE(折叠)、TVE_COLLAPSERESET(折叠并移走所有的子项)、TVE_EXPAND(展开)或TVE_TOGGLE(在展开和折叠状态之间翻转). 
 要查询或设置选择项,应调用GetSelectedItem或SelectItem.函数的声明为
HTREEITEM GetSelectedItem( );
BOOL SelectItem( HTREEITEM hItem ); 
 要对指定的项查询或设置,可调用GetItem和SetItem.用这两个功能强大的函数,几乎可以查询和设置项的所有属性,包括表项的正文、图像及选择状态.函数的声明为
BOOL GetItem( TV_ITEM* pItem );
BOOL SetItem( TV_ITEM* pItem );
参数pItem是指向TV_ITEM结构的指针,函数是通过该结构来查询或设置指定项的,在调用函数前应该使该结构的hItem成员有效以指定表项.CTreeCtrl还提供了一系列函数可完成GetItem和SetItem的部分功能,其中GetItemState、GetItemText、GetItemData、GetItemImage和ItemHasChildren函数用于查询,SetItemState、SetItemText、SetItemData和SetItemImage函数用于设置. 
 在使用树形视图控件时,一个经常遇到的问题是对于一个已知表项,如何找到与该项有某种关系的项,例如,父项、子项、兄弟项、下一个或前一个可见的项.利用功能强大的GetNextItem函数,可以解决这个问题.该函数也可以用来搜索具有某种状态的表项.GetNextItem在遍历树形视图时是很有用的,它的声明为
HTREEITEM GetNextItem( HTREEITEM hItem, UINT nCode );
参数hItem指定了一个项.参数nCode是一个标志,标明了与指定项的关系,nCode可以是如表6.27所示的各种标志.如果找到相关的项,函数返回该项的句柄,否则函数返回NULL. 
   
 表6.27 关系标志 
| 
  标志    | 
  含义    | 
| 
  TVGN_CARET    | 
  返回当前的选择项.    | 
| 
  TVGN_CHILD    | 
  返回指定表项的子项.    | 
| 
  TVGN_DROPHILITE    | 
  返回拖动操作的目标项.    | 
| 
  TVGN_FIRSTVISIBLE    | 
  返回第一个可见项.    | 
| 
  TVGN_NEXT    | 
  返回指定项的下一个兄弟项(Sibling Item).    | 
| 
  TVGN_NEXTVISIBLE    | 
  返回指定项的后一个可见项.    | 
| 
  TVGN_PARENT    | 
  返回指定项的父项.    | 
| 
  TVGN_PREVIOUS    | 
  返回指定项的前一个兄弟项.    | 
| 
  TVGN_PREVIOUSVISIBLE    | 
  返回指定项的前一个可见项.    | 
| 
  TVGN_ROOT    | 
  返回位于最高层(根位置)的第一个表项.    | 
   
   
 CTreeCtrl类提供了一系列的成员函数来完成GetNextItem的某一项功能,包括GetRootItem、GetFirstVisibleItem、GetNextVisibleItem、GetPrevVisibleItem、GetChildItem、GetNextSiblingItem、GetPrevSiblingItem、GetParentItem、GetSelectedItem和GetDropHilightItem. 
   
 除了表6.22列出的控件消息外,树形视图控件还会发送自己特有的通知消息,其中常用的有下面这几个: 
 TVN_SELCHANGING和TVN_SELCHANGED.在用户改变了对表项的选择时,控件会发送这两个消息.消息会附带一个指向NM_TREEVIEW结构的指针,程序可从该结构中获得必要的信息.两个消息都会在该结构的itemOld成员中包含原来的选择项的信息,在itemNew成员中包含新选择项的信息,在action成员中表明是用户的什么行为触发了该通知消息(若是TVC_BYKEYBOARD则表明是键盘,若是TVC_BYMOUSE则表明是鼠标,若是TVC_UNKNOWN则表示未知).两个消息的不同之处在于,如果TVN_SELCHANGING的消息处理函数返回TRUE,那么就阻止选择的改变,如果返回FALSE,则允许改变. 
 TVN_KEYDOWN.该消息表明了一个键盘事件.消息会附带一个指向TV_KEYDOWN结构的指针,通过该结构程序可以获得按键的信息. 
 TVN_BEGINLABELEDIT和TVN_ENDLABELEDIT.分别在用户开始编辑和结束编辑项的标题时发送.消息会附带一个指向TV_DISPINFO结构的指针,程序可从该结构中获得必要的信息.在前者的消息处理函数中,可以调用GetEditControl成员函数返回一个指向用于编辑标题的编辑框的指针,如果处理函数返回FALSE,则允许编辑,如果返回TRUE,则禁止编辑.在后者的消息处理函数中,TV_DISPINFO结构中的item.pszText指向编辑后的新标题,如果pszText为NULL,那么说明用户放弃了编辑,否则,程序应负责更新项的标题,这可以由SetItem或SetItemText函数来完成. 
   
 树形视图控件还可以支持拖放操作,限于篇幅,这里就不作介绍了. 
 6.2.6 列表视图控件 
   列表视图(List View)用来成列地显示数据.在Windows 95的资源管理器的右侧窗口中就有一个典型的列表视图,如图6.5所示.列表视图的表项通常包括图标(Icon)和标题(Label)两部分,它们分别提供了对数据的形象和抽象描述.列表视图控件是对传统的列表框的重大改进,它能够以下列四种格式显示数据.读者可以在资源管理器中的视图(View)菜单中切换列表视图的显示格式,来看看四种格式的不同之处. 
- 
 大图标格式(Large Icons).可逐行显示多列表项,图标的大小可由应用程序指定,通常是32×32像素,在图标的下面显示标题. 
 - 
 小图标格式(Small Icons).可逐行显示多列表项,图标的大小可由应用程序指定,通常是16×16像素,在图标的右面显示标题.表项以行的方式组织. 
 - 
 列表格式(List).与小图标格式类似.不同之处在于表项是逐列多列显示的. 
 - 
 报告格式(Report或Details).每行仅显示一个表项,在标题的左边显示一个图标,表项可以不显示图标而只显示标题.表项的右边可以附加若干列子项(Subitem),子项只显示正文.在控件的顶端还可以显示一个列表头用来说明各列的类型.列表视图的报告格式很适合显示报表(如数据库报表). 
 
   
 在讨论如何使用列表视图控件以前,显向读者介绍一下与该控件有关的一些数据类型: 
 LV_COLUMN结构.该结构仅用于报告式列表视图,用来描述表项的某一列.要想向表项中插入新的一列,需要用到该结构.LV_COLUMN结构的定义为 
 typedef struct _LV_COLUMN {  
 UINT mask; //屏蔽位的组合(见下面括号),表明哪些成员是有效的. 
 int fmt; /*该列的表头和子项的标题显示格式(LVCF_FMT).可以是LVCFMT_CENTER、LVCFMT_LEFT或LVCFMT_RIGHT*/ 
 int cx; //以像素为单位的列的宽度(LVCF_FMT) 
 LPTSTR pszText; //指向存放列表头标题正文的缓冲区(LVCF_TEXT) 
 int cchTextMax; //标题正文缓冲区的长度(LVCF_TEXT) 
 int iSubItem; //说明该列的索引(LVCF_SUBITEM) 
 } LV_COLUMN; 
 LV_ITEM结构.该结构用来描述一个表项或子项,它包含了项的各种属性,其定义为 
 typedef struct _LV_ITEM {  
 UINT mask; //屏蔽位的组合(见下面括号),表明哪些成员是有效的 
 int iItem; //从0开始编号的表项索引(行索引) 
 int iSubItem; /*从1开始编号的子项索引(列索引),若值为0则说明该成员无效,结构描述的是一个表项而不是子项*/ 
 UINT state; //项的状态(LVIF_STATE) 
 UINT stateMask; //项的状态屏蔽 
 LPTSTR pszText; //指向存放项的正文的缓冲区(LVIF_TEXT) 
 int cchTextMax; //正文缓冲区的长度(LVIF_TEXT) 
 int iImage; //图标的索引(LVIF_IMAGE) 
 LPARAM lParam; // 32位的附加数据(LVIF_PARAM) 
 } LV_ITEM; 
 其中lParam成员可用来存储与项相关的数据,这在有些情况下是很有用的.state和stateMask的值如表6.28所示,stateMask用来说明要获取或设置哪些状态. 
   
 表6.28 列表视图的状态 
| 
  状态    | 
  对应的状态屏蔽    | 
  含义    | 
| 
  LVIS_CUT    | 
  同左    | 
  项被选择用来进行剪切和粘贴操作.    | 
| 
  LVIS_DROPHILITED    | 
  同左    | 
  项成为拖动操作的目标.    | 
| 
  LVIS_FOCUSED    | 
  同左    | 
  项具有输入焦点.    | 
| 
  LVIS_SELECTED    | 
  同左    | 
  项被选中.    | 
 NM_LISTVIEW结构.该结构用于存储列表视图的通知消息的有关信息,大部分列表视图的通知消息都会附带指向该结构的指针.NM_LISTVIEW的定义为 
 typedef struct tagNM_LISTVIEW {  
 NMHDR hdr; //标准的NMHDR结构 
 int iItem; //表项的索引,若为-1则无效 
 int iSubItem; //子项的索引,若为0则无效 
 UINT uNewState; //项的新状态 
 UINT uOldState; //项原来的状态 
 UINT uChanged; /*取值与LV_ITEM的mask成员相同,用来表明哪些状态发生了变化*/ 
 POINT ptAction; //事件发生时鼠标的客户区坐标 
 LPARAM lParam; //32位的附加数据 
 } NM_LISTVIEW; 
 LV_DISPINFO结构.该结构包含了与项的显示有关的信息,其定义为 
 typedef struct tagLV_DISPINFO {  
 NMHDR hdr;  
 LV_ITEM item;  
 } LV_DISPINFO; 
 LV_KEYDOWN结构.该结构包含一些与键盘有关的信息,其定义为 
 typedef struct tagLV_KEYDOWN {  
 NMHDR hdr;  
 WORD wVKey; //虚拟键盘码 
 UINT flags; //总为0 
 } LV_KEYDOWN; 
 MFC的CListCtrl类封装了列表视图控件.该类的Create函数负责创建控件,函数的声明为 
 BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID ); 
   
 其中参数dwStyle是如表6.29所示的控件风格的组合.
 
 表6.29 列表视图的风格 
| 
  控件风格    | 
  含义    | 
| 
  LVS_ALIGNLEFT    | 
  当显示格式是大图标或小图标时,标题放在图标的左边.缺省情况下标题放在图标的下面.    | 
| 
  LVS_ALIGNTOP    | 
  当显示格式是大图标或小图标时,标题放在图标的上边.    | 
| 
  LVS_AUTOARRANGE    | 
  当显示格式是大图标或小图标时,自动排列控件中的表项.    | 
| 
  LVS_EDITLABELS    | 
  用户可以修改标题.    | 
| 
  LVS_ICON    | 
  指定大图标显示格式.    | 
| 
  LVS_LIST    | 
  指定列表显示格式.    | 
| 
  LVS_NOCOLUMNHEADER    | 
  在报告格式中不显示列的表头.    | 
| 
  LVS_NOLABELWRAP    | 
  当显示格式是大图标时,使标题单行显示.缺省时是多行显示.    | 
| 
  LVS_NOSCROLL    | 
  列表视图无滚动条.    | 
| 
  LVS_NOSORTHEADER    | 
  报告列表视图的表头不能作为排序按钮使用.    | 
| 
  LVS_OWNERDRAWFIXED    | 
  由控件的拥有者负责绘制表项.    | 
| 
  LVS_REPORT    | 
  指定报告显示格式.    | 
| 
  LVS_SHAREIMAGELISTS    | 
  使列表视图共享图像序列.    | 
| 
  LVS_SHOWSELALWAYS    | 
  即使控件失去输入焦点,仍显示出项的选择状态.    | 
| 
  LVS_SINGLESEL    | 
  指定一个单选择列表视图.缺省时可以多项选择.    | 
| 
  LVS_SMALLICON    | 
  指定小图标显示格式.    | 
| 
  LVS_SORTASCENDING    | 
  按升序排列表项.    | 
| 
  LVS_SORTDESCENDING    | 
  按降序排列表项.    | 
   
 
除上表的风格外,一般还要指定WS_CHILD和WS_VISIBLE窗口风格.风格组合WS_CHILD| WS_VISIBLE|LVS_REPORT|LVS_AUTOARRANGE|LVS_EDITLABLES|LVS_SINGLESEL将指定一个自动排列的、可编辑标题的、单选择报告式列表视图控件.要指定大图标、小图标或列表式的列表视图控件,则应该把LVS_REPORT换成LVS_ICON、LVS_SMALLICON或LVS_LIST. 
 对于用对话框模板创建的列表视图控件,可以在控件的属性对话框中指定上表中列出的控件风格。例如,在属性对话框的Styles页的View栏中选择Icon,相当于指定了LVS_ICON风格. 
 CListCtrl类提供了大量的成员函数.在这里,我们结合实际应用来介绍一些常用的函数: 
 列的插入和删除.在以报告格式显示列表视图时,一般会显示一列表项和多列子项.在初始化列表视图时,先要调用InsertColumn插入各个列,该函数的声明为
int InsertColumn( int nCol, const LV_COLUMN* pColumn );
其中参数nCol是新列的索引,参数pColumn指向一个LV_COLUMN结构,函数根据该结构来创建新的列.若插入成功,函数返回新列的索引,否则返回-1.
要删除某列,应调用DeleteColumn函数,其声明为
BOOL DeleteColumn( int nCol ); 
 表项的插入.要插入新的表项,应调用InsertItem.如果要显示图标,则应该先创建一个CImageList对象并使该对象包含用作显示图标的位图序列.然后调用SetImageList来为列表视图设置位图序列.函数的声明为 
 int InsertItem( const LV_ITEM* pItem );
参数pItem指向一个LV_ITEM结构,该结构提供了对表项的描述.若插入成功则函数返回新表项的索引,否则返回-1. 
 CImageList* SetImageList( CImageList* pImageList, int nImageList );
参数pImageList指向一个CImageList对象,参数nImageList用来指定图标的类型,若其值为LVSIL_NORMAL,则位图序列用作显示大图标,若值为LVSIL_SMALL,则位图序列用作显示小图标.可用该函数同时指定一套大图标和一套小图标. 
 要删除某表项,应调用DeleteItem,要删除所有的项,应调用DeleteAllItems.一旦表项被删除,其子项也被删除.函数的声明为
BOOL DeleteItem( int nItem );
BOOL DeleteAllItems( ); 
 调用GetItemText和SetItemText来查询和设置表项及子项显示的正文.SetItemText的一个重要用途是对子项进行初始化.函数的声明为
int GetItemText( int nItem, int nSubItem, LPTSTR lpszText, int nLen ) const;
CString GetItemText( int nItem, int nSubItem ) const;
BOOL SetItemText( int nItem, int nSubItem, LPTSTR lpszText );
其中参数nItem是表项的索引(行索引),nSubItem是子项的索引(列索引),若nSubItem为0则说明函数是针对表项的.参数lpszText指向正文缓冲区,参数nLen说明了缓冲区的大小.第二个版本的GetItemText返回一个含有项的正文的Cstring对象. 
 调用GetItem和SetItem来查询和设置.用这两个功能强大的函数,几乎可以查询和设置指定项的所有属性,包括正文、图标及选择状态.函数的声明为
BOOL GetItem( LV_ITEM* pItem ) const;
BOOL SetItem( const LV_ITEM* pItem );
参数pItem是指向LV_ITEM结构的指针,函数是通过该结构来查询或设置指定项的,在调用函数前应该使该结构的iItem或iSubItem成员有效以指定表项或子项.CListCtrl还提供了一系列函数可完成GetItem和SetItem的部分功能,其中GetItemState、GetItemText和GetItemData函数用于查询,SetItemState、SetItemText和SetItemData函数用于设置. 
 要查询表项的数目,应该调用GetItemCount,其声明为 int GetItemCount( ); 
 要寻找与指定表项项相关的表项,或寻找具有某种状态的表项,应该调用GetNextItem.该函数的一个重要用处是搜索被选择的表项.函数的声明为
int GetNextItem( int nItem, int nFlags ) const;
参数nItem是指定项的索引,参数nFlags是如表6.30所示的标志,用来指定查询的关系.函数返回搜索到的表项的索引,若未找到则返回-1. 
 表6.30 关系标志 
| 
  标志    | 
  含义    | 
| 
  LVNI_ABOVE    | 
  返回位于指定表项上方的表项.    | 
| 
  LVNI_ALL    | 
  缺省标志,返回指定表项的下一个表项(以索引为序).    | 
| 
  LVNI_BELOW    | 
  返回位于指定表项下方的表项.    | 
| 
  LVNI_TOLEFT    | 
  返回位于指定表项左边的表项.    | 
| 
  LVNI_TORIGHT    | 
  返回位于指定表项右边的表项.    | 
| 
  LVNI_DROPHILITED    | 
  返回拖动操作的目标表项.    | 
| 
  LVNI_FOCUSED    | 
  返回具有输入焦点的表项.    | 
| 
  LVNI_SELECTED    | 
  返回被选择的表项.    | 
   
   
 要对表项进行排列、排序和搜索,可分别调用Arrange、SortItems和FindItems函数来完成. 
 有时需要在列表视图创建后动态地改变其显示格式,例如,资源管理器中的列表视图就可以在四中显示格式之间切换.改变显示格式其实就是改变列表视图的风格,要改变控件的风格,应先调用::GetWindowLong获取控件原来的风格,并对其进行修改,然后调用::SetWindowLong设置新的风格.这两个函数不是成员函数,而是Windows API函数,用来查询和设置窗口的风格. 
   
 除了表6.22列出的控件消息外,列表视图控件还会发送自己特有的通知消息,其中常用的有下面这几个: 
 LVN_ITEMCHANGING和LVN_ITEMCHANGED.当列表视图的状态发生变化时,会发送这两个通知消息.例如,当用户选择了新的表项时,程序就会收到这两个消息.消息会附带一个指向NM_LISTVIEW结构的指针,消息处理函数可从该结构中获得状态信息.两个消息的不同之处在于,前者的消息处理函数如果返回TRUE,那么就阻止选择的改变,如果返回FALSE,则允许改变. 
 LVN_KEYDOWN.该消息表明了一个键盘事件.消息会附带一个指向LV_KEYDOWN结构的指针,通过该结构程序可以获得按键的信息. 
 LVN_BEGINLABELEDIT和LVN_ENDLABELEDIT.分别在用户开始编辑和结束编辑标题时发送.消息会附带一个指向LV_DISPINFO结构的指针.在前者的消息处理函数中,可以调用GetEditControl成员函数返回一个指向用于编辑标题的编辑框的指针,如果处理函数返回FALSE,则允许编辑,如果返回TRUE,则禁止编辑.在后者的消息处理函数中,LV_DISPINFO结构中的item.pszText指向编辑后的新标题,如果pszText为NULL,那么说明用户放弃了编辑,否则,程序应负责更新表项的标题,这可以由SetItem或SetItemText函数来完成. 
   
 列表视图控件还可以支持拖放操作,这里就不详细介绍了. 
 6.2.7 测试新型Win32控件的一个例子 
 本小节将向读者提供一个测试Win32控件的例子.测试程序名为Ctrl32,其界面如图6.6所示,该程序对前面介绍的五种Win32控件均进行了测试: 
- 
 对树形视图的测试着重演示了如何在各个层次上加入表项以及如何使表项显示图象,表项在平常状态下和选中状态下将显示不同的图象. 
 - 
 对列表视图的测试包括如何生成一个报告式列表视图,如何在四个显示格式间切换以及如何使表项显示图标.读者可以在列表视图下面的下拉列表式组合框选择不同的显示格式. 
 - 
 轨道条的测试包括初始化及WM_HSCROLL消息的处理.进度条的进度将会随着滑尺位置的改变而改变. 
 - 
 演示了如何为旋转按钮指定伙伴控件以及旋转按钮的初始化. 
 
 
 
 图6.6 Ctrl32测试程序 
   
 请读者按下列步骤操作: 
 用AppWizard建立一个基于对话框的MFC应用程序,程序名为Ctrl32. 
 插入两个位图(Bitmap)资源,其ID分别是IDB_BITMAP1和IDB_BITMAP2,树形视图和列表视图将这用两幅位图来为表项显示图象.两个位图的尺寸分别为64×16和128×32.每个位图都包含4个子图,每个子图中都有一个颜色不同的矩形或圆,请按图6.7和表6.31绘制.两个位图的子图都是一样的,只不过大小不同,一个是16×16,一个是32×32. 
 
 
 图6.7 用于树形视图和列表视图的位图 
 表6.31 
| 
  子图的形状    | 
  前景色    | 
  背景色    | 
| 
  矩形    | 
  蓝色    | 
  黄色    | 
| 
  矩形    | 
  红色    | 
  淡蓝色    | 
| 
  圆    | 
  蓝色    | 
  黄色    | 
| 
  圆    | 
  红色    | 
  淡蓝色    | 
 打开IDD_CTRL32_DIALOG对话框模板资源,清除OK按钮外的所有控件并把OK按钮的标题改为C&lose.将对话框的大小调整为300×150,然后按图6.6和表6.32加入控件,并按表6.32用ClassWizard为CCtrl32Dlg类加入成员变量,注意,这些成员变量都是控件对象. 
   
 表6.32 
| 
  控件类型    | 
  控件ID    | 
  需设置的属性    | 
  对应的控件对象名    | 
| 
  树形视图    | 
  缺省    | 
  选择Has buttons、Has lines、Lines at root和Edit labels.    | 
  m_Tree    | 
| 
  列表视图    | 
  缺省    | 
  选择Report格式、Auto arrange、Edit lables    | 
  m_List    | 
| 
  轨道条    | 
  缺省    | 
  选择Tick marks和Autoticks.    | 
  m_Slider    | 
| 
  进度条    | 
  缺省    | 
  缺省    | 
  m_Progress    | 
| 
  组合框    | 
  缺省    | 
  选择Drop List类型,不选择Sort.加入4个表项:Icon、Small icon、List和Report.    | 
  m_ListBox    | 
| 
  编辑框    | 
  缺省    | 
  缺省.要注意其tab顺序比旋转按钮小1.    | 
    | 
| 
  旋转按钮    | 
  缺省    | 
  选择Auto buddy和Set buddy integer    | 
  m_Spin    | 
   
   
 在CCtrl32Dlg类的定义处为改类加入下面两个成员,这两个CImageList对象用来向树形视图和列表视图提供位图序列.
CImageList m_SmallImageList;
CImageList m_LargeImageList; 
 用ClassWizard为CCtrl32Dlg类加入如表6.33所示的消息处理函数.其中OnHScroll函数用来处理轨道条的通知消息,OnSelchangeCombo用来切换列表视图的显示格式. 
   
 表6.33 CCtrl32Dlg类的控件通知消息处理函数 
| 
  Object IDS    | 
  Messages    | 
  Member functions    | 
| 
  CCtrl32Dlg    | 
  WM_HSCROLL    | 
  OnHScroll    | 
| 
  IDC_COMBO1    | 
  CBN_SELCHANGE    | 
  OnSelchangeCombo    | 
   
   
 最后,请读者按清单6.2修改程序. 
   
 清单6.2 CCtrl32Dlg类的部分源代码 
 // Ctrl32Dlg.cpp : implementation file 
   
 . . . . . . 
   
 char *szLabel[2]={"Rectangle","Circle"}; 
 char *szColumn[3]={"Shape","Fore color","Back color"}; 
 char *szData[4][3]= 
 {{"Rectangle","Blue","Yellow"}, 
 {"Rectangle","Red","Blue"}, 
 {"Circle","Blue","Yellow"}, 
 {"Circle","Red","Blue"}}; 
 DWORD nStyle[4]={LVS_ICON,LVS_SMALLICON,LVS_LIST,LVS_REPORT}; 
   
 BOOL CCtrl32Dlg::OnInitDialog() 
 { 
 CDialog::OnInitDialog(); 
 . . . . . . 
 // TODO: Add extra initialization here 
   
 //初始化旋转按钮 
 m_Spin.SetRange(0,200);  
 m_Spin.SetPos(0); 
 //初始化轨道条 
 m_Slider.SetRange(0,20);  
 m_Slider.SetTicFreq(2); 
 m_Slider.SetLineSize(2); 
 m_Slider.SetPageSize(4); 
 m_Slider.SetPos(0); 
 //初始化进度条 
 m_Progress.SetRange(0,20);  
 m_Progress.SetPos(0); 
 //创建位图序列,用于树形视图和列表视图显示图像 
 m_SmallImageList.Create(IDB_BITMAP1,16,0,FALSE); //16*16的位图序列 
 m_LargeImageList.Create(IDB_BITMAP2,32,0,FALSE); //32*32的位图序列 
 //初始化树形视图 
 TV_INSERTSTRUCT tvInsert;  
 HTREEITEM hItem; 
 int i,j; 
 char buffer[20]; 
   
 m_Tree.SetImageList(&m_SmallImageList,TVSIL_NORMAL); 
   
 tvInsert.item.mask=TVIF_TEXT|TVIF_IMAGE|TVIF_SELECTEDIMAGE; 
 for(i=0;i<2;i++) 
 { 
 tvInsert.hParent=NULL; //指定该项位于最高层 
 tvInsert.hInsertAfter=TVI_LAST; 
 tvInsert.item.pszText=szLabel[i]; 
 tvInsert.item.iImage=i*2; //指定表项显示的图象 
 tvInsert.item.iSelectedImage=i*2+1; //指定选择状态下应显示的图象 
 hItem=m_Tree.InsertItem(&tvInsert); 
 for(j=0;j<3;j++) 
 {  
 tvInsert.hParent=hItem; //指定该项为子项 
 tvInsert.hInsertAfter=TVI_SORT; 
 sprintf(buffer,"%s%d",szLabel[i],j); 
 tvInsert.item.pszText=buffer; 
 m_Tree.InsertItem(&tvInsert); 
 } 
 } 
 //初始化列表视图 
 LV_COLUMN lvc; 
 LV_ITEM lvi; 
   
 m_List.SetImageList(&m_SmallImageList,LVSIL_SMALL); 
 m_List.SetImageList(&m_LargeImageList,LVSIL_NORMAL); 
 m_ComboBox.SelectString(-1,"Report"); 
   
 lvc.mask=LVCF_FMT|LVCF_WIDTH|LVCF_TEXT|LVCF_SUBITEM; 
 lvc.fmt=LVCFMT_LEFT; 
 for(i=0;i<3;i++) //插入各列 
 { 
 lvc.pszText=szColumn[i]; 
 if(i==0) 
 lvc.cx=m_List.GetStringWidth(szColumn[0])+50; 
 else 
 lvc.cx=m_List.GetStringWidth(szColumn[i])+15; 
 lvc.iSubItem=i; 
 m_List.InsertColumn(i,&lvc); 
 } 
 lvi.mask=LVIF_TEXT|LVIF_IMAGE; 
 lvi.iSubItem=0; 
 for(i=0;i<4;i++) //插入表项 
 { 
 lvi.pszText=szData[i][0]; 
 lvi.iItem=i; 
 lvi.iImage=i; 
 m_List.InsertItem(&lvi); 
 for(j=1;j<3;j++) 
 m_List.SetItemText(i,j,szData[i][j]); 
 } 
   
 return TRUE; // return TRUE unless you set the focus to a control 
 } 
   
 void CCtrl32Dlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)  
 { 
 // TODO: Add your message handler code here and/or call default 
   
 CSliderCtrl* pSlider=(CSliderCtrl*)pScrollBar; 
 //判断是否是m_Slider轨道条发送的消息 
 if(&m_Slider!=pSlider) return;  
 m_Progress.SetPos(m_Slider.GetPos()); 
 CDialog::OnHScroll(nSBCode, nPos, pScrollBar); 
 } 
   
 void CCtrl32Dlg::OnSelchangeCombo()  
 { 
 // TODO: Add your control notification handler code here 
   
 long lStyle; 
 lStyle=GetWindowLong(m_List.GetSafeHwnd(),GWL_STYLE); 
 //清除所有与显示格式有关的风格标志 
 lStyle&=~(LVS_ICON|LVS_SMALLICON|LVS_LIST|LVS_REPORT); 
 lStyle|=nStyle[m_ComboBox.GetCurSel()]; 
 //设置新的风格 
 SetWindowLong(m_List.GetSafeHwnd(),GWL_STYLE,lStyle);  
 m_List.Invalidate(); //刷新 
 } 
   对控件的初始化工作在OnInitDialog中完成.函数中使用的各种结构和函数在前面均介绍过,并不难懂.唯一需要说明的是CImageList对象的使用.CImageList对象用来存储多个大小相同的图象,如果程序中要用到大量的尺寸相同的位图或图标,可以用CImageList对象把它们组织成图象序列来使用,通过指定序列的索引,可以获得序列中的图象.树形视图和列表视图均使用CImageList对象来设置图象序列.在OnInitDialog函数中,调用了CImageList::Create来创建一个图象序列.该函数的声明为 
 BOOL Create( UINT nBitmapID, int cx, int nGrow, COLORREF crMask ); 
   参数nBitmapID是位图资源的ID,在该位图中包含了一些尺寸相同的子图,参数cx说明了序列中每幅图象的宽度(以像素为单位),参数nGrow说明了当对象包含的图象序列增大时应预留的空位个数,参数crMask如果为TRUE,则说明图象序列中包含屏蔽图象. 
   在函数中调用了CTreeCtrl::SetImageList和CListCtrl::SetImageList来为树形视图和列表视图设置图象序列.注意列表视图对象m_List设置了两个图象序列,分别用于小图标和大图标显示格式.在插入表项时,只要指定了图象序列的索引,表项就可以显示相应的图象. 
   OnHScroll函数负责处理轨道条发出的通知消息,函数根据轨道条的当前位置设置进度条的进度.OnSelchangeCombo函数响应用户对下拉列表式组合框的选择,函数先调用CWnd::GetWindowLong获取列表视图原来的风格,然后调用CWnd::SetWindowLong设置新的风格(注意调用CWnd::GetSafeHwnd可以获得窗口的句柄),最后调用CWnd::Invalidate刷新列表视图. 
   Ctrl32程序只是对常用的Win 32控件进行了一些基本的测试。Visual C++提供了一个全面测试Win 32控件的MFC例子cmnctrls(在samples \ mfc \ general \ cmnctrls目录下),有兴趣的读者可以研究一下。