[转贴] Windows编程和面向对象技术 chap6

第六课 控件

  在上一课中,同学们已经接触到了一些常用的控件。控件实际上是子窗口,在应用程序与用户进行交互的过程中,控件是主要角色。因此,有必要对控件进行详细的讨论。

  Windows提供了五花八门的标准控件,这些控件可粗分为两类。一类是在Windows 3.x就已支持的传统控件,一类是Windows 95/NT支持的新型Win32控件。Windows提供控件的目的就是方便程序与用户的交互。应用程序应该根据自己的实际情况,选择合适的控件。

  不管是什么类型的控件,一般都具有WS_CHILD和WS_VISIBLE窗口风格.WS_CHILD指定窗口为子窗口,WS_VISIBLE使窗口是可见的.另外,大部分控件还具有WS_TABSTOP风格,WS_TABSTOP使控件具有Tabstop属性.

  MFC提供了大量的控件类,它们封装了控件的功能。通过这些控件类,程序可以方便地创建控件,对控件进行查询和控制。所有的控件类都是CWnd类的直接或间接派生类.

  在学习这一讲之前,有几个问题需要先行说明:

在本节中,同学们会经常遇到控件类的Create成员函数,该函数负责创建控件.在上一章中同学们已经试验过,只要把控件放入对话框模板中,在调用DoModal或Create创建对话框时,框架会根据模板资源中的信息自动地创建控件。但有时需要用手工动态地创建控件,这通常需要按下面的步骤进行:

构建一个控件对象。

调用控件对象的成员函数Create来创建控件。

在6.3节中将对控件的创建进行详细讨论。

在上一章中,介绍了用ClassWizard为对话框类创建与传统控件对应的成员变量的方法.成员变量可以是数据变量或控件对象.需要指出的是,对于新的Win32控件,只能创建控件对象,不能创建数据变量.

在控件类的函数说明中,读者会经常看到LPCTSTR参数类型,LPCTSTR是一个宏,相当于const char far *,它用来说明指向常量字符串的指针.MFC的字符串类CString定义了一个与LPCTSTR同名的操作符,该操作符可以把一个CString对象转换成一个常量字符串.因此,如果函数的参数是用LPCTSTR来说明的,则既可以向该参数传递一个指向常量字符串的指针,也可以传递一个CString对象.

  这一讲将对一些常用的控件及其控件类进行较详细的讨论,讨论的侧重点包括控件的创建、控件类的成员函数以及控件的通知消息。具体讲,本章主要包括以下主要内容:

传统控件

新型Win32控件

控件的技术总结

在非对话框窗口中使用控件

 

6.1 传统控件

  在上一课的表5.1已经列出了Windows的传统控件及其对应的控件类。在这些控件中,读者应该重点掌握命令按钮、选择框、单选按钮、编辑框、列表框和组合框。

.1.1 传统控件的控件通知消息

  控件通过向父窗口发送控件通知消息来表明发生了某种事件.例如,当用户在按钮上单击鼠标时,按钮控件会向父窗口发送BN_CLICKED消息.传统控件的通知消息实际上是通过WM_COMMAND消息发给父窗口的(滚动条除外),在该消息的wParam中含有通知消息码(如BN_CLICKED)和控件的ID,在lParam中则包含了控件的句柄.

  利用ClassWizard可以很容易地为控件通知消息加入消息映射和消息处理函数,这在上一章中已经演示过了.传统控件的消息映射宏是ON_XXXX,其中XXXX表示通知消息码,如BN_CLICKED.ON_XXXX消息映射如下所示,该宏有两个参数,一个是控件的ID,一个是消息处理函数名.

ON_XXXX(nID, memberFxn)

消息处理函数的声明应该有如下形式:

afx_msg void memberFxn( );

例如,某按钮的BN_CLICKED消息的消息映射及其处理函数的声明如下所示

ON_BN_CLICKED(IDC_ADD,OnAdd)

afx_msg void OnAdd( );

  有时,为了处理方便,需要把多个ID连续的控件发出的相同消息映射到同一个处理函数上.这就要用到ON_CONTROL_RANGE宏.ON_CONTROL_RANGE消息映射宏的第一个参数是控件消息码,第二和第三个参数分别指明了一组连续的控件ID中的头一个和最后一个ID,最后一个参数是消息处理函数名。例如,要处理一组单选按钮发出的BN_CLICKED消息,相应的消息映射如下所示:

ON_CONTROL_RANGE(BN_CLICKED, IDC_FIRST, IDC_LAST, OnRadioClicked)

函数OnRadioClicked的声明如下,该函数比上面的OnAdd多了一个参数nID以说明发送通知消息的控件ID.

afx_msg void OnRadioClicked(UINT nID);

ClassWizard不支持ON_CONTROL_RANGE宏,所以需要手工建立消息映射和消息处理函数.

提示:事实上,在使用ClassWizard时只要运用一个小小的技巧,就可以把不同控件的通知消息映射到同一个处理函数上,也可以把一个控件的不同通知消息映射到同一个处理函数上.这个技巧就是在用ClassWizard创建消息处理函数时,指定相同的函数名即可.此方法的优点在于控件的ID不必是连续的,缺点是处理函数没有nID参数,因而不能确定是哪一个控件发送的消息.

6.1.2 静态控件

  静态控件包括静态正文(Static Text)和图片控件(Picture)。静态正文控件用来显示正文。图片控件可以显示位图、图标、方框和图元文件,在图片控件中显示图片的好处是不必操心图片的重绘问题。静态控件不能接收用户的输入。在上一章中,读者已经用过静态正文和组框控件。图片控件的例子可以在AppWizard创建的IDD_ABOUTBOX对话框模板中找到,在该模板中有一个图片控件用来显示图标。

  静态控件的主要起说明和装饰作用。MFC的CStatic类封装了静态控件。CStatic类的成员函数Create负责创建静态控件,该函数的声明为

BOOL Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );

  参数lpszText指定了控件显示的正文。dwStyle指定了静态控件的风格,表6.1显示了静态控件的各种风格,dwStyle可将这些风格组合起来。rect是一个对RECT或CRect结构的引用,用来说明控件的位置和尺寸。pParentWnd指向父窗口,该参数不能为NULL。nID则说明了控件的ID。如果创建成功,该函数返回TRUE,否则返回FALSE.

表6.1 静态控件的风格

控件风格

含义

SS_BLACKFRAME

指定一个具有与窗口边界同色的框(缺省为黑色)。

SS_BLACKRECT

指定一个具有与窗口边界同色的实矩形(缺省为黑色)。

SS_CENTER

使显示的正文居中对齐,正文可以回绕。

SS_GRAYFRAME

指定一个具有与屏幕背景同色的边框。

SS_GRAYRECT

指定一个具有与屏幕背景同色的实矩形。

SS_ICON

使控件显示一个在资源中定义的图标,图标的名字有Create函数的lpszText参数指定。

SS_LEFT

左对齐正文,正文能回绕。

SS_LEFTNOWORDWRAP

左对齐正文,正文不能回绕。

SS_NOPREFIX

使静态正文串中的&不是一个热键提示符。

SS_NOTIFY

使控件能向父窗口发送鼠标事件消息。

SS_RIGHT

右对齐正文,可以回绕。

SS_SIMPLE

使静态正文在运行时不能被改变并使正文显示在单行中。

SS_USERITEM

指定一个用户定义项。

SS_WHITEFRAME

指定一个具有与窗口背景同色的框(缺省为白色)。

SS_WHITERECT

指定一个具有与窗口背景同色的实心矩形(缺省为白色)。

  除了上表中的风格外,一般还要为控件指定WS_CHILD和WS_VISIBLE窗口风格。一个典型的静态正文控件的风格为WS_CHILD|WS_VISIBLE|SS_LEFT。

  对于用对话框模板编辑器创建的静态控件,可以在控件的属性对话框中指定表6.1中列出的控件风格。例如,可以在静态正文控件的属性对话框中选择Simple,这相当于指定了SS_SIMPLE风格。

  Cstatic类主要的成员函数在表6.2中列出。可以利用CWnd类的成员函数GetWindowText,SetWindowText和GetWindowTextLength等函数来查询和设置静态控件中显示的正文.

表6.2 CStatic类的主要成员函数

函数声明

用途

HBITMAP SetBitmap( HBITMAP hBitmap );

指定要显示的位图。

HBITMAP GetBitmap( ) const;

获取由SetBitmap指定的位图。

HICON SetIcon( HICON hIcon );

指定要显示的图标。

HICON GetIcon( ) const;

获取由SetIcon指定的图标。

HCURSOR SetCursor( HCURSOR hCursor );

指定要显示的光标图片。

HCURSOR GetCursor( );

获取由SetCursor指定的光标。

HENHMETAFILE SetEnhMetaFile( HENHMETAFILE hMetaFile );

指定要显示的增强图元文件。

HENHMETAFILE GetEnhMetaFile( ) const;

获取由SetEnhMetaFile指定的图元文件。

  静态控件较简单,故这里就不举例说明了。

6.1.3 按钮控件

  按钮是指可以响应鼠标点击的小矩形子窗口。按钮控件包括命令按钮(Pushbutton)、检查框(Check Box)、单选按钮(Radio Button)、组框(Group Box)和自绘式按钮(Owner-draw Button)。命令按钮的作用是对用户的鼠标单击作出反应并触发相应的事件,在按钮中既可以显示正文,也可以显示位图。选择框控件可作为一种选择标记,可以有选中、不选中和不确定三种状态。单选按钮控件一般都是成组出现的,具有互斥的性质,即同组单选按钮中只能有一个是被选中的。组框用来将相关的一些控件聚成一组.自绘式按钮是指由程序而不是系统负责重绘的按钮。

  按钮主要是指命令按钮、选择框和单选按钮。后二者实际上是一种特殊的按钮,它们有选择和未选择状态。当一个选择框处于选择状态时,在小方框内会出现一个“√”,当单选按钮处于选择状态时,会在圆圈中显示一个黑色实心圆。此外,检查框还有一种不确定状态,这时检查框呈灰色显示,不能接受用户的输入,以表明控件是无效的或无意义的。

  按钮控件会向父窗口发出如表6.3所示的控件通知消息。

表6.3 按钮控件的通知消息

消息

含义

BN_CLICKED

用户在按钮上单击了鼠标。

BN_DOUBLECLICKED

用户在按钮上双击了鼠标。

FC的CButton类封装了按钮控件。CButton类的成员函数Create负责创建按钮控件,该函数的声明为

BOOL Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );

  参数lpszCaption指定了按钮显示的正文。dwStyle指定了按钮的风格,如表6.4所示,dwStyle可以是这些风格的组合。rect说明了按钮的位置和尺寸。pParentWnd指向父窗口,该参数不能为NULL。nID是按钮的ID。如果创建成功,该函数返回TRUE,否则返回FALSE.

 

表6.4 按钮的风格

控件风格

含义

BS_AUTOCHECKBOX

同BS_CHECKBOX,不过单击鼠标时按钮会自动反转。

BS_AUTORADIOBUTTON

同BS_RADIOBUTTON,不过单击鼠标时按钮会自动反转。

BS_AUTO3STATE

同BS_3STATE,不过单击按钮时会改变状态。

BS_CHECKBOX

指定在矩形按钮右侧带有标题的选择框。

BS_DEFPUSHBUTTON

指定缺省的命令按钮,这种按钮的周围有一个黑框,用户可以按回车键来快速选择该按钮。

BS_GROUPBOX

指定一个组框。

BS_LEFTTEXT

使控件的标题显示在按钮的左边。

BS_OWNERDRAW

指定一个自绘式按钮。

BS_PUSHBUTTON

指定一个命令按钮。

BS_RADIOBUTTON

指定一个单选按钮,在圆按钮的右边显示正文。

BS_3STATE

同BS_CHECKBOX,不过控件有三种状态:选择、未选择和变灰。

 

 

除了上表中的风格外,一般还要为控件指定WS_CHILD、WS_VISIBLE和WS_TABSTOP窗口风格,WS_TABSTOP使控件具有Tabstop属性。创建一个普通按钮应指定的风格为WS_CHILD|WS_VISIBLE|WS_TABSTOP。创建一个普通检查框应指定风格WS_CHILD|WS_VISIBLE|WS_TABSTOP| BS_AUTOCHECKBOX。创建组中第一个单选按钮应指定风格WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_GROUP| BS_AUTORADIOBUTTON,组中其它单选按钮应指定风格则不应该包括WS_TABSTOP和WS_GROUP。

对于用对话框模板编辑器创建的按钮控件,可以在控件的属性对话框中指定表6.4中列出的控件风格。例如,在命令按钮的属性对话框中选择Default button,相当于指定了BS_DEFPUSHBUTTON。

CButton类的主要的成员函数有:

UINT GetState( ) const;
该函数返回按钮控件的各种状态。可以用下列屏蔽值与函数的返回值相与,以获得各种信息。

0x0003。用来获取检查框或单选按钮的状态。0表示未选中,1表示被选中,2表示不确定状态(仅用于检查框)。

0x0004。用来判断按钮是否是高亮度显示的。非零值意味着按钮是高亮度显示的。当用户点击了按钮并按主鼠标左键时,按钮会呈高亮度显示。

0x0008。非零值表示按钮拥有输入焦点。

void SetState( BOOL bHighlight );
当参数bHeightlight值为TRUE时,该函数将按钮设置为高亮度状态,否则,去除按钮的高亮度状态。

int GetCheck( ) const;
返回检查框或单选按钮的选择状态。返回值0表示按钮未被选择,1表示按钮被选择,2表示按钮处于不确定状态(仅用于检查框)。

void SetCheck( int nCheck );
设置检查框或单选按钮的选择状态。参数nCheck值的含义与GetCheck返回值相同。

UINT GetButtonStyle( ) const;
获得按钮控件的BS_XXXX风格。

void SetButtonStyle( UINT nStyle, BOOL bRedraw = TRUE );
设置按钮的风格。参数nStyle指定了按钮的风格。bRedraw为TRUE则重绘按钮,否则就不重绘。

HBITMAP SetBitmap( HBITMAP hBitmap );
设置按钮显示的位图。参数hBitmap指定了位图的句柄。该函数还会返回按钮原来的位图。

HBITMAP GetBitmap( ) const;
返回以前用SetBitmap设置的按钮位图。

HICON SetIcon( HICON hIcon );
设置按钮显示的图标。参数hIcon指定了图标的句柄。该函数还会返回按钮原来的图标。

HICON GetIcon( ) const;
返回以前用SetIcon设置的按钮图标。

HCURSOR SetCursor( HCURSOR hCursor );
设置按钮显示的光标图。参数hCursor指定了光标的句柄。该函数还会返回按钮原来的光标。

HCURSOR GetCursor( );
返回以前用GetCursor设置的光标。

 

另外,可以使用下列的一些与按钮控件有关的CWnd成员函数来设置或查询按钮的状态。用这些函数的好处在于不必构建按钮控件对象,只要知道按钮的ID,就可以直接设置或查询按钮。

void CheckDlgButton( int nIDButton, UINT nCheck );
用来设置按钮的选择状态。参数nIDButton指定了按钮的ID。nCheck的值0表示按钮未被选择,1表示按钮被选择,2表示按钮处于不确定状态。

void CheckRadioButton( int nIDFirstButton, int nIDLastButton, int nIDCheckButton );
用来选择组中的一个单选按钮。参数nIDFirstButton指定了组中第一个按钮的ID,nIDLastButton指定了组中最后一个按钮的ID,nIDCheckButton指定了要选择的按钮的ID。

int GetCheckedRadioButton( int nIDFirstButton, int nIDLastButton );
该函数用来获得一组单选按钮中被选中按钮的ID。参数nIDFirstButton说明了组中第一个按钮的ID,nIDLastButton说明了组中最后一个按钮的ID。

UINT IsDlgButtonChecked( int nIDButton ) const;
返回检查框或单选按钮的选择状态。返回值0表示按钮未被选择,1表示按钮被选择,2表示按钮处于不确定状态(仅用于检查框)。

 

可以调用CWnd成员函数GetWindowText,GetWindowTextLength和SetWindowText来查询或设置按钮中显示的正文.

MFC还提供了CButton的派生类CBitmapButton。利用该类可以创建一个拥有四幅位图的命令按钮,按钮在不同状态时会显示不同的位图,这样可以使界面显得生动活泼。如果读者对CBitmapButton感兴趣,可以参看VC5.0随盘提供的MFC例子CTRLTEST。

在上一章的Register例子中已演示了各种按钮控件的使用,故这里就不再举例了。

 

 

6.1.4 编辑框控件

编辑框(Edit Box)控件实际上是一个简易的正文编辑器,用户可以在编辑框中输入并编辑正文。编辑框既可以是单行的,也可以是多行的,多行编辑框是从零开始编行号的.在一个多行编辑框中,除了最后一行外,每一行的结尾处都有一对回车换行符(用"/r/n"表示).这对回车换行符是正文换行的标志,在屏幕上是不可见的.

编辑框控件会向父窗口发出如表6.5所示的控件通知消息。

 

表6.5

消息

含义

EN_CHANGE

编辑框的内容被用户改变了。与EN_UPDATE不同,该消息是在编辑框显示的正文被刷新后才发出的。

EN_ERRSPACE

编辑框控件无法申请足够的动态内存来满足需要。

EN_HSCROLL

用户在水平滚动条上单击鼠标。

EN_KILLFOCUS

编辑框失去输入焦点。

EN_MAXTEXT

输入的字符超过了规定的最大字符数。在没有ES_AUTOHSCROLL或ES_AUTOVSCROLL的编辑框中,当正文超出了编辑框的边框时也会发出该消息。

EN_SETFOCUS

编辑框获得输入焦点。

EN_UPDATE

在编辑框准备显示改变了的正文时发送该消息。

EN_VSCROLL

用户在垂直滚动条上单击鼠标。

 

 

MFC的CEdit类封装了编辑框控件。CEdit类的成员函数Create负责创建按钮控件,该函数的声明为

BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );

 

参数dwStyle指定了编辑框控件风格,如表6.6所示,dwStyle可以是这些风格的组合。rect指定了编辑框的位置和尺寸。pParentWnd指定了父窗口,不能为NULL。编辑框的ID由nID指定。如果创建成功,该函数返回TRUE,否则返回FALSE.

 

表6.6 编辑框控件的风格

控件风格

含义

ES_AUTOHSCROLL

当用户在行尾键入一个字符时,正文将自动向右滚动10个字符,当用户按回车键时,正文总是滚向左边。

ES_AUTOVSCROLL

当用户在最后一个可见行按回车键时,正文向上滚动一页。

ES_CENTER

在多行编辑框中使正文居中。

ES_LEFT

左对齐正文。

ES_LOWERCASE

把用户输入的字母统统转换成小写字母。

ES_MULTILINE

指定一个多行编辑器。若多行编辑器不指定ES_AUTOHSCROLL风格,则会自动换行,若不指定ES_AUTOVSCROLL,则多行编辑器会在窗口中正文装满时发出警告声响。

ES_NOHIDESEL

缺省时,当编辑框失去输入焦点后会隐藏所选的正文,当获得输入焦点时又显示出来。设置该风格可禁止这种缺省行为。

ES_OEMCONVERT

使编辑框中的正文可以在ANSI字符集和OEM字符集之间相互转换。这在编辑框中包含文件名时是很有用的。

ES_PASSWORD

使所有键入的字符都用“*”来显示。

ES_RIGHT

右对齐正文。

ES_UPPERCASE

把用户输入的字母统统转换成大写字母。

ES_READONLY

将编辑框设置成只读的。

ES_WANTRETURN

使多行编辑器接收回车键输入并换行。如果不指定该风格,按回车键会选择缺省的命令按钮,这往往会导致对话框的关闭。

 

 

除了上表中的风格外,一般还要为控件指定WS_CHILD、WS_VISIBLE、WS_TABSTOP和WS_BORDER窗口风格,WS_BORDER使控件带边框。创建一个普通的单行编辑框应指定风格为WS_CHILD|WS_VISIBLE|WS_TABSTOP |WS_BORDER|ES_LEFT|ES_AUTOHSCROLL,这将创建一个带边框、左对齐正文、可水平滚动的单行编辑器。要创建一个普通多行编辑框,还要附加ES_MULTILINE|ES_WANTRETURN|ES_AUTOVSCROLL |WS_HSCROLL| WS_VSCROLL风格,这将创建一个可水平和垂直滚动的,带有水平和垂直滚动条的多行编辑器。

对于用对话框模板编辑器创建的编辑框控件,可以在控件的属性对话框中指定表6.6中列出的控件风格。例如,在属性对话框中选择Multi-line项,相当与指定了ES_MULTILINE风格。

编辑框支持剪贴板操作。CEdit类提供了一些与剪贴板有关的成员函数,如表6.7所示。

 

表6.7 与剪切板有关的CEdit成员函数

函数声明

用途

void Clear( )

清除编辑框中被选择的正文。

void Copy( )

把在编辑框中选择的正文拷贝到剪贴板中。

void Cut( )

清除编辑框中被选择的正文并把这些正文拷贝到剪贴板中。

void Paste( )

将剪贴板中的正文插入到编辑框的当前插入符处。

BOOL Undo( )

撤消上一次键入。对于单行编辑框,该函数总返回TRUE,对于多行编辑框,返回TRUE表明操作成功,否则返回FALSE。

 

 

可以用下列CEdit或CWnd类的成员函数来查询编辑框。在学习下面的函数时,读者会经常遇到术语字符索引.字符的字符索引是指从编辑框的开头字符开始的字符编号,它是从零开始编号的.也就是说,字符索引实际上是指当把整个编辑正文看作一个字符串数组时,该字符所在的数组元素的下标.

 

int GetWindowText( LPTSTR lpszStringBuf, int nMaxCount ) const;
void GetWindowText( CString& rString ) const;
这两个函数均是CWnd类的成员函数,可用来获得窗口的标题或控件中的正文。第一个版本的函数用lpszStringBuf参数指向的字符串数组作为拷贝正文的缓冲区,参数nMaxCount可以拷贝到缓冲区中的最大字符数,该函数返回以字节为单位的实际拷贝字符数(不包括结尾的空字节)。第二个版本的函数用一个CString对象作为缓冲区。

int GetWindowTextLength( ) const;
CWnd的成员函数,可用来获得窗口的标题或控件中的正文的长度。

DWORD GetSel( ) const;
void GetSel( int& nStartChar, int& nEndChar ) const;
两个函数都是CEdit的成员函数,用来获得所选正文的位置。GetSel的第一个版本返回一个DWORD值,其中低位字说明了被选择的正文开始处的字符索引,高位字说明了选择的正文结束处的后面一个字符的字符索引,如果没有正文被选择,那么返回的低位和高位字节都是当前插入符所在字符的字符索引。GetSel的第二个版本的两个参数是两个引用,其含义与第一个版本函数返回值的低位和高位字相同。

int LineFromChar( int nIndex = –1 ) const;
CEdit的成员函数,仅用于多行编辑框,用来返回指定字符索引所在行的行索引(从零开始编号)。参数nIndex指定了一个字符索引,如果nIndex是-1,那么函数将返回选择正文的第一个字符所在行的行号,若没有正文被选择,则该函数会返回当前的插入符所在行的行号。

int LineIndex( int nLine = –1 ) const;
CEdit的成员函数,仅用于多行编辑框,用来获得指定行的开头字符的字符索引,如果指定行超过了编辑框中的最大行数,该函数将返回-1。参数nLine是指定了从零开始的行索引,如果它的值为-1,则函数返回当前的插入符所在行的字符索引。

int GetLineCount( ) const;
CEdit的成员函数,仅用于多行编辑框,用来获得正文的行数。如果编辑框是空的,那么该函数的返回值是1。

int LineLength( int nLine = –1 ) const;
CEdit的成员函数,用于获取指定字符索引所在行的字节长度(行尾的回车和换行符不计算在内)。参数nLine说明了字符索引.如果nLine的值为-1,则函数返回当前行的长度(假如没有正文被选择),或选择正文占据的行的字符总数减去选择正文的字符数(假如有正文被选择)。若用于单行编辑框,则函数返回整个正文的长度。

int GetLine( int nIndex, LPTSTR lpszBuffer ) const;
int GetLine( int nIndex, LPTSTR lpszBuffer, int nMaxLength ) const;
CEdit的成员函数,仅用于多行编辑框,用来获得指定行的正文(不包括行尾的回车和换行符)。参数nIndex是行号,lpszBuffer指向存放正文的缓冲区,nMaxLength规定了拷贝的最大字节数,若。函数返回实际拷贝的字节数,若指定的行号大于编辑框的实际行数,则函数返回0。需要注意的是,GetLine函数不会在缓冲区中字符串的末尾加字符串结束符(NULL).

 

下列CWnd或CEdit类的成员函数可用来修改编辑框控件。

void SetWindowText( LPCTSTR lpszString );
CWnd的成员函数,可用来设置窗口的标题或控件中的正文。参数lpszString可以是一个CString对象,或是一个指向字符串的指针。

void SetSel( DWORD dwSelection, BOOL bNoScroll = FALSE );
void SetSel( int nStartChar, int nEndChar, BOOL bNoScroll = FALSE );
CEdit的成员函数,用来选择编辑框中的正文。参数dwSelection的低位字说明了选择开始处的字符索引,高位字说明了选择结束处的字符索引。如果低位字为0且高位字节为-1,那么就选择所有的正文,如果低位字节为-1,则取消所有的选择.参数bNoScroll的值如果是FALSE,则滚动插入符并使之可见,否则就不滚动.参数nStartChar和nEndChar的含义与参数dwSelection的低位字和高位字相同.

void ReplaceSel( LPCTSTR lpszNewText, BOOL bCanUndo = FALSE );
CEdit的成员函数,用来将所选正文替换成指定的正文.参数lpszNewText指向用来替换的字符串.参数bCanUndo的值为TRUE说明替换是否可以被撤消的.

 

在调用上述函数时,如果涉及的是一个多行编辑框,那么除了LineLength和GetLine函数外,都要把回车和换行符考虑在内.例如,假设在编辑框中有如下几行正文:

abcd

efg

ij

那么字母"e"的字符索引是6而不是4,因为"abcd"后面还有一对回车换行符.调用LineLength(7)会返回第二行的长度3.调用LineIndex(2)会得到11.调用LineFromChar(8)会返回1.如果没有选择任何正文,并且插入符在字母"e"上,那么调用GetSel返回值的低位和高位字都是6.

通过分析上述函数,我们可以总结出一些查询和设置编辑框的方法.

调用CWnd的成员函数GetWindowText和SetWindowText可以查询和设置编辑框的整个正文,在上一章的Register程序中,我们就使用过这两个函数.

如果想对多行编辑框逐行查询,那么应该先调用GetLineCount获得总行数,然后再调用GetLine来获取每一行的正文.下面一段代码演示了如何对多行编辑框进行逐行查询.

char buf[40];

int total=MyEdit.GetLineCount();

int i,length;

for(i=0;i

{

length=MyEdit.GetLine(i,buf,39);

buf[length]=0; //加字符串结束符

. . . . . .

}

可以利用LineIndex和LineFromChar来在字符索引和字符的行列坐标之间相互转换.下列代码演示了在已知字符索引的情况下,如何获得对应的行列坐标:
int row,column;
row=MyEdit.LineFromChar(charIndex);
column=charIndex-MyEdit.LineIndex(row);
下列代码演示了在已知字符的行列坐标的情况下,如何获得对应的字符索引:
int charIndex;
charIndex=MyEdit.LineIndex(row)+column;
不难看出字符索引与对应的行列坐标的关系是:字符索引=LineIndex(行坐标)+列坐标.

对于选择正文的查询和设置,应该利用函数GetSel、SetSel和ReplaceSel.

可以利用GetSel和SetSel来查询和设置插入符的位置.SetSel可以使编辑框滚动到插入符的新位置.
要获取插入符的行列坐标,可用下面的代码实现:
MyEdit.SetSel(-1,0); //取消正文的选择
int start,end,row,column;
MyEdit.GetSel(start,end); //start或end的值就是插入符的字符索引
row=MyEdit.LineFromChar(start); //获取插入符的行坐标
column=start-MyEdit.LineIndex(row); //获取插入符的列坐标
下面的代码演示了如何把插入符移到指定的行和列:
MyEdit.SetSel(-1,0); //取消正文的选择
int charIndex=MyEdit.LineIndex(row)+column;
MyEdit.SetSel(charIndex,charIndex);

可以利用ReplaceSel函数在 插入符处插入正文,典型的代码如下所示:
MyEdit.SetSel(-1,0); //取消正文的选择
MyEdit.ReplaceSel(“......”);

可以利用ReplaceSel清除编辑框中的正文,典型的代码如下所示:
MyEdit.SetSel(0,-1); //选择全部正文
MyEdit.ReplaceSel(“”);

在后面的小节中,读者将会看到使用编辑框的例子.

.1.5 滚动条控件

滚动条(Scroll Bar)主要用来从某一预定义值范围内快速有效地进行选择.滚动条分垂直滚动条和水平滚动条两种.在滚动条内有一个滚动框,用来表示当前的值.用鼠标单击滚动条,可以使滚动框移动一页或一行,也可以直接拖动滚动框.滚动条既可以作为一个独立控件存在,也可以作为窗口、列表框和组合框的一部分.Windows 95的滚动条支持比例滚动框,即用滚动框的大小来反映页相对于整个范围的大小.Windows 3.x使用单独的滚动条控件来调整调色板、键盘速度以及鼠标灵敏度,在Windows 95中,滚动条控件被轨道条取代(参见6.2.3)不提倡使用单独的滚动条控件.

需要指出的是,从性质上划分,滚动条可分为标准滚动条和滚动条控件两种.标准滚动条是由WS_HSCROLL或WS_VSCROLL风格指定的,它不是一个实际的窗口,而是窗口的一个组成部分(例如列表框中的滚动条),只能位于窗口的右侧(垂直滚动条)或底端(水平滚动条).标准滚动条是在窗口的非客户区中创建的.与之相反,滚动条控件并不是窗口的一个零件,而是一个实际的窗口,可以放置在窗口客户区的任意地方,它既可以独立存在,也可以与某一个窗口组合,行使滚动窗口的职能.由于滚动条控件是一个独立窗口,因此可以拥有输入焦点,可以响应光标控制键,如PgUp、PgDown、Home和End.

MFC的CScrollBar类封装了滚动条控件.CScrollBar类的Create成员函数负责创建控件,该函数的声明为

BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );

参数dwStyle指定了控件的风格.rect说明了控件的位置和尺寸.pParentWnd指向父窗口,该参数不能为NULL。nID则说明了控件的ID。如果创建成功,该函数返回TRUE,否则返回FALSE.

要创建一个普通的水平滚动条控件,应指定风格WS_CHILD|WS_VISIBLE|BS_HORZ.要创建一个普通的垂直滚动条控件,应指定风格WS_CHILD|WS_VISIBLE|BS_VERT.

主要的CScrollBar类成员函数如下所示:

int GetScrollPos( ) const;
该函数返回滚动框的当前位置.若操作失败则返回0.

int SetScrollPos( int nPos, BOOL bRedraw = TRUE );
该函数将滚动框移动到指定位置.参数nPos指定了新的位置.参数bRedraw表示是否需要重绘滚动条,如果为TRUE,则重绘之.函数返回滚动框原来的位置.若操作失败则返回0.

void GetScrollRange( LPINT lpMinPos, LPINT lpMaxPos ) const;
该函数对滚动条的滚动范围进行查询.参数lpMinPos和lpMaxPos分别指向滚动范围的最小最大值.

void SetScrollRange( int nMinPos, int nMaxPos, BOOL bRedraw = TRUE );
该函数用于指定滚动条的滚动范围.参数nMinPos和nMaxPos分别指定了滚动范围的最小最大值.由这两者指定的滚动范围不得超过32767.当两者都为0时,滚动条将被隐藏.参数bRedraw表示是否需要重绘滚动条,如果为TRUE,则重绘之.

BOOL GetScrollInfo( LPSCROLLINFO lpScrollInfo, UINT nMask );
该函数用来获取滚动条的各种状态,包括滚动范围、滚动框的位置和页尺寸.参数lpScrollInfo指向一个SCROLLINFO结构,该结构如下所示:
typedef struct tagSCROLLINFO {
UINT cbSize; //结构的尺寸(字节为单位)
UINT fMask; /*说明结构中的哪些参数是有效的,可以是屏蔽值的组合, 如SIF_POS|SIF_PAGE,若为SIF_ALL则整个结构都有效*/
int nMin; //滚动范围最大值,当fMask中包含SIF_RANGE时有效
int nMax; //滚动范围最小值,当fMask中包含SIF_RANGE时有效
UINT nPage; /*页尺寸,用来确定比例滚动框的大小,当fMask中包含 SIF_PAGE时有效*/
int nPos; //滚动框的位置,当fMask中包含SIF_POS有效
int nTrackPos; /*拖动时滚动框的位置,当fMask中包含 SIF_TRACKPOS时有效,该参数只能查询,不能设 置,最好不要用该参数来查询拖动时滚动框的位置*/
} SCROLLINFO;
typedef SCROLLINFO FAR *LPSCROLLINFO;
参数nMask的意义与SCROLLINFO结构中的fMask相同.函数在获得有效值后返回TRUE,否则返回FALSE.

BOOL SetScrollInfo( LPSCROLLINFO lpScrollInfo, BOOL bRedraw = TRUE );
该函数用于设置滚动条的各种状态,一个重要用途是设定页尺寸从而实现比例滚动框.参数lpScrollInfo指向一个SCROLLINFO结构,参数bRedraw表示是否需要重绘滚动条,如果为TRUE,则重绘之.若操作成功,该函数返回TRUE,否则返回FALSE.

 

CWnd类也提供了一些函数来查询和设置所属的标准滚动条.这些函数与CScrollBar类的函数同名,且功能相同,但每个函数都多了一个参数,用来选择滚动条.例如,CWnd:: GetScrollPos 的声明为

int GetScrollPos( int nBar ) const;
参数nBar用来选择滚动条,可以为下列值:
SB_HORZ //指定水平滚动条
SB_VERT //指定垂直滚动条

 

无论是标准滚动条,还是滚动条控件,滚动条的通知消息都是用WM_HSCROLL和WM_VSCROLL消息发送出去的.对这两个消息的确省处理函数是CWnd::OnHScroll和CWnd::OnVScroll,它们几乎什么也不做.一般需要在派生类中对这两个函数从新设计,以实现滚动功能.这两个函数的声明为

afx_msg void OnHScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );

afx_msg void OnVScroll( UINT nSBCode, UINT nPos, CScrollBar* pScrollBar );
参数nSBCode是通知消息码,如表6.8所示.nPos是滚动框的位置,只有在nSBCode为SB_THUMBPOSITION或SB_THUMBTRACK时,该参数才有意义.如果通知消息是滚动条控件发来的,那么pScrollBar是指向该控件的指针,如果是标准滚动条发来的,则pScrollBar为NULL.

 

表6.8 滚动条的通知消息码

消息

含义

SB_BOTTOM / SB_RIGHT(二者的消息码是一样的,因此可以混用,下同)

滚动到底端(右端).

SB_TOP / SB_LEFT

滚动到顶端(左端).

SB_LINEDOWN / SB_LINERIGHT

向下(向右)滚动一行(列).

SB_LINEUP / SB_LINELEFT

向上(向左)滚动一行(列).

SB_PAGEDOWN / SB_PAGERIGHT

向下(向右)滚动一页.

SB_PAGEUP / SB_PAGELEFT

向上(向左)滚动一页.

SB_THUMBPOSITION

滚动到指定位置.

SB_THUMBTRACK

滚动框被拖动.可利用该消息来跟踪对滚动框的拖动.

SB_ENDSCROLL

滚动结束.

6.1.8小节的例子中,读者将学会如何使用滚动条以及如何编写自己的OnHScroll函数.

6.1.6 列表框控件

列表框主要用于输入,它允许用户从所列出的表项中进行单项或多项选择,被选择的项呈高亮度显示.列表框具有边框,并且一般带有一个垂直滚动条.列表框分单选列表框和多重选择列表框两种.单选列表框一次只能选择一个列表项,而多重选择列表框可以进行多重选择.对于列表项的选择,微软公司有如下建议:

单击鼠标选择一个列表项,单击一个按钮来处理选择的项.

双击鼠标选择一个列表项是处理选择项的快捷方法.

 

列表框会向父窗口发送如表6.9所示的通知消息.

 

表6.9 列表框控件的通知消息

消息

含义

LBN_DBLCLK

用户用鼠标双击了一列表项.只有具有LBS_NOTIFY的列表框才能发送该消息.

LBN_ERRSPACE

列表框不能申请足够的动态内存来满足需要.

LBN_KILLFOCUS

列表框失去输入焦点.

LBN_SELCANCEL

当前的选择被取消.只有具有LBS_NOTIFY的列表框才能发送该消息.

LBN_SELCHANGE

单击鼠标选择了一列表项.只有具有LBS_NOTIFY的列表框才能发送该消息.

LBN_SETFOCUS

列表框获得输入焦点.

WM_CHARTOITEM

当列表框收到WM_CHAR消息后,向父窗口发送该消息.只有具有LBS_WANTKEYBOARDINPUT风格的列表框才会发送该消息.

WM_VKEYTOITEM

当列表框收到WM_KEYDOWN消息后,向父窗口发送该消息.只有具有LBS_WANTKEYBOARDINPUT风格的列表框才会发送该消息.

 

 

MFC的CListBox类封装了列表框.CListBox类的Create成员函数负责列表框的创建,该函数的声明是

BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );

 

参数dwStyle指定了列表框控件的风格,如表6.10所示,dwStyle可以是这些风格的组合.rect说明了控件的位置和尺寸.pParentWnd指向父窗口,该参数不能为NULL。nID则说明了控件的ID。如果创建成功,该函数返回TRUE,否则返回FALSE.

 

表6.10 列表框控件的风格

控件风格

含义

LBS_EXTENDEDSEL

支持多重选择.在点击列表项时按住Shift键或Ctrl键即可选择多个 项.

LBS_HASSTRINGS

指定一个含有字符串的自绘式列表框.

LBS_MULTICOLUMN

指定一个水平滚动的多列列表框,通过调用CListBox::SetColumnWidth来设置每列的宽度.

LBS_MULTIPLESEL

支持多重选择.列表项的选择状态随着用户对该项单击或双击鼠标而翻转.

LBS_NOINTEGRALHEIGHT

列表框的尺寸由应用程序而不是Windows指定.通常,Windows指定尺寸会使列表项的某些部分隐藏起来.

LBS_NOREDRAW

当选择发生变化时防止列表框被更新,可发送WM_SETREDRAW来改变该风格.

LBS_NOTIFY

当用户单击或双击鼠标时通知父窗口.

LBS_OWNERDRAWFIXED

指定自绘式列表框,即由父窗口负责绘制列表框的内容,并且列表项有相同的高度.

LBS_OWNERDRAWVARIABLE

指定自绘式列表框,并且列表项有不同的高度.

LBS_SORT

使插入列表框中的项按升序排列.

LBS_STANDARD

相当于指定了WS_BORDER|WS_VSCROLL|LBS_SORT |LBS_NOTIFY.

LBS_USETABSTOPS

使列表框在显示列表项时识别并扩展制表符(‘/t’),缺省的制表宽度是32个对话框单位.

LBS_WANTKEYBOARDINPUT

允许列表框的父窗口接收WM_VKEYTOITEM和WM_CHARTOITEM消息,以响应键盘输入.

LBS_DISABLENOSCROLL

使列表框在不需要滚动时显示一个禁止的垂直滚动条.

 

 

除了上表中的风格外,一般还要为列表框控件指定WS_CHILD、WS_VISIBLE、WS_TABSTOP、WS_BORDER和WS_VSCROLL风格.要创建一个普通的单选择列表框,应指定的风格为WS_CHILD|WS_VISIBLE|WS_TABSTOP|LBS_STANDARD.要创建一个多重选择列表框,应该在单选择列表框风格的基础上再加上 LBS_MULTIPLESEL或LBS_ EXTENDEDSEL.如果不希望列表框排序,就不能使用LBS_STANDARD风格.

对于用对话框模板编辑器创建的列表框控件,可以在控件的属性对话框中指定表6.10中列出的控件风格。例如,在属性对话框中选择Sort项,相当与指定了LBS_SORT风格。

CListBox类的成员函数有数十个之多.我们可以把一些常用的函数分为三类,在下面列出.需要说明的是,可以用索引来指定列表项,索引是从零开始的.

首先,CListBox成员函数提供了下列函数用于插入和删除列表项.

int AddString( LPCTSTR lpszItem );
该函数用来往列表框中加入字符串,其中参数lpszItem指定了要添加的字符串.函数的返回值是加入的字符串在列表框中的位置,如果发生错误,会返回LB_ERR或LB_ERRSPACE(内存不够).如果列表框未设置LBS_SORT风格,那么字符串将被添加到列表的末尾,如果设置了LBS_SORT风格,字符串会按排序规律插入到列表中.

int InsertString( int nIndex, LPCTSTR lpszItem );
该函数用来在列表框中的指定位置插入字符串.参数nIndex给出了插入位置(索引),如果值为-1,则字符串将被添加到列表的末尾.参数lpszItem指定了要插入的字符串.函数返回实际的插入位置,若发生错误,会返回LB_ERR或LB_ERRSPACE.与AddString函数不同,InsertString函数不会导致LBS_SORT风格的列表框重新排序.不要在具有LBS_SORT风格的列表框中使用InsertString函数,以免破坏列表项的次序.

int DeleteString( UINT nIndex );
该函数用于删除指定的列表项,其中参数nIndex指定了要删除项的索引.函数的返回值为剩下的表项数目,如果nIndex超过了实际的表项总数,则返回LB_ERR.

void ResetContent( );
该函数用于清除所有列表项.

int Dir( UINT attr, LPCTSTR lpszWildCard );
该函数用来向列表项中加入所有与指定通配符相匹配的文件名或驱动器名.参数attr为文件类型的组合,如表6.11所示.参数lpszWildCard指定了通配符(如*.cpp,*.*等).

 

表6.11 Dir函数attr参数的含义

含义

0x0000

普通文件(可读写的文件).

0x0001

只读文件.

0x0002

隐藏文件.

0x0004

系统文件.

0x0010

目录.

0x0020

文件的归档位已被设置.

0x4000

包括了所有与通配符相匹配的驱动器.

0x8000

排除标志.若指定该标志,则只列出指定类型的文件名,否则,先要列出普通文件,然后再列出指定的文件.

 

 

下列的CListBox成员函数用于搜索、查询和设置列表框.

int GetCount( ) const;
该函数返回列表项的总数,若出错则返回LB_ERR.

int FindString( int nStartAfter, LPCTSTR lpszItem ) const;
该函数用于对列表项进行与大小写无关的搜索.参数nStartAfter指定了开始搜索的位置, 合理指定nStartAfter可以加快搜索速度,若nStartAfter为-1,则从头开始搜索整个列表.参数lpszItem指定了要搜索的字符串.函数返回与lpszItem指定的字符串相匹配的列表项的索引,若没有找到匹配项或发生了错误,函数会返回LB_ERR.FindString函数先从nStartAfter指定的位置开始搜索,若没有找到匹配项,则会从头开始搜索列表.只有找到匹配项,或对整个列表搜索完一遍后,搜索过程才会停止,所以不必担心会漏掉要搜索的列表项.

int GetText( int nIndex, LPTSTR lpszBuffer ) const;
void GetText( int nIndex, CString& rString ) const;
用于获取指定列表项的字符串.参数nIndex指定了列表项的索引.参数lpszBuffer指向一个接收字符串的缓冲区.引用参数rString则指定了接收字符串的CString对象.第一个版本的函数会返回获得的字符串的长度,若出错,则返回LB_ERR.

int GetTextLen( int nIndex ) const;
该函数返回指定列表项的字符串的字节长度.参数nIndex指定了列表项的索引.若出错则返回LB_ERR.

DWORD GetItemData( int nIndex ) const;
每个列表项都有一个32位的附加数据.该函数返回指定列表项的附加数据,参数nIndex指定了列表项的索引.若出错则函数返回LB_ERR.

int SetItemData( int nIndex, DWORD dwItemData );
该函数用来指定某一列表项的32位附加数据.参数nIndex指定了列表项的索引.dwItemData是要设置的附加数据值.

提示:列表项的32位附加数据可用来存储与列表项相关的数据,也可以放置指向相关数据的指针.这样,当用户选择了一个列表项时,程序可以从附加数据中快速方便地获得与列表项相关的数据.

 

 

int GetTopIndex( ) const;
该函数返回列表框中第一个可见项的索引,若出错则返回LB_ERR.

int SetTopIndex( int nIndex );
用来将指定的列表项设置为列表框的第一个可见项,该函数会将列表框滚动到合适的位置.参数nIndex指定了列表项的索引.若操作成功,函数返回0值,否则返回LB_ERR.

提示:由于列表项的内容一般是不变的,故CListBox未提供更新列表项字符串的函数.如果要改变某列表项的内容,可以先调用DeleteString删除该项,然后再用InsertString或AddString将更新后的内容插入到原来的位置.

下列CListBox的成员函数与列表项的选择有关.

int GetSel( int nIndex ) const;
该函数返回指定列表项的状态.参数nIndex指定了列表项的索引.如果查询的列表项被选择了,函数返回一个正值,否则返回0,若出错则返回LB_ERR.

int GetCurSel( ) const;
该函数仅适用于单选择列表框,用来返回当前被选择项的索引,如果没有列表项被选择或有错误发生,则函数返回LB_ERR.

int SetCurSel( int nSelect );
该函数仅适用于单选择列表框,用来选择指定的列表项.该函数会滚动列表框以使选择项可见.参数nIndex指定了列表项的索引,若为-1,那么将清除列表框中的选择.若出错函数返回LB_ERR.

int SelectString( int nStartAfter, LPCTSTR lpszItem );
该函数仅适用于单选择列表框,用来选择与指定字符串相匹配的列表项.该函数会滚动列表框以使选择项可见.参数的意义及搜索的方法与函数FindString类似.如果找到了匹配的项,函数返回该项的索引,如果没有匹配的项,函数返回LB_ERR并且当前的选择不被改变.

int GetSelCount( ) const;
该函数仅用于多重选择列表框,它返回选择项的数目,若出错函数返回LB_ERR.

int SetSel( int nIndex, BOOL bSelect = TRUE );
该函数仅适用于多重选择列表框,它使指定的列表项选中或落选.参数nIndex指定了列表项的索引,若为-1,则相当于指定了所有的项.参数bSelect为TRUE时选中列表项,否则使之落选.若出错则返回LB_ERR.

int GetSelItems( int nMaxItems, LPINT rgIndex ) const;
该函数仅用于多重选择列表框,用来获得选中的项的数目及位置.参数nMaxItems说明了参数rgIndex指向的数组的大小.参数rgIndex指向一个缓冲区,该数组是一个整型数组,用来存放选中的列表项的索引.函数返回放在缓冲区中的选择项的实际数目,若出错函数返回LB_ERR.

int SelItemRange( BOOL bSelect, int nFirstItem, int nLastItem );
该函数仅用于多重选择列表框,用来使指定范围内的列表项选中或落选.参数nFirstItem和nLastItem指定了列表项索引的范围.如果参数bSelect为TRUE,那么就选择这些列表项,否则就使它们落选.若出错函数返回LB_ERR.

在6.1.8小节的例子中,读者将会看到对列表框的测试.6.1.7 组合框控件

组合框把一个编辑框和一个单选择列表框结合在了一起.用户既可以在编辑框中输入,也可以从列表框中选择一个列表项来完成输入.如上一章所提到的,组合框分为简易式(Simple)、下拉式(Dropdown)和下拉列表式(Drop List)三种.简易式组合框包含一个编辑框和一个总是显示的列表框。下拉式组合框同简易式组合框类似,二者的区别在于仅当单击下滚箭头后列表框才会弹出。下拉列表式组合框也有一个下拉的列表框,但它的编辑框是只读的,不能输入字符。

Windows中比较常用的是下拉式和下拉列表式组合框,在Developer Studio中就大量使用了这两种组合框.二者都具有占地小的特点,这在界面日益复杂的今天是十分重要的.下拉列表式组合框的功能与列表框类似.下拉式组合框的典型应用是作为记事列表框使用,既把用户在编辑框中敲入的东西存储到列表框组件中,这样当用户要重复同样的输入时,可以从列表框组件中选取而不必在编辑框组件中从新输入.在Developer Studio中的Find对话框中就可以找到一个典型的下拉式组合框.

要设计一个记事列表框,应采取下列原则:

在创建组合框时指定CBS_DROPDOWNLIST风格.

要限制列表项的数目,以防止内存不够.

如果在编辑框中输入的字符串不能与列表框组件中的列表项匹配,那么应该把该字符串插入到列表框中的0位置处.最老的项处于列表的末尾.如果列表项的数目超出了限制,则应把最老的项删除.

如果在编辑框中输入的字符串可以与列表框组件中的某一项完全匹配,则应该先把该项从列表的当前位置删除,然后在将其插入道列表的0位置处.

 

组合框控件会向父窗口发送表6.12所示的通知消息.

 

表6.12 组合框控件的通知消息

消息

含义

CBN_CLOSEUP

组合框的列表框组件被关闭.简易式组合框不会发出该消息.

CBN_DBLCLK

用户在某列表项上双击鼠标.只有简易式组合框才会发出该消息.

CBN_DROPDOWN

组合框的列表框组件下拉.简易式组合框不会发出该消息.

CBN_EDITCHANGE

编辑框的内容被用户改变了。与CBN_EDITUPDATE不同,该消息是在编辑框显示的正文被刷新后才发出的。下拉列表式组合框不会发出该消息.

CBN_EDITUPDATE

在编辑框准备显示改变了的正文时发送该消息。下拉列表式组合框不会发出该消息.

CBN_ERRSPACE

组合框无法申请足够的内存来容纳列表项.

CBN_SELENDCANCEL

表明用户的选择应该取消.当用户在列表框中选择了一项,然后又在组合框控件外单击鼠标时就会导致该消息的发送.

CBN_SELENDOK

用户选择了一项,然后按了回车键或单击了下滚箭头.该消息表明用户确认了自己所作的选择.

CBN_KILLFOCUS

组合框失去了输入焦点.

CBN_SELCHANGE

用户通过点击或移动箭头键改变了列表的选择.

CBN_SETFOCUS

组合框获得了输入焦点.

 

 

MFC的CComboBox类封装了组合框.需要指出的是,虽然组合框是编辑框和列表框的选择,但是CComboBox类并不是CEdit类和CListBox类的派生类,而是CWnd类的派生类.

CComboBox的成员函数Create负责创建组合框,该函数的说明如下:

BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );

 

参数dwStyle指定了组合框控件的风格,如表6.10所示,dwStyle可以是这些风格的组合.rect说明的是列表框组件下拉后组合框的位置和尺寸.pParentWnd指向父窗口,该参数不能为NULL。nID则说明了控件的ID。如果创建成功,该函数返回TRUE,否则返回FALSE.

提示:在用Create函数创建组合框时,参数rect说明的是包括列表框组件在内的组合框的位置和尺寸,而不是列表框组件隐藏时的编辑框组件尺寸.要设置编辑框组件的高度,可以调用成员函数SetItemHeight(-1,cyItemHeight),其中参数cyItemHeight指定了编辑框的高度(以像素为单位).

表6.13 组合框的风格

控件风格

含义

CBS_AUTOHSCROLL

使编辑框组件具有水平滚动的风格.

CBS_DROPDOWN

指定一个下拉式组合框.

CBS_DROPDOWNLIST

指定一个下拉列表式组合框.

CBS_HASSTRINGS

指定一个含有字符串的自绘式组合框.

CBS_OEMCONVERT

使编辑框组件中的正文可以在ANSI字符集和OEM字符集之间相互转换。这在编辑框中包含文件名时是很有用的。

CBS_OWNERDRAWFIXED

指定自绘式组合框,即由父窗口负责绘制列表框的内容,并且列表项有相同的高度.

CBS_OWNERDRAWVARIABLE

指定自绘式组合框,并且列表项有不同的高度.

CBS_SIIMPLE

指定一个简易式组合框.

CBS_SORT

自动对列表框组件中的项进行排序.

CBS_DISABLENOSCROLL

使列表框在不需要滚动时显示一个禁止的垂直滚动条.

CBS_NOINTEGRALHEIGHT

组合框的尺寸由应用程序而不是Windows指定.通常,由Windows指定尺寸会使列表项的某些部分隐藏起来.

 

 

CBS_SIMPLE、CBS_DROPDOWN和CBS_DROPDOWNLIST分别用来将组合框指定为简易式、下拉式和下拉列表式.一般还要为组合框指定WS_CHILD、WS_VISIBLE、WS_TABSTOP、WS_VSCROLL和CBS_AUTOHSCROLL风格.如果要求自动排序,还应指定CBS_SORT风格.

对于用对话框模板编辑器创建的组合框控件,可以在控件的属性对话框中指定上表中列出的控件风格。例如,在属性对话框中选择Dropdown,相当于指定了CBS_DROPDOWN.

CComboBox类的成员函数较多.其中常用的函数可粗分为两类,分别针对编辑框组件和列表框组件.可以想象,这些函数与CEdit类和CListBox类的成员函数肯定有很多类似之处,但它们也会有一些不同的特点.如果读者能从"组合框是由编辑框和列表框组成"这一概念出发,就能够很快的掌握CComboBox的主要成员函数.

事实上,绝大部分CComboBox的成员函数都可以看成是CEdit或CListBox成员函数的翻版.函数的功能,函数名,甚至函数的参数都是类似的.为了方便学习,在下面列出CComboBox类的成员函数时,采用了与对应的CEdit或CListBox成员函数相比较的做法.在成员函数的列表中,分别列出了成员函数名,对应的CEdit或CListBox成员函数,以及二者之间的不同之处.不同之处是指函数的功能、参数以及返回值有什么差别.

针对编辑框组件的主要成员函数如表6.14所示.该表的前三个函数实际上是CWnd类的成员函数,可用来查询和设置编辑框组件.

 

表6.14 针对编辑框组件的CComboBox成员函数

成员函数名

对应的CEdit成员函数

与CEdit成员函数的不同之处

CWnd::GetWindowText

CWnd::GetWindowText

无.

CWnd::SetWindowText

CWnd::SetWindowText

无.

CWnd::GetWindowTextLength

CWnd::GetWindowTextLength

 

GetEditSel

GetSel的第一个版本

仅函数名不同.

SetEditSel

SetSel的第二个版本

函数名不同,且无bNoScroll参数.

Clear

Clear

无.

Copy

Copy

无.

Cut

Cut

无.

Paste

Paste

无.

 

 

与CListBox的成员函数类似,针对列表框组件的CComboBox成员函数也可以分为三类.表6.15列出了用于插入和删除列表项的成员函数,表6.16列出了用于搜索、查询和设置列表框的成员函数,与列表项的选择有关的成员函数在表6.17中列出.需要指出的是,如果这些函数出错,则反回CB_ERR,而不是LB_ERR.另外,排序的组合框具有的是CBS_SORT风格,而不是LBS_SORT.

 

 

6.15 用于插入和删除列表项的CComboBox成员函数

成员函数名

对应的CListBox成员函数

与CListBox成员函数的不同之处

AddString

AddString

无.

InsertString

InsertString

无.

DeleteString

DeleteString

无.

ResetContent

ResetContent

无.

Dir

Dir

无.

 

 

6.16 用于搜索、查询和设置列表框的CComboBox成员函数

成员函数名

对应的CListBox成员函数

与CListBox成员函数的不同之处

GetCount

GetCount

无.

FindString

FindString

无.

GetLBText

GetText

仅函数名不同.

GetLBTextLen

GetTextLen

仅函数名不同.

GetItemData

GetItemData

无.

SetItemData

SetItemData

无.

GetTopIndex

GetTopIndex

无.

SetTopIndex

SetTopIndex

无.

 

 

表6.17 与列表项的选择有关的CComboBox成员函数

成员函数名

对应的CListBox成员函数

与CListBox成员函数的不同之处

GetCurSel

GetCurSel

无.

SetCurSel

SetCurSel

新选的列表项的内容会被拷贝到编辑框组件中.

SelectString

SelectString

新选的列表项的内容会被拷贝到编辑框组件中.

 

 

另外,CComboBox的ShowDropDown成员函数专门负责显示或隐藏列表框组件,该函数的声明为

void ShowDropDown( BOOL bShowIt = TRUE );

 

如果参数bShowIt的值为TRUE,那么将显示列表框组件,否则,就隐藏之.该函数对简易式组合框没有作用.

 6.1.8 测试传统控件的一个例子

现在让我们编写一个程序来测试一下上面介绍的一些传统控件.该程序名为CtrlTest,其界面如图6.1所示.前面介绍的程序都是基于框架窗口的,而CtrlTest程序是一个基于对话框的应用程序,即以对话框作为程序的主窗口.该程序主要对组合框、列表框、多行编辑框和滚动条控件进行了测试,其中:

Input组合框是一个记事列表框.在编辑框组件中输入字符串,或从列表框组件中选择以前输入过的字符串,然后按Add按钮,该字符串就会被加入到List列表框中.

List列表框是一个多重选择列表框.该列表框具有LBS_EXTENDEDSEL风格,用户可以单击鼠标进行单项选择,也可以按住Shift或Ctrl键后单击鼠标来进行多重选择.用户可以按Delete键删除列表框中选择的项.

History of SELCHANGE多行编辑框.该编辑框用于跟踪Input组合框的列表框组件发出的CBN_SELCHANGE通知消息,编辑框对该消息的响应是显示XXXX selected,以表明用户新选择了一个列表项.读者通过该编辑框可以了解组合框是在什么情况下发送CBN_SELCHANGE通知消息的.按Clear按钮将清除编辑框.

水平滚动条控件的滚动范围是0 — 50.在滚动条的左边有一个静态正文控件用来动态反映当前滚动框的位置.

图6.1 CtrlTest程序

 

首先,让我们用AppWizard建立一个基于对话框的MFC应用程序.这一过程很简单,先将新建的工程命名为CtrlTest,然后在MFC AppWizard对话框的第一步中选择Dialog based就行了.

AppWizard会自动建立一个用于应用程序主窗口的对话框模板IDD_CTRLTEST_DIALOG及其对应的对话框类CCtrlTestDlg.对该对话框的使用与普通对话框并没有什么不同,只不过在程序启动后对话框会自动显示出来,而当用户关闭对话框后,应用程序也就终止了.如果读者观察CCtrlTestApp:: InitInstance函数就会发现,该函数调用DoModal来显示一个CCtrlTestDlg对话框,并使m_pMainWnd指针指向CCtrlTestDlg对象,从而使该对话框成为程序的主窗口.

接下来,需要设计IDD_CTRLTEST_DIALOG对话框模板.请读者将该模板上除OK按钮以外的控件都删除掉,将OK按钮的标题改为Cl&ose,并去掉该按钮的Default button(缺省按钮)属性.当用户在对话框内按回车键时,会激活缺省按钮,一般应该把用来确认用户输入操作的按钮设计成缺省按钮.在本例中,显然应该把Add按钮设计成缺省按钮,而不是Close按钮.这样,用户在Input组合框中输入字符串后,按回车键就可以将该串加入到List列表框中.

请读者根据图6.1和表6.18,向IDD_CTRLTEST_DIALOG对话框模板中加入测试用的控件.

 

表6.18

控件类型 ID 标题(Caption) 其它属性

静态正文

缺省

Input:

缺省.

组合框

ID_COMBOBOX

 

去掉Sort属性.

命令按钮

IDC_ADD

&Add

选择Default button属性.

静态正文

缺省

List:

缺省.

列表框

IDC_LISTBOX

 

在Selection栏中选择Extended,并去掉Sort属性.

命令按钮

IDC_DELETE

&Delete

缺省.

静态正文

缺省

History of SELCHANGE

缺省.

编辑框

IDC_MULTIEDIT

 

选择Multi-line,Vertical scroll,AutoVScroll和Want return属性.

命令按钮

IDC_CLEAR

&Clear

缺省.

静态正文

IDC_INDICATOR

缺省

缺省.

滚动条

IDC_SCROLLBAR

 

缺省.

 

 

接着,利用ClassWizard为CCtrlTestDlg类加入成员变量,如表6.19所示,这些成员变量都是控件对象.

 

表6.19 CCtrlTestDlg类的成员变量

控件ID

变量类型

变量名

IDC_COMBOBOX

CComboBox

m_ComboBox

IDC_LISTBOX

CListBox

m_ListBox

IDC_MULTIEDIT

CEdit

m_MultiEdit

IDC_INDICATOR

CStatic

m_Indicator

IDC_SCROLLBAR

CScrollBar

m_ScrollBar

 

 

接下来,用ClassWizard为CCtrlTestDlg类加入控件通知消息处理函数,如表6.20所示.

 

表6.20 CCtrlTestDlg的控件通知消息处理函数

Object IDS

Messages

Member functions

IDC_ADD

BN_CLICKED

OnAdd(缺省名)

IDC_DELETE

BN_CLICKED

OnDelete(缺省名)

IDC_CLEAR

BN_CLICKED

OnClear(缺省名)

IDC_COMBOBOX

CBN_SELCHANGE

OnSelchangeCombobox(缺省名)

CCtrlTestDlg

WM_HSCROLL

OnHScroll(缺省名)

 

最后,请读者按清单6.1修改源代码,限于篇幅,这里仅列出需要手工修改的那一部分.

 

清单6.1 CCtrlTestDlg类的部分源代码

// CtrlTestDlg.cpp : implementation file

//

 

#define MAX_HISTORY 5

. . . . . .

BOOL CCtrlTestDlg::OnInitDialog()

{

 

. . . . . .

// TODO: Add extra initialization here

 

m_ScrollBar.SetScrollRange(0,50);

m_Indicator.SetWindowText("0");

m_ComboBox.SetFocus(); //使组合框获得输入焦点

 

return FALSE; // 返回FALSE以表明为某一控件设置了输入焦点

}

 

void CCtrlTestDlg::OnAdd()

{

// TODO: Add your control notification handler code here

 

int i;

CString str;

m_ComboBox.GetWindowText(str);

m_ListBox.AddString(str);

i=m_ComboBox.FindString(-1,str);

if(i>=0)

{

m_ComboBox.DeleteString(i);

m_ComboBox.InsertString(0,str); //将匹配项移到0位置

}

else

{

m_ComboBox.InsertString(0,str);

if(m_ComboBox.GetCount()>MAX_HISTORY)

m_ComboBox.DeleteString(m_ComboBox.GetCount()-1); //删除旧的项

}

}

 

void CCtrlTestDlg::OnClear()

{

// TODO: Add your control notification handler code here

 

m_MultiEdit.SetSel(0,-1);

m_MultiEdit.ReplaceSel("");

}

 

void CCtrlTestDlg::OnDelete()

{

// TODO: Add your control notification handler code here

 

int i,count;

int *pBuffer;

count=m_ListBox.GetSelCount();

if(count<=0)return;

pBuffer=new int[count];

m_ListBox.GetSelItems(count,pBuffer);

for(i=count-1;i>=0;i--) //倒序删除选择项

m_ListBox.DeleteString(pBuffer[i]);

delete pBuffer;

}

 

void CCtrlTestDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)

{

// TODO: Add your message handler code here and/or call default

 

int nScrollMin,nScrollMax,nScrollPos;

int nPageSize;

CString str;

if(&m_ScrollBar!=pScrollBar)return;

nScrollPos=m_ScrollBar.GetScrollPos();

m_ScrollBar.GetScrollRange(&nScrollMin,&nScrollMax);

nPageSize=(nScrollMax-nScrollMin)/10; //指定页长

switch(nSBCode)

{

case SB_LEFT:

nScrollPos=nScrollMin;

break;

case SB_RIGHT:

nScrollPos=nScrollMax;

break;

case SB_LINELEFT:

nScrollPos-=1;

break;

case SB_LINERIGHT:

nScrollPos+=1;

break;

case SB_PAGELEFT:

nScrollPos-=nPageSize;

break;

case SB_PAGERIGHT:

nScrollPos+=nPageSize;

break;

case SB_THUMBPOSITION:

nScrollPos=nPos; //由参数nPos获取滚动框的位置

break;

case SB_THUMBTRACK:

nScrollPos=nPos; //由参数nPos获取滚动框的位置

break;

default:;

}

if(nScrollPos

if(nScrollPos>nScrollMax)nScrollPos=nScrollMax;

if(nScrollPos!=m_ScrollBar.GetScrollPos())

m_ScrollBar.SetScrollPos(nScrollPos); //设置滚动框的新位置

str.Format("%d",nScrollPos);

m_Indicator.SetWindowText(str); //更新静态正文

 

CDialog::OnHScroll(nSBCode, nPos, pScrollBar);

}

 

void CCtrlTestDlg::OnSelchangeCombobox()

{

// TODO: Add your control notification handler code here

 

int length=m_MultiEdit.GetWindowTextLength();

CString str;

m_MultiEdit.SetSel(-1,0);

m_MultiEdit.SetSel(length,length); //移动插入符到编辑正文的末尾

m_ComboBox.GetLBText(m_ComboBox.GetCurSel(),str);

str+=" selected/r/n";

m_MultiEdit.ReplaceSel(str);

}

  在OnInitDialog成员函数中,对一些控件进行了初始化,包括设置滚动条的范围,将静态正文的显示置为 “0”,以及使组合框获得输入焦点.注意,缺省时,OnInitDialog返回TRUE,而新版的函数返回了FALSE.如果OnInitDialog返回TRUE,那么Windows将使tab顺序最靠前的可输入控件获得输入焦点,如果返回FALSE,则表明在OnInitDialog函数中人为地使某个控件获得输入焦点,函数返回后系统就不会再设置输入焦点了.有时,只要合理的安排了控件的tab顺序,就不必在OnInitDialog中人为设置输入焦点.

  当用户点击Add按钮或按回车键后,成员函数OnAdd被调用.该函数将组合框的编辑框中的字符串加入到List列表框的末尾,并将该字符串存入到记事列表框中.这时函数会判断,如果在记事列表中没有匹配的项,则把字符串插入0位置,并在必要时删除最老的列表项,在本例中,记事列表框最多可以容纳5项;如果在记事列表中有匹配的项,那么就把该项移到0位置.

  当用户点击Delete按钮时,成员函数OnDelete被调用,该函数根据CComboBox::GetSelCount获得选择项的数目,并根据这个数目动态创建一个整型数组以存放选择项的索引.然后,调用CComboBox::GetSelItems来获取选择项的索引.最后,把这些选择项删除.注意,这里是倒序删除的,如果按顺序删除,则会使选择项的索引产生错位.

  成员函数OnSelchangeCombobox是Input组合框的CBN_SELCHANGE消息的处理函数.该函数先把多行编辑框的插入符移到编辑正文的末尾,然后从插入符处加入一行形如"XXXX selected"的字符串,以表明用户从记事列表框中新选择了哪个列表项.值得一提的是,上一章的Register程序是用SetWindowText来在编辑正文中插入新的正文的,此方法有一个缺点,就是不能把插入符滚动到新修改过的地方.在本例中,插入编辑正文的方法是先调用CEdit::SetSel移动插入符到指定位置(必要时要滚动以使该位置可见),然后再调用CEdit::ReplaceSel插入新的正文,这样做的好处是编辑框总是滚动到新修改过的地方,从而使得新修改过的地方总是可见的.

  对滚动条控件的测试是在OnHScroll成员函数中完成的.该函数是对话框也即父窗口对水平滚动条控件产生的WM_HSCROLL消息的处理函数.该函数负责移动滚动框并及时更新静态正文的显示以反映滚动框的当前位置.在函数的开头,首先判断是不是m_ScrollBar滚动条发来的消息,这是因为可能会有几个滚动条控件.在该函数中有一个大的switch分枝语句,用来获取滚动框的新位置.需要指出的是,对于SB_THUMBPOSITION和SB_THUMBTRACK这两种情况,应该从OnHScroll函数的nPos参数中获取滚动框的新位置.对于SB_THUMBTRACK,不要企图用CScrollBar::GetScrollPos来获取滚动框的新位置,因为该函数不能正确返回拖动时的滚动框位置.另一个要注意的问题是Windows本身不会自动地使滚动框移动到新位置上,所以需要在OnHScroll中调用CScrollBar::SetScrollPos来移动滚动框.

 

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目录下),有兴趣的读者可以研究一下。

 

6.3 技术总结

  在上一章和本章中,读者已经接触和使用了各种控件。这些控件虽然五花八门,但它们却具有一些共同的特点。本节的目的就是讨论这些共同点,以使读者能在概念上更好地理解控件。

6.3.1 所有的控件都是窗口

  确切地说,所有的控件都是子窗口。控件窗口都具有WS_CHILD风格,它们总是依附于某一个父窗口。所有MFC的控件类都是基本窗口类CWnd的直接或间接派生类,这意味着可以调用CWnd类的某些成员函数来查询和设置控件。常用于控件的CWnd成员函数在表6.34列出,这些函数对所有的控件均适用。

表6.34 常用于控件的CWnd成员函数

函数名

用途

ShowWindow

调用ShowWindow(SW_SHOW)显示窗口,调用ShowWindow(SW_HIDE)则隐藏窗口.

EnableWindow

调用EnableWindow(TRUE)允许窗口,调用EnableWindow(FALSE)则禁止窗口.一个禁止的窗口呈灰色显示且不能接受用户输入.

DestroyWindow

删除窗口.

MoveWindow

改变窗口的位置和尺寸.

SetFocus

使窗口具有输入焦点.

  例如,如果想把一个编辑框控件隐藏起来,可以用下面这行代码完成.

m_MyEdit.ShowWindow(SW_HIDE);

6.3.2 控件的创建方法

  控件的创建有自动和手工两种常用方法.

  控件的自动创建是通过向对话框模板中添加控件实现的.到目前为止,读者所使用的控件都是用这种方法创建的.当调用对话框类的DoModal和Create显示对话框时,框架会根据对话框模板资源提供的控件信息自动地创建控件.这种方法的优点是方便直观,用户可以在对话框模板编辑器的控件面板中选择控件,可以在对话框模板中调整控件的位置和大小,还可以通过属性对话框设置控件的风格.

  手工创建控件是一种比较专业的方法,包括下面两步:

构建一个控件对象.通常的做法是把控件对象嵌入到父窗口(如对话框)对象中,即以成员变量的形式定义一个控件对象.这样,在构建父窗口对象时,控件对象会被自动构建.程序也可以用new操作符创建控件对象,但要注意MFC的控件对象不具有自动清除的功能,因此需要在关闭父窗口时用delete操作符删除控件对象(参见5.4.2).

调用控件对象的Create成员函数创建控件.一般来说,如果要在对话框中创建控件,那么应该在OnInitDialog函数中调用Create,如果要在非对话框窗口中创建控件,则应该在OnCreate函数中调用Create.

  清单6.3是一个手工创建控件的实例.

清单6.3 控件的手工创建

#define ID_EXTRA_EDIT 100

 

class CMyDialog : public CDialog

{

protected:

CEdit m_edit; // Embedded edit object

public:

virtual BOOL OnInitDialog();

};

. . . . . .

BOOL CMyDialog::OnInitDialog()

{

CDialog::OnInitDialog();

CRect rect(85, 110, 180, 210);

 

m_edit.Create(WS_CHILD | WS_VISIBLE | WS_TABSTOP |

ES_AUTOHSCROLL | WS_BORDER, rect, this, ID_EXTRA_EDIT);

m_edit.SetFocus();

return FALSE;

}

  不难看出,控件的手工创建是在程序中通过控件对象完成的,与对话框模板无关.在Create函数中,需要提供控件的风格,控件的尺寸和位置,控件的ID等信息.手工创建实际上是一种动态创建过程,程序可以在任何时侯根据需要来创建,不一定非要在OnInitDialog中进行.

  控件并不是对话框所专有的,某些非对话框窗口也可以拥有控件.如果要在象工具条或状态栏这样的非对话框窗口中创建控件,就必需用手工创建方法.自动创建由于要依赖对话框模板,因此只适用于对话框.

6.3.3 访问控件的方法

  控件是一种交互的工具,应用程序需要通过某种方法来访问控件以对其进行查询和设置.访问控件有四种方法:

  1. 利用对话框的数据交换功能访问控件.这种方法适用于自动创建的控件.先用ClassWizard为对话框类加入与控件对应的数据成员变量,然后在适当的时侯调用UpdateData,就可以实现对话框和控件的数据交换.这种方法只能交换数据,不能对控件进行全面的查询和设置,而且该方法不是针对某个控件,而是针对所有参与数据交换的控件.另外,对于新型的Win32控件,不能用ClassWizard创建数据成员变量.因此,该方法有较大的局限性.

  2. 通过控件对象来访问控件.控件对象对控件进行了封装,它拥有功能齐全的成员函数,用来查询和设置控件的各种属性.通过控件对象来访问控件无疑是最能发挥控件功能的一种方法,但这要求程序必需创建控件对象并使该对象与某一控件相连.对于自动创建的控件,可利用ClassWizard方便地创建与控件对应的控件对象.对于手工创建的控件,因为控件本身就是通过控件对象创建的,所以不存在这一问题.

  3. 利用CWnd类的一些用于管理控件的成员函数来访问控件.这些函数已在表5.5列出.只要向这些函数提供控件的ID,就可以对该控件进行访问.使用这些函数的好处是无需创建控件对象,就可以对控件的某些常用属性进行查询和设置.该方法对自动和手工创建的控件均适用.

  4. 用CWnd::GetDlgItem访问控件.该函数根据参数说明的控件ID,返回指定控件的一个CWnd型指针,程序可以把该指针强制转换成相应的控件类指针,然后通过该指针来访问控件.该方法对自动和手工创建的控件均适用.在上一章中就曾经使用过这种方法,读者可参见5.3.6.其实该方法与通过控件对象来访问控件的方法在本质上是一样的,在表5.5中亦包括GetDlgItem函数,但为了强调其重要性,这里把它单独列为一种方法.

 

6.3.4 控件及控件对象的删除

  当关闭父窗口时,控件会被自动删除,因此在一般情况下不必操心删除问题.如果由于某种需要想手工删除控件,可以调用CWnd::DestroyWindow来完成.

  对于控件对象的删除,有两种情况.若控件对象是以成员变量的形式创建的,那么该对象将会随着父窗口对象的删除而被删除,因此在程序中无需操心.若控件对象是用new操作符在堆中创建的,则必需在关闭父窗口时用delete操作符删除对象,这是因为所有MFC的控件类都是非自动清除的(参见5.4.2).

6.3.5 控件通知消息

  传统控件和Win32 控件采用了不同的通知消息机制,请参见6.1.1和6.2.1。

 

6.4 在非对话框窗口中使用控件

  控件并不是对话框独有的,事实上,很多非对话框窗口都可以使用控件.比较典型的应用是在表单视图、工具条和状态栏中使用控件.

6.4.1 在表单视图中使用控件

  MFC提供了一个名为CFormView的特殊视图类,我们称其为表单视图.表单视图是指用控件来输入和输出数据的视图,用户可以方便地在表单视图中使用控件.表单视图具有对话框和滚动视图的特性,它使程序看起来象是一个具有滚动条的对话框.在有些情况下,用表单视图比用普通视图更符合用户的需要,例如,在向数据库输入数据时,显然用表单的形式可以更习惯些.

  用AppWizard可以方便地创建基于表单视图的应用程序,只要在MFC AppWizard对话框的第六步先选择CView,然后在Base class栏中选择CFormView,AppWizard就会创建一个基于CFormView的应用程序.

  读者可以按上述方法建立一个名为Test的应用程序.在Test工程的资源中,读者会发现一个ID为IDD_TEST_DIALOG的对话框模板,该对话框模板可供用户放置和安排控件.在程序运行时,框架根据该对话框模板创建CFormView对象,并根据模板的信息在表单视图中自动创建控件.与设计对话框类相类似,用户可以用ClassWizard为表单视图类加入与控件对应的成员变量,可以调用UpdateData在控件和成员变量之间交换数据,但对控件的初始化工作是在OnInitialUpdate函数而不是在OnInitDialog函数中进行的.

  基于表单视图的应用程序与基于对话框的应用程序都是在应用程序中直接使用控件,但二者有很多不同之处.基于对话框的应用程序是用一个对话框来作为程序的主窗口的,因而程序的主窗口的特性与对话框类似,如窗口的大小不能改变,程序没有菜单条、工具条和状态栏等.基于表单视图的应用程序仍然是基于Doc/View框架结构的(见七、八、九章),只是视图被换成了表单视图,也就是说,应用程序的窗口可以改变大小,程序有菜单条、工具条和状态栏,且程序仍然可以Dov/View运行机制来处理文档.

  表单视图比较简单,这里就不举例说明了.在第十章,读者会看到使用表单视图的例子.

6.4.2 在工具条和状态栏中使用控件

  一个专业的程序常常会在工具条和状态栏中加入一些控件以方便用户的使用.例如,在Developer Studio的工具条中就有不少组合框,而在状态栏中则常常会显示一个进度条来表明工作的进度.

  如果读者想在自己程序的工具条和状态栏中加入控件,则需要掌握一些技巧.在本小节,我们将结合一个具体实例来演示这些技巧.例程的名为CtrlInBar,其界面如图6.8所示.可以看出,该程序在工具条中创建了一个组合框,在状态栏中创建了一个进度条.

图6.8 CtrlInBar程序

  现在让我们开始工作.首先,请读者用AppWizard建立一个名为CtrlInBar的单文挡MFC应用程序,然后,请按清单6.4修改源代码.注意在程序中编写了一个CToolBar类的派生类CMyToolBar,以及一个CStatusBar的派生类CMyStatusBar,这两个类与CMainFrame类在同一模块中.

清单6.4 在工具条和状态栏中创建控件的有关代码

// MainFrm.h : interface of the CMainFrame class

//

/

#define IDC_MYCOMBO 100

class CMyToolBar : public CToolBar

{

public:

CComboBox m_ComboBox;

BOOL CreateComboBox(int nIndex);

};

 

#define ID_INDICATOR_PROGRESS 100

class CMyStatusBar : public CStatusBar

{

public:

CProgressCtrl m_Progress;

int m_nProgressPane;

BOOL CreateProgressCtrl(int nPane);

 

afx_msg void OnSize(UINT nType, int cx, int cy);

DECLARE_MESSAGE_MAP()

};

 

class CMainFrame : public CFrameWnd

{

 

. . . . . .

protected: // control bar embedded members

CMyStatusBar m_wndStatusBar;

CMyToolBar m_wndToolBar;

 

. . . . . .

};

 

// MainFrm.cpp : implementation of the CMainFrame class

//

 

. . . . . .

static UINT indicators[] =

{

ID_SEPARATOR, // status line indicator

 

ID_SEPARATOR

};

 

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CFrameWnd::OnCreate(lpCreateStruct) == -1)

return -1;

 

if (!m_wndToolBar.Create(this) ||

!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))

{

TRACE0("Failed to create toolbar/n");

return -1; // fail to create

}

 

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: Remove this if you don't want tool tips or a resizeable toolbar

m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |

CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);

 

 

m_wndToolBar.CreateComboBox(0); //在工具条的最左边创建组合框

m_wndStatusBar.CreateProgressCtrl(1); //在第二个窗格创建进度条

 

m_wndToolBar.m_ComboBox.AddString("Item1");

m_wndToolBar.m_ComboBox.AddString("Item2");

m_wndToolBar.m_ComboBox.AddString("Item3");

m_wndToolBar.m_ComboBox.AddString("Item4");

m_wndStatusBar.m_Progress.SetRange(0,200);

m_wndStatusBar.m_Progress.SetPos(100);

 

 

. . . . . .

}

 

 

/

//CMyToolBar

 

//参数nIndex是按钮的索引,函数将在该按钮的左侧创建组合框

BOOL CMyToolBar::CreateComboBox(int nIndex)

{

if(m_ComboBox.GetSafeHwnd()) //防止重复创建

return FALSE;

 

CToolBarCtrl &ToolBarCtrl=GetToolBarCtrl();

TBBUTTON button;

CRect rect;

button.fsStyle=TBSTYLE_SEP;

ToolBarCtrl.InsertButton(nIndex,&button); //插入空位

ToolBarCtrl.InsertButton(nIndex,&button);

ToolBarCtrl.InsertButton(nIndex,&button);

//设置空位的宽度(处于中间的空位用来容纳组合框)

SetButtonInfo(nIndex+1,IDC_MYCOMBO,TBBS_SEPARATOR,100);

SetButtonInfo(nIndex, ID_SEPARATOR, TBBS_SEPARATOR, 12);

SetButtonInfo(nIndex+2, ID_SEPARATOR, TBBS_SEPARATOR, 12);

GetItemRect(nIndex+1, &rect); //获取中间空位的坐标

rect.top = 3;

rect.bottom = rect.top + 100;

if (!m_ComboBox.Create(CBS_DROPDOWNLIST|WS_VISIBLE|WS_TABSTOP,

rect, this, IDC_MYCOMBO))

return FALSE;

m_ComboBox.SetItemHeight(-1,15); //设置编辑框组件的高度

return TRUE;

}

 

 

/

//CMyStatusBar

 

 

BEGIN_MESSAGE_MAP(CMyStatusBar, CStatusBar)

ON_WM_SIZE()

END_MESSAGE_MAP()

 

//参数nPane是窗格的索引,函数将在该窗格内创建进度条控件

BOOL CMyStatusBar::CreateProgressCtrl(int nPane)

{

if(m_Progress.GetSafeHwnd()) //防止重复创建

return FALSE;

 

//设置该窗格的宽度为200

SetPaneInfo(nPane,GetItemID(nPane),SBPS_NORMAL,200);

CRect rect(0,0,1,1);

if(!m_Progress.Create(WS_CHILD|WS_VISIBLE,rect,this,

ID_INDICATOR_PROGRESS))

return FALSE;

m_nProgressPane=nPane;

return TRUE;

}

 

void CMyStatusBar::OnSize(UINT nType, int cx, int cy)

{

CStatusBar::OnSize(nType, cx, cy);

if(m_Progress.GetSafeHwnd()==NULL) return;

 

CRect rect;

GetItemRect(m_nProgressPane,&rect);

m_Progress.MoveWindow(rect); //调整控件的位置和尺寸

}

  CMyToolBar类可以在工具条中指定按钮的左边放置一个下拉列表式组合框,并在组合框的两端留出空位.该类的CreateComboBox成员负责创建组合框,参数nIndex是工具条按钮的索引,需注意的是工具条的一个空位也要占有一个索引.在CreateComboBox中,主要调用了下列函数:

调用CToolBar::GetToolBarCtrl返回一个CToolBarCtrl对象.从4.0版开始,CToolBar类是在新控件类CToolBarCtrl类的基础上实现的,后者具有更强大的功能.例如CToolBarCtrl提供了CToolBar没有的InsertButton成员函数.

调用CToolBarCtrl::InsertButton在nIndex索引处插入三个空位.

调用CToolBar::SetButtonInfo设置空位的宽度,其中中间的空位有100像素宽,用来容纳组合框.

调用CToolBar::GetItemRect获得中间空位的坐标.

调用CComboBox::Create函数创建组合框.注意rect对象说明的是包括列表框在内的组合框的尺寸.

调用CComboBox::SetItemHeight设置编辑框组件的高度.

 

  CMyStatusBar类可以在指定的状态栏窗格中放置一个进度条.该类的CreateProgressCtrl成员负责创建进度条,参数nPane是窗格的索引.在该函数中主要调用了下列函数:

调用CStatusBar::SetPaneInfo设置窗格的宽度为200.在调用该函数时,先调用CStatusBar::GetItemID返回窗格的ID.

调用CProgressCtrl::Create创建控件.

  大家可能会奇怪,CProgressCtrl::Create创建的控件只有1×1大小.这是由于在调用该函数创建控件时,状态栏的大小往往并未确定.这时如果调用CStatusBar::GetItemRect,只能得到0坐标,而不能得到正确的窗格坐标,所以程序只好先创建一个1×1的控件.

  工具条中按钮和控件的尺寸及其相对于工具条的位置不会随外界因素发生变化,而状态栏则不同,当用户改变了框架窗口的宽度时,状态栏的宽度也会随之改变,并且它会重新调整各窗格的大小和位置,此时如果不及时调整进度条的坐标,那么进度条与所在窗格之间将发生错位.调整进度条的大小和位置的工作由CMyStatusBar::OnSize函数完成.当窗口的尺寸发生改变后,窗口会收到WM_SIZE消息,OnSize是WM_SIZE消息的处理函数.在CMyStatusBar::OnSize函数中,先调用CStatusBar::GetItemRect获得进度条所在窗格的坐标,然后调用CWnd::MoveWindow来调整进度条控件的坐标.

  在窗口形成时,也会收到WM_SIZE消息,这时OnSize函数可以及时调整进度条的大小和位置.

 

6.5 设计新的控件类

  虽然Windows提供了大量的控件,但不一定总能满足用户的需要.有时,用户需要一些有特殊功能的控件.例如,有时希望编辑框控件只能接受数字输入,当用户输入非数字字符时,编辑框控件会发出声响来提醒用户.在这种情况下,标准的CEdit类就无能为力了.

  当控件无法满足需要时,用户可以从原来的控件类派生一个新类.通过合理地设计派生类,可以修改控件的行为和属性以达到用户的要求.利用ClassWizard的强大功能,读者可以方便地创建和设计控件类的派生类.

6.5.1 创建标准控件类的派生类

  这个任务可以用ClassWizard完成,其具体步骤如下:

按Ctrl+W进入ClassWizard.

单击Add Class按钮并选择New...菜单项,则打开Create New Class对话框.

在Create New Class对话框的Name栏中输入派生类的名字,在Base class栏中选择一个标准的控件类做为基类,然后按Create按钮即可.

6.5.2 利用MFC的控件通知消息反射机制完善派生类的功能

  创建好派生类后,接下来的任务就是修改新类的代码以完善其功能.例如,为新类添加必要的成员变量,提供新的成员函数以及消息处理函数等等.其中为控件添加消息处理函数是最重要的,因为这直接关系着控件新功能的实现.

  与控件有关的消息包括控件本身接收的消息和发给父窗口的通知消息两种.利用ClassWizard可以方便地为派生类创建这两类消息的处理函数.读者也许会感到奇怪,控件通知消息不是由父窗口处理的吗,难道控件本身也有机会处理通知消息?

  答案是肯定的.从4.0版开始,MFC提供了一种消息反射机制(Message Reflection),可以把控件通知消息反射回控件.具体地讲,当父窗口收到控件通知消息时,如果父窗口有该消息的处理函数,那么就由父窗口处理该消息,如果父窗口不处理该消息,则框架会把该消息反射给控件,这样控件就有机会处理该消息了.由此可见,新的消息反射机制并不破坏原来的通知消息处理机制.

  消息反射机制为控件提供了处理通知消息的机会,这在有些情况下是很有用的.例如,如果派生类想改变控件的背景色,就需要处理WM_CTLCOLOR通知消息.大多数控件在需要重绘时,会向父窗口发送WM_CTLCOLOR消息,父窗口在处理该消息时会返回一个刷子用来画控件的背景.如果按传统的方法,由父窗口来处理这个消息,则加重了控件对象对父窗口的依赖程度,每当使用这样一个新控件时,都要在父窗口中提供控件的WM_CTLCOLOR消息处理函数,这显然违背了面向对象的原则.若由控件自己处理WM_CTLCOLOR消息,则使得控件对象具有更大的独立性,而父窗口也可以省去一些不必要的工作.

  读者可以在自己的程序中用ClassWizard创建一个CEdit类的派生类试试.在派生类的消息列表中,在有些消息前面有一个"="符号,这表明这些消息是可以反射的通知消息.读者可以按照通常的方法创建反射消息的处理函数.

6.5.3 利用SubclassDlgItem函数动态连接控件和控件对象

  要在程序中创建新设计的控件,显然不能用自动创建的办法,因为对话框模板对新控件的特性一无所知.程序可以用手工方法创建控件,在调用派生类的Create函数时,派生类会调用基类的Create函数创建控件.用Create函数创建控件是一件比较麻烦的工作,程序需要为函数指定一大堆的控件风格以及控件的坐标和ID.特别是控件的坐标,没有经验的程序员很难确切地安排控件的位置和大小,往往需要反复调整.利用MFC的CWnd::SubclassDlgItem提供的动态连接功能,可以避免Create函数的许多麻烦,该函数大大简化了在对话框中创建派生控件的过程.

  大家知道,在用手工方法创建控件时,先要构建一个控件对象,然后再用Create函数在屏幕上创建控件窗口,也就是说,控件的创建工作是由控件对象完成的.动态连接的思路则不同,SubclassDlgItem可以把对话框中已有的控件与某个窗口对象动态连接起来,该窗口对象将接管控件的消息处理,从而使控件具有新的特性.SubclassDlgItem函数的声明为

BOOL SubclassDlgItem( UINT nID, CWnd* pParent );

 

  参数nID是控件的ID,pParent是指向父窗口的指针.若连接成功则函数返回TRUE,否则返回FALSE.

  综上所述,要在程序中使用派生控件,应该按下面两步进行:

在对话框模板中放置好基类控件.

在对话框类中嵌入派生控件类的对象.

在OnInitDialog中调用SubclassDlgItem将派生类的控件对象与对话框中的基类控件相连接,则这个基类控件变成了派生控件.

 

  例如,如果要在对话框中使用新设计的编辑框控件,应先在对话框模板中的合适位置放置一个普通的编辑框,然后,在OnInitDialog函数中按下面的方式调用SubclassDlgItem即可:

BOOL CMyDialog::OnInitDialog()

{

CDialog::OnInitDialog();

m_MyEdit.SubclassDlgItem(IDC_MYEDIT, this);

return TRUE;

}

 

 

你可能感兴趣的:(技术文章)