13. 列表框控件¶
列表框(ListBox)用于提供多个选项,用户可以选择其中一个或者多个条目,但是不能直接编辑列表框的数据。如 图13_1 。
图 13‑1 列表框
13.1. 创建列表框¶
13.1.1. 标准消息类型及参数说明¶
这里使用的消息类型有:WM_CREATE、WM_NOTIFY、WM_CTLCOLOR、WM_DRAWITEM和WM_PAINT。这些消息使用方法都是大同小异,重点说明一下消息WM_NOTIFY。
WM_NOTIFY消息的参数lParam是指向一个NMHDR结构体数据头的地址指针,其成员code存放的是控件发送的事件,可以是列表项被选中(LBN_SELCHANGE),列表项被单击(LBN_CLICKED),成员idFrom表示消息来自于哪个控件,为控件的ID值。
13.1.2. 列表框命令说明¶
列表框没有类似进度条的配置结构体,配置列表框,获取列表框的参数,都是通过发送相应的命令来实现的。下面我们看一下,每一个列表框命令的作用。
表格 13‑1 列表框命令
命令 |
参数lParam |
参数wParam |
具体说明 |
---|---|---|---|
LB_ADDSTRING |
列表项编号 |
显示的字符串 |
设置列表项的内容 |
LB_INSERTSTRING |
列表项编号 |
显示的字符串 |
在某个位置插入列表项 |
LB_DELETESTRING |
列表项编号 |
无 |
删除某个位置的列表项 |
LB_RESETCONTENT |
无 |
无 |
清除所有列表项的内容 |
LB_SETCURSEL |
列表项编号 |
无 |
选中某个列表项 |
LB_GETCURSEL |
无 |
无 |
获取当前选中项 |
LB_GETTEXT |
列表项编号 |
存放的地址,通常使用数组。 |
获取列表项的显示内容 |
LB_GETTEXTLEN |
列表项编号 |
无 |
得到列表项的内容长度 |
LB_GETCOUNT |
列表项编号 |
无 |
得到列表框中的项目数 |
LB_SETPOS |
无 |
偏移值 |
设置列表框的偏移位置 |
LB_GETPOS |
无 |
无 |
得到列表框的偏移值 |
LB_GETTOPINDEX |
无 |
无 |
获取列表框中当前第一项的编号 |
LB_GETITEMRECT |
列表项编号 |
无 |
得到列表项的位置,大小 |
LB_GETITEMDATA |
列表项编号 |
无 |
得到列表项的附加值 |
LB_SETITEMDATA |
列表项编号 |
附加值 |
设置列表项的附加值 |
LB_SETITEMHEIGHT |
列表项编号 |
高度值 |
设置列表项的高度值 |
LB_GETITEMHEIGHT |
列表项编号 |
无 |
获取列表项的高度值 |
注意,列表项编号值都是从零开始,每个列表框的编号都是相互独立,如 图13_2。
图 13‑2 列表项编号
List1,List2都是列表框,item后面的括号代表的是编号值,两个列表框的项目编号都是独立开的。编号值可以理解为当前列表项所在的行数(从0开始计数),当用户设置的索引值大于现有的总行数,便会把这个新的item的索引值强制修正到“总行数-1”的值,也就是在最后面的位置。
图 13_3 列表行号
图13_3,我们要在原有的列表框中,加入新的item,他的行数为10,此时的总行数为7(算上新来的),emXGUI则会将行数修改为“7-1”,使item7插入到列表框的最后面。
13.1.3. 创建列表框控件函数¶
1 2 3 | HWND CreateWindowEx( U32 dwExStyle, LPCVOID lpClass, LPCWSTR lpWindowName,
U32 dwStyle, int x, int y, int nWidth, int nHeight,
HWND hwndParent, UINT WinId,HINSTANCE hInstance,LPVOID lpParam);
|
lpClass:窗口类。列表项控件,这里选择LISTBOX。
dwStyle:列表框的风格。列表框控件支持窗口风格参数,还可以使用以下参数:产生额外的通知码(LBS_NOTIFY)、行与行之间有分界线(LBS_LINE) 和没有可选择项(LBS_NOSEL)。
至于其他的参数,也是同样的用法,这里不作描述。我们创建控件调用的CreateWindow函数,实际上就是CreateWindowEx函数。
13.2. 创建列表框控件实验¶
13.2.2. 代码分析¶
创建父窗口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | void GUI_DEMO_Listbox(void)
{
HWND hwnd;
WNDCLASS wcex;
MSG msg;
InvalidateRect(GetDesktopWindow(),NULL,TRUE);
wcex.Tag = WNDCLASS_TAG;
wcex.Style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = win_proc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = 0;//hInst;
wcex.hIcon = 0;//LoadIcon(hInstance, (LPCTSTR)IDI_WIN32_APP_TEST);
wcex.hCursor = 0;//LoadCursor(NULL, IDC_ARROW);
hwnd =CreateWindowEx( NULL,
&wcex,
_T("GUI Demo - Listbox"),
WS_CAPTION|WS_DLGFRAME|WS_BORDER|WS_CLIPCHILDREN,
0,0,GUI_XSIZE,GUI_YSIZE,
NULL,NULL,NULL,NULL);
ShowWindow(hwnd,SW_SHOW);
while(GetMessage(&msg,hwnd))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
|
创建父窗口,标题栏为“GUI Demo - Listbox”,带有大小边框,设置winProc作为窗口回调函数。
窗口回调函数
WM_CREATE
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | case WM_CREATE:
{
//创建自绘制列表框LISTBOX1
wnd=CreateWindow(LISTBOX,_T("Listbox"),WS_OWNERDRAW|LBS_LINE|LBS_NOTIFY|WS_VISIBLE,
8,120,160,200,hwnd,ID_LISTBOX1,NULL,NULL);
//设置列表项的内容
SendMessage(wnd,LB_ADDSTRING,0,(LPARAM)L" ");
SendMessage(wnd,LB_ADDSTRING,1,(LPARAM)L" ");
SendMessage(wnd,LB_ADDSTRING,2,(LPARAM)L"Item-1-2");
SendMessage(wnd,LB_ADDSTRING,3,(LPARAM)L"Item-1-3");
SendMessage(wnd,LB_ADDSTRING,4,(LPARAM)L"Item-1-4");
SendMessage(wnd,LB_ADDSTRING,5,(LPARAM)L"Item-1-5");
SendMessage(wnd,LB_ADDSTRING,6,(LPARAM)L"Item-1-6");
SendMessage(wnd,LB_ADDSTRING,7,(LPARAM)L"Item-1-7");
SendMessage(wnd,LB_ADDSTRING,8,(LPARAM)L"Item-1-8");
SendMessage(wnd,LB_ADDSTRING,9,(LPARAM)L"Item-1-9");
SendMessage(wnd,LB_ADDSTRING,10,(LPARAM)L"Item-1-10");
//设置LISTBOX1中的列表项行高
for(int i = 0; i < 11; i++)
SendMessage(wnd,LB_SETITEMHEIGHT,i,40);
//创建列表框LISTBOX2
wnd=CreateWindow(LISTBOX,_T("Listbox2"),LBS_LINE|WS_BORDER|WS_VISIBLE,
200,8,100,160,hwnd,ID_LISTBOX2,NULL,NULL);
//设置列表项的内容
SendMessage(wnd,LB_ADDSTRING,0,(LPARAM)L"Item-2-0");
SendMessage(wnd,LB_ADDSTRING,1,(LPARAM)L"Item-2-1");
SendMessage(wnd,LB_ADDSTRING,2,(LPARAM)L"Item-2-2");
SendMessage(wnd,LB_ADDSTRING,3,(LPARAM)L"Item-2-3");
SendMessage(wnd,LB_ADDSTRING,4,(LPARAM)L"Item-2-4");
SendMessage(wnd,LB_ADDSTRING,5,(LPARAM)L"Item-2-5");
SendMessage(wnd,LB_ADDSTRING,6,(LPARAM)L"Item-2-6");
SendMessage(wnd,LB_ADDSTRING,7,(LPARAM)L"Item-2-7");
SendMessage(wnd,LB_ADDSTRING,8,(LPARAM)L"Item-2-8");
//创建列表框LISTBOX3
wnd=CreateWindow(LISTBOX,_T("Listbox3"),LBS_LINE|WS_BORDER|WS_VISIBLE,
8,8,160,100,hwnd,ID_LISTBOX3,NULL,NULL);
//设置列表项的内容
SendMessage(wnd,LB_ADDSTRING,0,(LPARAM)L"Item-3-0");
SendMessage(wnd,LB_ADDSTRING,1,(LPARAM)L"Item-3-1");
SendMessage(wnd,LB_ADDSTRING,2,(LPARAM)L"Item-3-2");
SendMessage(wnd,LB_ADDSTRING,3,(LPARAM)L"Item-3-3");
SendMessage(wnd,LB_ADDSTRING,4,(LPARAM)L"Item-3-4");
SendMessage(wnd,LB_ADDSTRING,5,(LPARAM)L"Item-3-5");
SendMessage(wnd,LB_ADDSTRING,6,(LPARAM)L"Item-3-6");
SendMessage(wnd,LB_ADDSTRING,7,(LPARAM)L"Item-3-7");
SendMessage(wnd,LB_ADDSTRING,8,(LPARAM)L"Item-3-8");
return TRUE;
}
|
创建三个列表框:List1由用户自定义绘制的控件,且可以额外的通知码(LBN_SELCHANGE、LBN_KILLFOCUS和LBN_SETFOCUS),List2和List3是标准控件。发送消息LB_ADDSTRING来设置列表项的内容。发送消息LB_SETITEMHEIGHT来设置LISTBOX 1列表项的高度。List1这里创建了两个显示空白字符的列表项,是为了实现透明效果的,具体见WM_DRAWITEM消息。
WM_CTLCOLOR
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | case WM_CTLCOLOR:
{
u16 id;
id =LOWORD(wParam);
//修改ID_LISTBOX3的颜色
if(id== ID_LISTBOX3)
{
CTLCOLOR *cr;
cr =(CTLCOLOR*)lParam;
cr->TextColor =RGB888(255,255,255);//文字颜色
cr->BackColor =RGB888(0,0,0);//背景颜色
cr->BorderColor =RGB888(50,150,50);//边框颜色
cr->ForeColor =RGB888(0,50,0);//选中框颜色
return TRUE;
}
else
{
return FALSE;
}
}
|
LISTBOX3控件在绘制前,会发送WM_CTLCOLOR到父窗口,通过WM_CTLCOLOR消息来改变控件的颜色值,返回TRUE,否则,系统将忽略本次操作,继续使用默认的颜色进行绘制。
WM_DRAWITEM
1 2 3 4 5 6 7 8 9 10 11 12 13 | case WM_DRAWITEM:
{
DRAWITEM_HDR *ds;
ds =(DRAWITEM_HDR*)lParam;
if(wParam==ID_LISTBOX1)
{
_listbox_owner_draw_x(ds);
return TRUE;
}
return FALSE;
}
|
WM_DRAWITEM消息里面负责对List Box1实现重绘。调用函数_listbox_owner_draw_x,来实现,见 代码清单13_6。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | static void _listbox_owner_draw_x(DRAWITEM_HDR *ds)
{
HWND hwnd;
HDC hdc,hdc0,hdc1;
HDC hdc_mem;
RECT rc;
int x,y,w,h;
hwnd =ds->hwnd;
hdc =ds->hDC;
//创建三个一样大小的DC,把listbox分别绘制进去,但颜色参数不同的.
hdc_mem = CreateMemoryDC(SURF_SCREEN,ds->rc.w,ds->rc.h); //透明图层
hdc0 =CreateMemoryDC(SURF_SCREEN,ds->rc.w,ds->rc.h); //缩小图层
hdc1 =CreateMemoryDC(SURF_SCREEN,ds->rc.w,ds->rc.h); //放大图层
//绘制与窗口背景颜色一样的矩形(实现透明)
SetBrushColor(hdc_mem,MapRGB(hdc_mem,207,212,215));
FillRect(hdc_mem,&ds->rc);
//一个listbox绘到hdc0中
_draw_listbox(hdc0,hwnd,RGB888(100,149,237),RGB888(250,0,0),hDefaultFont);
//
//一个listbox绘到hdc1中(图片的第三部分)
_draw_listbox(hdc1,hwnd,RGB888(0,0,0),RGB888(250,0,0),hZoomFont);
//获取列表框中的第二行的高度
SendMessage(hwnd,LB_GETITEMRECT,2,(LPARAM)&rc);
//第一步/
//将透明部分从hdc_mem里复制出来.
x =0;
y =0;
w =rc_m.w;
h =rc.y;
BitBlt(hdc,x,y,w,h,hdc_mem,x,y,SRCCOPY);
//第二步/
//将缩小部分从hdc0里复制出来.
x = 0;
y = rc.y;
w = rc_m.w;
h = rc_m.y - rc.y;
BitBlt(hdc,x,y,w,h,hdc0,x,y,SRCCOPY);
//第三步/
//中间矩形部分从hdc1里复制出来.
BitBlt(hdc,rc_m.x,rc_m.y,rc_m.w,rc_m.h,hdc1,rc_m.x,rc_m.y,SRCCOPY);
//第四步/
//下面的矩形部分从hdc0里复制出来.
x =0;
y =rc_m.y+rc_m.h;
w =rc_m.w;
h =ds->rc.h-(rc_m.y+rc_m.h);
BitBlt(hdc,x,y,w,h,hdc0,x,y,SRCCOPY);
//释放内存
DeleteDC(hdc0);
DeleteDC(hdc1);
DeleteDC(hdc_mem);
}
|
下面我们重点讲解这个函数,只有学会这个函数里面的操作,未来才有可能在emXGUI中“为所欲为”。
在PS里面,有一个叫做图层的概念。简单地说,每一个图层都是一张独立的图像,每个图层依次取图像的一部分内容,将这些内容按顺序叠加起来,便可以看到完整的图像,参考 图13_5。
图 13‑5 合成流程
当列表项经过白色区域时,颜色会改变,且它的字体会变大,而其他的列表项字体颜色和大小不发生改变。
1处:画个和背景颜色一样的图层,来实现透明的效果;
2、3处:绘制一个红色背景的矩形区域,在矩形的高度的1/4处绘制一个白色区域(选择框),采用淡蓝色的小字体来显示列表项内容。
4处:绘制一个与处一模一样的内容,在列表项内容显示的格式上,采用黑色的大字体。
依次取上述~处的内容,则可以合成最后的图画,以上就是理论部分的实现。在emXGUI中,内存型DC就相当于我们所说的图层,调用CreateMemoryDC函数来创建图层,见 代码清单13_7。
1 | HDC CreateMemoryDC(SURF_FORMAT Format,int nWidth,int nHeight);
|
Format:颜色的格式,可以是屏幕颜色一致(SURF_SCREEN)、ARGB4444格式(SURF_ARGB4444)、ARGB8888格式(SURF_ARGB8888)。使用的时候,不需要修改底层驱动。
nWidth、nHeight:创建DC的大小。创建内存型DC,实际上就是在我们的内存中开辟一块区域,用来绘制图形的。这块区域的起始位置一定是(0,0),我们只需要负责定义这块区域的大小即可。
切记,在使用后,需要使用DeleteDC,来释放掉内存型DC。
在 代码清单13_6 中,创建了三个内存型DC,分别对应了 图13_5 的图片(从左到右)。
调用_draw_listbox函数来绘制列表框的外观,包括矩形区域,白色选择框以及字体,见 代码清单13_8 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | static void _draw_listbox(HDC hdc,HWND hwnd,COLOR_RGB32 text_c,COLOR_RGB32 back_c,HFONT hFont)
{
RECT rc,rc_cli;
int i,count;
WCHAR wbuf[128];
GetClientRect(hwnd,&rc_cli);
SetBrushColor(hdc,MapRGB888(hdc,back_c));
FillRect(hdc,&rc_cli);
//定义一个中间的矩形.
rc_m.w =rc_cli.w;
rc_m.h =40;
rc_m.x =0;
rc_m.y =(rc_cli.h-rc_m.h)>>1;
//中间框绘制到hdc1中.
SetBrushColor(hdc,MapRGB(hdc,255,255,255));
FillRect(hdc,&rc_m);
SetFont(hdc,hFont);
SetTextColor(hdc,MapRGB888(hdc,text_c));
i=SendMessage(hwnd,LB_GETTOPINDEX,0,0);
count=SendMessage(hwnd,LB_GETCOUNT,0,0);
while(i<count)
{
SendMessage(hwnd,LB_GETITEMRECT,i,(LPARAM)&rc);
if(rc.y > rc_cli.h)
{
break;
}
SendMessage(hwnd,LB_GETTEXT,i,(LPARAM)wbuf);
DrawText(hdc,wbuf,-1,&rc,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
i++;
}
}
|
_draw_listbox函数的形参为:窗口的绘图DC,父窗口,文字颜色,背景颜色以及使用的字体。
使用LB_GETTOPINDEX来获取处于当前列表框的第一项,注意,这里的第一项不是我们自己定义的第一项,是列表框滑动后,显示在最开始位置的那一项。
发送LB_GETCOUNT来获取列表框的总行数。如果第一项的值小于总行数,则对第一项及其后面的N项进行重绘。
例程中,通过发送LB_GETITEMRECT来获取每一个列表项的位置以及大小,并在这个区域内,使用DrawText函数来显示文字。
到这里,就完成了放大图层和缩小图层的绘制,也就是图 13‑5的第二幅和第三幅小图。
第一幅小图的实现,是通过调用SetBrushColor函数设置矩形的颜色,与窗口的颜色一致,这里的窗口颜色为RGB(207,212,215),调用FillRect函数来填充矩形。
以上操作,就将三个图层的内容绘制完毕。至于如何每个图层显示的内容,请看下面讲解。
图 13_6 合成图的放大版
图13_6,是合成图层的放大版。处是item2的x,y坐标。处是选择框的x,y坐标,处的坐标是(0,选择框的y坐标+选择框的高度)。
有了这三个坐标,我们就可以对每一个图层进行切割合成了。在emXGUI中,使用BitBlt函数来实现图像的切割合成,函数的具体使用方法,可以参考《emXGUI API编程手册》章节:绘图API。
对于透明的图层,我们只需要顶部到item处的区域即可,也就是起点为(0,0),高度为item2的y坐标,宽度则为控件的宽度,见 代码清单13_9。
1 2 3 4 5 6 7 8 9 | //获取列表框中的第二行的高度
SendMessage(hwnd,LB_GETITEMRECT,2,(LPARAM)&rc);
//第一步/
//将透明部分从hdc_mem里复制出来.
x =0;
y =0;
w =rc_m.w;
h =rc.y;
BitBlt(hdc,x,y,w,h,hdc_mem,x,y,SRCCOPY);
|
使用LB_GETITEMRECT消息来获取Item2的位置以及大小。使用BitBlt函数,将透明图层(hdc_mem)的内容,复制到合成图层中(hdc)。这里的w,h限制了截取的大小,是否就相等于切割的过程。将hdc_mem的内容复制到合成图层中,则是合成的过程。请注意,合成图层和透明图像的x,y坐 标需要是一致的,否则就会产生错位,读者可以脑补一下画面。
~之间的区域:起始位置为(item2的x坐标,item2的y坐标),大小为控件的宽度*(白色选择框的y坐标减去item2的y坐标)和处的区域:起始位置为(白色选择框的x坐标, 白色选择框的y坐标),大小为控件的宽度*(控件的高度减去白色选择框的y坐标),就是缩小图层的内容,见 代码清单13_10。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //第二步/
//将缩小部分从hdc0里复制出来.
x = 0;
y = rc.y;
w = rc_m.w;
h = rc_m.y - rc.y;
BitBlt(hdc,x,y,w,h,hdc0,x,y,SRCCOPY);
//第四步/
//下面的矩形部分从hdc0里复制出来.
x =0;
y =rc_m.y+rc_m.h;
w =rc_m.w;
h =ds->rc.h-(rc_m.y+rc_m.h);
BitBlt(hdc,x,y,w,h,hdc0,x,y,SRCCOPY);
|
上述代码将~之间的区域和处的区域提取出来,并赋值到合成图层(hdc)中。
最后,将放大图层的选择框区域提取出来,就可以得到我们的放大图像了,见 代码清单13_11。
1 2 3 | //第三步/
//中间矩形部分从hdc1里复制出来.
BitBlt(hdc,rc_m.x,rc_m.y,rc_m.w,rc_m.h,hdc1,rc_m.x,rc_m.y,SRCCOPY);
|
这样窗口就设计完成了,将GUI_DEMO_Listbox函数加入到GUI_AppMain中,见 代码清单13_12。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void GUI_AppMain(void)
{
while(1)
{
GUI_DEMO_Button();
GUI_DEMO_Checkbox();
GUI_DEMO_Radiobox();
GUI_DEMO_Textbox();
GUI_DEMO_Progressbar();
GUI_DEMO_Scrollbar();
GUI_DEMO_Listbox();
}
}
|