Chapter 9 Child Window Control

Child Window Control:

conrtol(控件)讲白了其实就是子窗口,不过是会向父窗口发送消息的子窗口:
在子窗口的WndProcChild里面利用hwndParent=GetParent(hwndChild);就能获取父窗口的句柄了,接着SendMessage(hwndParent,message,wparam,lparam);就能向父窗口发送消息;其中message参数只需要满足>=WM_USER消息便可,这样是为了防止与预定义的WM_消息发生冲突。这样子窗口处理鼠标,键盘消息之后发生变化就会向父窗口发送消息,换个角度看,这样的子窗口控件就变成了父窗口的高级输入设备了不是么。

上面讲的都是自己创建的子窗口来作为控件,不过Windows早就定义好了一系列的子窗口控件了,包括这些子窗口的注册好的类,处理各种消息的子窗口过程函数。所以我们如果想要使用这些预定义的控件,其实只要在CreateWindow那里使用注册好的类名,在window style里面更加准确描述好外观,MoveWindow调整好大小与位置就能使用了。这些控件SendMessage中message参数也是预定义好的,为WM_COMMAND


Create Child Window:

static HWND BtnHwnd[10];
	static int cxChar,cyChar;
	if(uimsg==WM_CREATE)
	{
		cxChar=LOWORD(GetDialogBaseUnits());
		cyChar=HIWORD(GetDialogBaseUnits());
		for(int i=0;i<10;i++)
		{
			BtnHwnd[i]=CreateWindow(TEXT("button"),btn[i].szDesz,WS_CHILD | WS_VISIBLE | btn[i].iStyle,cxChar,cyChar*(1+2*i),
				20*cxChar,7*cyChar/4,hwnd,(HMENU)i,((LPCREATESTRUCT)lparam)->hInstance,NULL);
		}
		return 0;
	}


这个部分的代码就是创建windows预定义好的子窗口控件中的button类型。
首先GetDialogBaseUnits()函数是用来获取默认字体的字符宽度与高度,它会返回一个32位的值,low word是字符宽度,high word是字符高度。当然前面用到的GetTextMetrics(&tm);函数也是能用的。
类名"button"是预定义好的;
子窗口的style:BS_PUSHBUTTON,BS_DEFPUSHBUTTON,BS_CHECKBOX,BS_AUTOCHECKBOX,BS_3STATE,BS_AUTO3STATE,BS_RADIOBOX,BS_AUTORADIOBOX,
BS_GROUPBOX,BS_OWNERDRAW(10种)
WM_CREATE消息的lparam参数是CREATESTRUCT结构体的指针,而hInstance则是这个结构体的一个成员
((LPCREATESTRUCT)lparam)->hInstance
等同于
(HINSTANCE)GetWindowLong(hwnd,GWL_HINSTANCE)


The Child Talks To Its Parent:

when you click button的时候,button会发送WM_COMMAND消息到父窗口去。
WM_COMMAND的lparam参数是子窗口的句柄,wparam参数的low word是子窗口的ID,high word是通知码(使消息更为具体)
通知码(Notification codes):
BN_CLICKED     0
BN_PAINT           1
BN_HILITE or BN_PUSHED        2
BN_UNHILITE or BN_UNPUSHED       3
BN_DISABLE       4
BN_DOUBLECLICKED or BN_DBLCLK    5
BN_SETFOCUS   6
BN_KILLFOCUS   7

其中1-4是旧式按钮BS_USERBUTTON,现已被BS_OWNERDRAW取代。
5号双击通知码,只有BS_RADIOBUTTON,BS_AUTOBUTTON,BS_OWNERDRAW这3种接收到,或者是window style那里设置了BS_NOTIFY也能接收到。
6,7号焦点相关的通知码,只有设置了BS_NOTIFY才接受到。
click a button的时候按钮文本有虚线框表示具有input focus,这个时候所有keyboard message都传给button而不是父窗口,按钮会忽视除了空格之外其他按键,空格作用等同于鼠标单击

The Parent Talks to Its Child:

除了子窗口会send WM_COMMAND消息到父窗口之外,父窗口同样也能够send Message给子窗口,除了许多WM_开头的消息之外,还有专门为button开设的BM_开头的消息:
BM_GETCHECK 0x00F0
BM_SETCHECK 0x00F1
BM_GETSTATE 0x00F2
BM_SETSTATE 0x00F3
BM_SETSTYLE 0x00F4
BM_CLICK 0x00F5
BM_GETIMAGE 0x00F6
BM_SETIMAGE 0x00F7
父窗口发送BM_GETCHECK和BM_SETCHECK消息给子窗口,用于获取或者设置check box或者radio button的check mark
父窗口发送BM_GETSTATE和BM_SETSTATE消息给子窗口,是获取或者设置子窗口是普通状态还是pushed 状态
父窗口发送BM_SETSTYLE消息给子窗口,用于createwindow之后再次改变button style

id = GetWindowLong (hwndChild, GWL_ID) ;//获取子窗口的ID
id = GetDlgCtrlID (hwndChild) ;//获取子窗口的ID
hwndChild = GetDlgItem (hwndParent, id) ;//获取子窗口的HwndChild

Push Button:

push button有BS_PUSHBUTTON和BS_DEFPUSHBUTTON这2种。
press push button就会画出3D阴影效果显示按下的样子,当释放push button的时候又会恢复原状
push button与父窗口的交流:WM_COMMAND中HIWORD(wparam)==BN_CLICK
父窗口与push button的交流:
SendMessage(ChildHwnd,BM_SETSTATE,1,0);//把Push button的状态设置为pressed的状态
SendMessage(ChildHwnd,BM_SETSTATE,0,0);//把Push button的状态设置为普通状态
SendMessage(CHildHwnd,BM_GETSTATE,0,0);//返回值true表示按下,false表示释放


Check Box:

Check Box用于多选,有BS_CHECKBOX和BS_AUTOCHECKBOX这2种。
BS_CHECKBOX:
必须要自己set check mark
BS_CHECKBOX与BS_PUSHBUTTON类似,是当释放按钮的时候button才向父窗口发送WM_COMMAND,对于BS_CHECKBOX,我们没设置BS_NOTIFY,BN_SETFOCUS和BN_KILLFOCUS不会接收到,也没有BN_BDLCLK,所以不用特别判断Notification code直接在WM_COMMAND消息里面给check box发送设置check mark的消息就行,SendMessage((HWND)lparam,BM_GETCHECK,0,0)为true表示被选中,false表示没选中。
SendMessage(ChildHwnd,BM_SETCHECK,1,0);//把check box设为选中
SendMessage(ChildHwnd,BM_SETCHECK,0,0);//把check box设为没选中

BS_3STATE和BS_AUTO3STATE:
3State box有3种状态,SendMessage(ChildHwnd,BM_SETCHECK,0/1/2,0);//wparam为0设为没选中,1为选中,2为灰色状态
同理SendMessage(ChildHwnd,BM_GETCHECK,0,0);//返回值0没选中,1选中,2灰色状态

check box的最小高度是一个字符,最小宽度是box的文本长度外加2个字符的宽度。


Radio Button:

有BS_RADIOBUTTON和BS_AUTORADIOBUTTON这2种,但是BS_AUTORADIOBUTTON这种只用于对话框。
BS_RADIOBUTTON与BS_CHECKBOX类似,需要自己set check mark
对于发送了WM_COMMAND消息的子窗口,有ID和句柄,所以可以很清楚是哪一个radio button被选中,对于选中的radio button:
SendMessage(ChildHwnd,BM_SETCHECK,1,0);//把radio button设为选中
同一组的其他Radio Button就必须设置为没选中:
SendMessage(ChildHwnd,BM_SETCHACK,0,0);//把radio button设为没选中

Group Box:

group box是一种比较特殊的button,它不会处理任何mouse和keyboard message,也不会发送WM_COMMAND给父窗口,纯粹就是一个拿来看的矩形而已。

Change Text:

SetWindowText(hwnd,pszText);//hwnd是普通窗口就改变标题栏的文字,是button就改变button的文本
iLength=GetWindowText(hwnd,pszBuffer,iMaxLength);//把hwnd的文字保存到pszBuffer,最大长度iMaxLength,返回的是复制到的文字长度
iLength=GetWindowTextLenght(hwnd);//获取hwnd的文本长度

Visible and Enable:

只有visible and enabled的child wndow才能够接收到mouse and keyboard input。
CreateWindow没用WS_VISIBLE就需要调用ShowWindow函数来显示window,相反如果用了WS_VISIBLE那么就不需在ShowWindow来显示了,反而可以
ShowWindow(hwndChild,SW_HIDE);来隐藏子窗口
EnableWindow(hwndChild,false);//无效化hwndChild
EnableWindow(hwndChild,true);//有效化hwndChild

Button and input focus:

Windows在切换input focus的时候(例如由父窗口传给子窗口),首先向失去焦点的窗口发送WM_KILLFOCUS消息,wparam是即将获得焦点的窗口句柄,再向获得焦点的窗口发送WM_SETFOCUS消息,wparam是失去焦点的窗口的句柄。
可以这样防止父窗口丢失焦点:
case WM_KILLFOCUS :
for (i = 0 ; i < NUM ; i++)
if (hwndChild [i] == (HWND) wParam)
{
SetFocus (hwnd) ;
break ;
}
return 0 ;

或者:
case WM_KILLFOCUS :
if (hwnd == GetParent ((HWND) wParam))
SetFocus (hwnd) ;
return 0 ;


Control and Color:

有没发觉check box,radio button这些button有一个灰色框背景,push button反而好点,要解决这个问题需要了解系统颜色。
为了便于display绘图,windows定义了29种color:他们的宏以COLOR_开头,注意这些宏是一个整数,并不是代表颜色的,要想知道宏代表的颜色,就必须调用GetSysColor(int nIndex);//获取宏的颜色,返回值是DWORD,可以GetRValue(),GetGValue(),GetBValue()
同时也有SetSysColors:
BOOL WINAPI SetSysColors(
  _In_  int cElements,
  _In_  const INT *lpaElements,
  _In_  const COLORREF *lpaRgbValues
);
例子:
int iOldColor[]={COLOR_BTNFACE};
COLORREF iNewColor[]={RGB(255,0,0)};
SetSysColors(1,iOldColor,iNewColor);
system color是静态的,SetSysColors改变了一次,删除改变的代码也不会变回去,所以实验的时候要小心点。

一些系统颜色:
COLOR_BTNFACE用于push button的主表面,用于其他类型button的背景色,还用于dialog boxes and message boxes
COLOR_BTNSHADOW用于push button的底部和右侧,用于check box的方块里,用于radio button的圆圈里
COLOR_BTNTEXT用于button text color,其他则用COLOR_WINDOWTEXT


push button和owner-draw button在准备paint client的时候会发送WM_CTLCOLORBTN消息给父窗口,wparam是hdc(ChildWindow拿来绘图用的DC),lparam是hwndChild,我们需要在WM_CTLCOLORBTN消息里面返回一个HBRUSH,但是只有owner-draw button会用这个返回的画刷来绘制背景。

与WM_CTLCOLORBTN类似,owner-draw button每当需要重画的时候就会向父窗口发送WM_DRAWITEM消息,可以在这个消息里实现对owner-draw button自己的WM_PAINT消息类似的处理,就是画button的Client了。

WM_DRAWITEM消息的lparam是LPDRAWITEMSTRUCT,DRAWITEMSTRUCT结构体里面有:hDC(button的DC),rcItem(button的size,相当于GetClientRect(ChildHwnd,rcitem);一样),CtrlID(button的ID,当然wparam也是button的ID),itemState(记录按下与否,是否有焦点:itemState & ODS_SELECTED  
itemState & ODS_FOCUS)


Static 类:

除了button类之外Windows还定义了static类,这类子窗口不会处理mouse and keyboard message,也不会给父窗口发送WM_COMMAND消息的。
static类的style有:
SS_BLACKRECT,SS_BLACKFRAME,SS_GRAYRECT,GRAYFRAME,SS_WHITERECT,SS_WHITEFRAME(这6种都是不会显示字符的)
SS_LEFT,SS_RIGHT,SS_CENTER(只有这3种会显示字符,CreateWindow时标题参数是显示的字符,或者之后用SetWindowText函数来设置也可以)
SS_ETCHEDVERT,SS_ETCHEDHORZ,SS_ETCHEDFRAME(画分割线的)

static这里有一点特别要注意:我们一般都想用static类来划分窗口达到美化效果吧,例如想画条分割线分割内容,又或者想弄个黑色矩形,把button全部放在上面看起来像个工具箱一样的效果,但是我们必须记住,static都是子窗口来的,在黑色矩形上面放button,这些控件全都是兄弟窗口的时候就会遇到之前没遇到过的问题:兄弟窗口重叠的问题,之前最多就遇到过子窗口覆盖在父窗口上,接受mouse message的当然就是子窗口,子窗口也当然是可见的。但是兄弟窗口重叠却有点不同,这里遵循这样的一个原则,先创建的在下面,后创建的会覆盖在上面,举个例子就是先创建button,后创建static,黑色矩形会覆盖掉button,令你看不到button的,但是你点下button的位置,父窗口依旧可以收到WM_COMMAND消息,重绘button,这时就又见到button了,这里是这么一回事:
 当我按下那个button与static重叠的位置时,鼠标消息传给static,但是static是不处理这个消息的,它return a value of HTTRANSPARENT给Windows,导致Windows发送同样的消息给static下面的button,这是button就受到一个click,返回WM_COMMAND消息


Scroll Bar Class:

滚动条类与之前讲过的滚动条有区别的,首先它的位置是可以在ClIent的任何一处,在CreateWindow里面class:scrollbar ,style:SBS_VERT or SBS_HORZ
滚动条类不会发送WM_COMMAND消息,而是发送WM_VSCROLL or WM_HSCROLL消息,与之前的滚动条区别在于lparam参数为控件的hwnd,而之前的lparam参数则是0
wparam参数一样,LOWORD是SB_LINEUP,SB_LINEDOWN这些,HIWORD则是SB_THUMBTRACK或者SB_THUMBPOSTION的具体位置。
SetScrollPos这些函数一样的用法,只是一开始的句柄变成控件的hwnd,之后SB_VERT变成SB_CTL
OldScroll[i] = (WNDPROC) SetWindowLong (hwndScroll[i],GWL_WNDPROC, (LONG) ScrollProc) ;//改变窗口的过程函数,返回之前的过程函数
WM_CLTCOLORBTN与WM_CTLCOLORSTATIC,WM_CTLCOLORSCROLLBAR一样都是当要绘制子窗口控件的时候发送给父窗口的消息:
其中wparam是用来绘制子窗口的hdc,lparam是子窗口的hwnd,返回的是hbrush,用于绘制子窗口背景

scroll bar control会响应鼠标消息发送WM_VSCROLL这些消息,但是却不会得到input focus,因而处理不到键盘消息,可以在CreateWindow参数style加上WS_TABSTOP,那么scroll bar control就可以获得焦点了,thumb那里会有闪烁,并且自带的过程函数还处理光标键,PageUp,PageDown键,但是会ignore tab key,因此才需要加上一个ScrollProc窗口子类。


窗口子类:这是一种非常给力的想法哦。
我们在处理这些Windows早为我们准备好的类的时候,它的过程函数在Windows里面,我们不能修改这些过程函数导致我们没办法对我们感兴趣的消息进行处理,到目前为止我们只能通过控件给父窗口发送的WM_COMMAND,WM_CTLCOLORBTN,WM_VSCROLL这些消息来处理一部分相应,但是自由度还是不够,例如我想按钮处理WM_MOUSEMOVE消息怎么办?我们有必要跟Windows内部的这些类的过程函数打交道。
OldScroll[i] = (WNDPROC) SetWindowLong (hwndScroll[i], GWL_WNDPROC,
(LONG) ScrollProc)) ;
WNDPROC OldScroll[3];声明了3个过程函数类型的变量
SetWIndowLong(hwnd,GWL_WNDPROC,(LONG)NewWndProc);//SetWIndowLong真是个功能强大的函数,这个函数把hwnd这个窗口句柄所指示的窗口的过程函数给换成了新的NewWndProc,返回的是旧的过程函数。
到这里已经很明了了:
LRESULT CALLBACK NewWndProc(HWND hwnd,UINT uimsg,WPARAM wparam,LPARAM lparam)
{
	if(uimsg==WM_MOUSEMOVE)
	{
		//......
	}
	return CallWindowProc(OldWndProc,hwnd,uimsg,wparam,lparam);
}


最后再介绍下SetClassLong(hwnd,GCL_HBRBACKGROUND,(LONG)hbrush);//改变hwnd窗口所属的类的背景画刷,返回之前的画刷,改变之后记得无效化一下才会显示新的背景颜色



The Edit Class

编辑框控件,在CreateWindow里面className参数为"edit"就能创建编辑框控件
style:格式对齐的:ES_LEFT,ES_RIGHT,ES_CENTER(默认左对齐)
单行还是多行:ES_MULTILINE(默认单行)
ES_AUTOHSCROLL(可以向后一直加字符),WS_HSCROLL(多出一个水平滚动条可供拖动)
ES_AUTOVSCROLL,WS_VSCROLL
WS_BORDER(增加边界)

当编辑框得到焦点或者失去焦点,改变文本,拖动滚动条,空间溢满等情况都会发送WM_COMMAND给父窗口,lparam是edit控件的句柄,wparam的low word是ID,high word是通知码:
EN_SETFOCUS
Edit control has gained the input focus.
EN_KILLFOCUS
Edit control has lost the input focus.
EN_CHANGE
Edit control's contents will change.
EN_UPDATE
Edit control's contents have changed.
EN_ERRSPACE
Edit control has run out of space.
EN_MAXTEXT
Edit control has run out of space on insertion.
EN_HSCROLL
Edit control's horizontal scroll bar has been clicked.
EN_VSCROLL
Edit control's vertical scroll bar has been clicked.

获取和设置edit控件文本的方式:SetWIndowText与GetWindowTextLength,GetWindowText

发送给edit控件的消息:
SendMessage (hwndEdit, WM_CUT, 0, 0) ;
SendMessage (hwndEdit, WM_COPY, 0, 0) ;
SendMessage (hwndEdit, WM_CLEAR, 0, 0) ;
SendMessage (hwndEdit, WM_PASTE, 0, 0) ;

获取选中文本的起始与终止索引:
SendMessage (hwndEdit, EM_GETSEL, (WPARAM) &iStart,(LPARAM) &iEnd) ;
选中给出的索引的位置的文本:
SendMessage (hwndEdit, EM_SETSEL, iStart,iEnd) ;
代替选中的文本:
SendMessage (hwndEdit, EM_REPLACESEL, 0, (LPARAM) szString) ;
获取多行编辑框的行数:
iCount = SendMessage (hwndEdit, EM_GETLINECOUNT, 0, 0) ;
获取第i行的文本长度:
iLength = SendMessage (hwndEdit, EM_LINELENGTH, iLine, 0) ;
获取第i行文本内容:
iLength = SengMessage (hwndEdit,EM_GETLINE,iLine,(WPARAM)szbuffer);


List Box:

在CreateWindow的ClassName参数为"listbox"就能创建列表框了
Style:LBS_STANDARD==(LBS_NOTIFY | LBS_SORT | WS_VSCROLL | WS_BORDER)
其中LBS_NOTIFY很重要,有它的时候选中list box的item的时候就会给父窗口发送WM_COMMAND,否则之后出错或者焦点问题的时候才会发送WM_COMMAND
LBS_SORT令item按顺序排列

创建完之后就应该插入item:
SendMessage(hwndList,LB_ADDSTRING,0,(LPARAM)szString);
SendMessage(hwndList,LB_INSERTSTING,iIndex,(LPARAM)szString);//list box的索引是最上面那项为0
删除item:
SendMessage(hwndList,LB_DELETESTRING,iIndex,0);//把第iIndex项删除
SendMessage(hwndList,LB_RESETCONTENT,0,0);//清空所有item
获取选中的item:
index=SendMessage(hwndList,LB_GETCURSEL,0,0);
iLength=SendMessage(hwndList,LB_GETTEXTLEN,index,0);//获取index项item的长度
iLength=SendMessage(hwndList,LB_GETTEXT,index,(LPARAM)szbuffer);//把index项的item内容保存到szbuffer中
多选list box:
SendMessage(hwndList,LB_SETSEL,wparam,index);//把index项设为wparam状态,wparam:0->没选中       wparam:1->选中
iStatus=SendMessage(hwndList,LB_GETSEL,index,0);

list box发送的WM_COMMAND:
lparam:句柄        wparam:loword->ID    hiword->通知码
LBN_ERRSPACE
-2
LBN_SELCHANGE
1
LBN_DBLCLK
2
LBN_SELCANCEL
3
LBN_SETFOCUS
4
LBN_KILLFOCUS
5
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值