2007年6月14日木曜日

TCP的socket连接示例

Made In Zeal 转载请保留原始链接:http://www.zeali.net/entry/13
标签 ( Tags ): socket , tcp , timeout , 超时 , 异步 , ioctl , 源代码
用C实现的TCP socket连接/读/写操作。采用fcntl设置非阻塞式连接以实现connect超时处理;采用select方法来设置socket读写超时。此示例可被编译运行于Windows/unix系统。

源文件connector.c

原来的代码在windows下编译不通过,今天qzj问起才发现。因为加了异步的处理,没有对这部分代码进行兼容性处理。本着做学问一丝不苟嘀精神,重新修改了一下源代码。以下代码在VC++6和linux下编译执行通过 :)

view plainprint?

1. /*
2.
3. * on Unix:
4.
5. * cc -c connector.c
6.
7. * cc -o connector connector.o
8.
9. *
10.
11. * on Windows NT:
12.
13. * open connector.c in Visual Studio
14.
15. * press 'F7' to link -- a project to be created
16.
17. * add wsock32.lib to the link section under project setting
18.
19. * press 'F7' again
20.
21. *
22.
23. * running:
24.
25. * type 'connector' for usage
26.
27. */
28.
29.
30. #include
31.
32. #include
33.
34. #include
35.
36. #include
37.
38. #include
39.
40. #include
41.
42. #ifdef WIN32
43.
44. #include
45.
46. #else
47.
48. #include
49.
50. #include
51.
52. #include
53.
54. #include
55.
56. #include
57.
58. #include
59.
60. #include
61.
62. #endif
63.
64.
65. #ifndef INADDR_NONE
66.
67. #define INADDR_NONE 0xffffffff
68.
69. #endif
70.
71. #define MAX_STRING_LEN 1024
72.
73. #define BUFSIZE 2048
74.
75.
76. #ifndef WIN32
77.
78. #define SOCKET int
79.
80. #else
81.
82. #define errno WSAGetLastError()
83.
84. #define close(a) closesocket(a)
85.
86. #define write(a, b, c) send(a, b, c, 0)
87.
88. #define read(a, b, c) recv(a, b, c, 0)
89.
90. #endif
91.
92.
93. char buf[BUFSIZE];
94.
95.
96. static char i_host[MAX_STRING_LEN]; /* site name */
97.
98. static char i_port[MAX_STRING_LEN]; /* port number */
99.
100.
101. void err_doit(int errnoflag, const char *fmt, va_list ap);
102.
103. void err_quit(const char *fmt, ...);
104.
105. int tcp_connect(const char *host, const unsigned short port);
106.
107. void print_usage();
108.
109.
110. //xnet_select x defines
111.
112. #define READ_STATUS 0
113.
114. #define WRITE_STATUS 1
115.
116. #define EXCPT_STATUS 2
117.
118.
119. /*
120.
121. s - SOCKET
122.
123. sec - timeout seconds
124.
125. usec - timeout microseconds
126.
127. x - select status
128.
129. */
130.
131. SOCKET xnet_select(SOCKET s, int sec, int usec, short x)
132.
133. {
134.
135. int st = errno;
136.
137. struct timeval to;
138.
139. fd_set fs;
140.
141. to.tv_sec = sec;
142.
143. to.tv_usec = usec;
144.
145. FD_ZERO(&fs);
146.
147. FD_SET(s, &fs);
148.
149. switch(x){
150.
151. case READ_STATUS:
152.
153. st = select(s+1, &fs, 0, 0, &to);
154.
155. break;
156.
157. case WRITE_STATUS:
158.
159. st = select(s+1, 0, &fs, 0, &to);
160.
161. break;
162.
163. case EXCPT_STATUS:
164.
165. st = select(s+1, 0, 0, &fs, &to);
166.
167. break;
168.
169. }
170.
171. return(st);
172.
173. }
174.
175.
176. int tcp_connect(const char *host, const unsigned short port)
177.
178. {
179.
180. unsigned long non_blocking = 1;
181.
182. unsigned long blocking = 0;
183.
184. int ret = 0;
185.
186. char * transport = "tcp";
187.
188. struct hostent *phe; /* pointer to host information entry */
189.
190. struct protoent *ppe; /* pointer to protocol information entry*/
191.
192. struct sockaddr_in sin; /* an Internet endpoint address */
193.
194. SOCKET s; /* socket descriptor and socket type */
195.
196. int error;
197.
198.
199. #ifdef WIN32
200.
201. {
202.
203. WORD wVersionRequested;
204.
205. WSADATA wsaData;
206.
207. int err;
208.
209.
210.
211. wVersionRequested = MAKEWORD( 2, 0 );
212.
213.
214.
215. err = WSAStartup( wVersionRequested, &wsaData );
216.
217. if ( err != 0 ) {
218.
219. /* Tell the user that we couldn't find a usable */
220.
221. /* WinSock DLL. */
222.
223. printf("can't initialize socket library\n");
224.
225. exit(0);
226.
227. }
228.
229. }
230.
231. #endif
232.
233.
234.
235. memset(&sin, 0, sizeof(sin));
236.
237. sin.sin_family = AF_INET;
238.
239.
240.
241. if ((sin.sin_port = htons(port)) == 0)
242.
243. err_quit("invalid port \"%d\"\n", port);
244.
245.
246.
247. /* Map host name to IP address, allowing for dotted decimal */
248.
249. if ( phe = gethostbyname(host) )
250.
251. memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
252.
253. else if ( (sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE )
254.
255. err_quit("can't get \"%s\" host entry\n", host);
256.
257.
258.
259. /* Map transport protocol name to protocol number */
260.
261. if ( (ppe = getprotobyname(transport)) == 0)
262.
263. err_quit("can't get \"%s\" protocol entry\n", transport);
264.
265.
266.
267. /* Allocate a socket */
268.
269. s = socket(PF_INET, SOCK_STREAM, ppe->p_proto);
270.
271. if (s < 0)
272.
273. err_quit("can't create socket: %s\n", strerror(errno));
274.
275.
276.
277. /* Connect the socket with timeout */
278.
279. #ifdef WIN32
280.
281. ioctlsocket(s,FIONBIO,&non_blocking);
282.
283. #else
284.
285. ioctl(s,FIONBIO,&non_blocking);
286.
287. #endif
288.
289. //fcntl(s,F_SETFL, O_NONBLOCK);
290.
291. if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == -1){
292.
293. struct timeval tv;
294.
295. fd_set writefds;
296.
297. // 设置连接超时时间
298.
299. tv.tv_sec = 10; // 秒数
300.
301. tv.tv_usec = 0; // 毫秒
302.
303. FD_ZERO(&writefds);
304.
305. FD_SET(s, &writefds);
306.
307. if(select(s+1,NULL,&writefds,NULL,&tv) != 0){
308.
309. if(FD_ISSET(s,&writefds)){
310.
311. int len=sizeof(error);
312.
313. //下面的一句一定要,主要针对防火墙
314.
315. if(getsockopt(s, SOL_SOCKET, SO_ERROR, (char *)&error, &len) < 0)
316.
317. goto error_ret;
318.
319. if(error != 0)
320.
321. goto error_ret;
322.
323. }
324.
325. else
326.
327. goto error_ret; //timeout or error happen
328.
329. }
330.
331. else goto error_ret; ;
332.
333.
334. #ifdef WIN32
335.
336. ioctlsocket(s,FIONBIO,&blocking);
337.
338. #else
339.
340. ioctl(s,FIONBIO,&blocking);
341.
342. #endif
343.
344.
345. }
346.
347. else{
348.
349. error_ret:
350.
351. close(s);
352.
353. err_quit("can't connect to %s:%d\n", host, port);
354.
355. }
356.
357. return s;
358.
359. }
360.
361.
362. void err_doit(int errnoflag, const char *fmt, va_list ap)
363.
364. {
365.
366. int errno_save;
367.
368. char buf[MAX_STRING_LEN];
369.
370.
371. errno_save = errno;
372.
373. vsprintf(buf, fmt, ap);
374.
375. if (errnoflag)
376.
377. sprintf(buf + strlen(buf), ": %s", strerror(errno_save));
378.
379. strcat(buf, "\n");
380.
381. fflush(stdout);
382.
383. fputs(buf, stderr);
384.
385. fflush(NULL);
386.
387. return;
388.
389. }
390.
391.
392. /* Print a message and terminate. */
393.
394. void err_quit(const char *fmt, ...)
395.
396. {
397.
398. va_list ap;
399.
400. va_start(ap, fmt);
401.
402. err_doit(0, fmt, ap);
403.
404. va_end(ap);
405.
406. exit(1);
407.
408. }
409.
410.
411. #ifdef WIN32
412.
413. char *optarg;
414.
415.
416. char getopt(int c, char *v[], char *opts)
417.
418. {
419.
420. static int now = 1;
421.
422. char *p;
423.
424.
425. if (now >= c) return EOF;
426.
427.
428. if (v[now][0] == '-' && (p = strchr(opts, v[now][1]))) {
429.
430. optarg = v[now+1];
431.
432. now +=2;
433.
434. return *p;
435.
436. }
437.
438.
439. return EOF;
440.
441. }
442.
443.
444. #else
445.
446. extern char *optarg;
447.
448. #endif
449.
450.
451. #define required(a) if (!a) { return -1; }
452.
453.
454. int init(int argc, char *argv[])
455.
456. {
457.
458. char c;
459.
460. //int i,optlen;
461.
462. //int slashcnt;
463.
464.
465. i_host[0] = '\0';
466.
467. i_port[0] = '\0';
468.
469.
470. while ((c = getopt(argc, argv, "h:p:?")) != EOF) {
471.
472. if (c == '?')
473.
474. return -1;
475.
476. switch (c) {
477.
478. case 'h':
479.
480. required(optarg);
481.
482. strcpy(i_host, optarg);
483.
484. break;
485.
486. case 'p':
487.
488. required(optarg);
489.
490. strcpy(i_port, optarg);
491.
492. break;
493.
494. default:
495.
496. return -1;
497.
498. }
499.
500. }
501.
502.
503. /*
504.
505. * there is no default value for hostname, port number,
506.
507. * password or uri
508.
509. */
510.
511. if (i_host[0] == '\0' || i_port[0] == '\0')
512.
513. return -1;
514.
515.
516. return 1;
517.
518. }
519.
520.
521. void print_usage()
522.
523. {
524.
525. char *usage[] =
526.
527. {
528.
529. "Usage:",
530.
531. " -h host name",
532.
533. " -p port",
534.
535. "example:",
536.
537. " -h 127.0.0.1 -p 4001",
538.
539. };
540.
541. int i;
542.
543.
544. for (i = 0; i < sizeof(usage) / sizeof(char*); i++)
545.
546. printf("%s\n", usage[i]);
547.
548.
549.
550. return;
551.
552. }
553.
554.
555. int main(int argc, char *argv[])
556.
557. {
558.
559. SOCKET fd;
560.
561. int n;
562.
563.
564. /* parse command line etc ... */
565.
566. if (init(argc, argv) < 0) {
567.
568. print_usage();
569.
570. exit(1);
571.
572. }
573.
574.
575. buf[0] = '\0';
576.
577.
578. /* pack the info into the buffer */
579.
580. strcpy(buf, "HelloWorld");
581.
582.
583. /* make connection to the server */
584.
585. fd = tcp_connect(i_host, (unsigned short)atoi(i_port));
586.
587.
588. if(xnet_select(fd, 0, 500, WRITE_STATUS)>0){
589.
590. /* send off the message */
591.
592. write(fd, buf, strlen(buf));
593.
594. }
595.
596. else{
597.
598. err_quit("Socket I/O Write Timeout %s:%s\n", i_host, i_port);
599.
600. }
601.
602.
603. if(xnet_select(fd, 3, 0, READ_STATUS)>0){
604.
605. /* display the server response */
606.
607. printf("Server response:\n");
608.
609. n = read(fd, buf, BUFSIZE);
610.
611. buf[n] = '\0';
612.
613. printf("%s\n", buf);
614.
615. }
616.
617. else{
618.
619. err_quit("Socket I/O Read Timeout %s:%s\n", i_host, i_port);
620.
621. }
622.
623. close(fd);
624.
625.
626. #ifdef WIN32
627.
628. WSACleanup();
629.
630. #endif
631.
632.
633. return 0;
634.
635. }

2007年5月26日土曜日

inux Programming: 编写一个简单的Shell命令解释程序

这个版本的Shell支持管道功能和I/O重定向.
下一个版本将增加对内建(buildins)命令的支持,如if ..then else等控制语句的支持.
-------------CODE-------------------

#include
#include
#include
#include
#include
#define OPEN_MAX 10
#define MAXNAME 100
#define FALSE 0
#define TRUE 1
char *readcmd( FILE *fp ); //读入命令行字符串
char **getarg(char *argv); //把命令行字符串分解成字符数组指针
void getfile(char *name); //获得重定向文件名
void freearg(char **arg); //释放内存
char *buf; //存放用户输入的字符串
char *p; //指向buf的指针
int cmdnum; //记录命令个数
int pipefd[2]; //保存管道文件描述符
int wait_pid; //记录父进程PID
char infile[MAXNAME+1]; //输入重定向文件
char outfile[MAXNAME+1]; //输出重定向文件
int background; //记录命令是否在后台执行
int append; //记录是否写入文件末尾
struct cmd{
char ** argv; //命令行参数
int infd; //输入文件描述符
int outfd; // 输出文件描述符
}cmdline[BUFSIZ]; //命令行结构数组
int main()
{
pid_t pid;
int status;
int i, fd;
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_IGN); //处理中断(Ctrl-C)和退出(Ctrl-\)信号
printf("%% ");


while( (buf = readcmd(stdin)) != NULL ){
//-----------初始化-----------------
int k;
cmdnum = 0;
background = FALSE;
infile[0] = '\0';
outfile[0] = '\0';
append = FALSE;
for( k = 0; k < OPEN_MAX; k++ )
{
cmdline[k].infd = 0;
cmdline[k].outfd = 1;
}
for( k = 3; k < OPEN_MAX; k++ )
close(k);
fflush(stdout); //清空输出缓冲区
p = buf;
//-----------计算命令个数并获得命令参数----------
for(i = 0; i <= cmdnum; i++)
cmdline[i].argv = getarg(buf);

if( infile[0] != '\0' )
cmdline[0].infd = open(infile, O_RDONLY);
if( outfile[0] != '\0' )
if( append == FALSE )
cmdline[cmdnum].outfd = open(outfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
else
cmdline[cmdnum].outfd = open(outfile, O_WRONLY | O_CREAT | O_APPEND, 0666);
if( background == TRUE )
signal(SIGCHLD, SIG_IGN);
else
signal(SIGCHLD, SIG_DFL);
//-----------执行命令行-------------
for(i = 0; i <= cmdnum; i++)
{
//-----------处理管道-------------
if( i < cmdnum )
{
pipe(pipefd);
cmdline[i+1].infd = pipefd[0];
cmdline[i].outfd = pipefd[1];
}



if ( pid = fork() )
wait_pid = pid;
else if ( pid == 0 ){
if( cmdline[i].infd == 0 && background == TRUE )
cmdline[i].infd = open("/dev/null", O_RDONLY);
if(cmdline[i].infd != 0)
{
close(0);
dup(cmdline[i].infd);

}
if(cmdline[i].outfd != 1)
{
close(1);
dup(cmdline[i].outfd);

}

if( background == FALSE )
{
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
}
for( k = 3; k < OPEN_MAX; ++k)
close(k);
execvp(cmdline[i].argv[0], cmdline[i].argv);

}
if(fd = cmdline[i].infd)
close(fd);
if((fd = cmdline[i].outfd) != 1)
close(fd);
}
if( background == FALSE )
while ( waitpid(pid, &status, 0) != wait_pid )
;

printf("%% ");
//-------释放内存-----------
for(i = 0; i <= cmdnum; i++)
freearg(cmdline[i].argv);
free(buf);
}

exit(0);
}
void freearg(char **arg)
{
char **cp = arg;
while(*cp)
free(*cp++);
free(arg);
}
char *readcmd(FILE *fp)
{
char *buf;
int buflen = 0;
int i = 0;
int c;

while( (c = getc(fp)) != EOF ){
if( i +1 >= buflen ){
if( buflen == 0 ){
buf = (char*) malloc( BUFSIZ );
if( buf == NULL )
return NULL;
}
else{
buf = (char *)realloc(buf, buflen + BUFSIZ);
if( buf == NULL )
return NULL;
}
buflen += BUFSIZ;
}
if ( c == '\n' )
break;
buf[i++] = c;
}
if( c == EOF && i == 0 )
return NULL;
buf[i] = '\0';
return buf;
}
char **getarg(char * argv)
{
char **args;
if( (args = (void *)malloc(BUFSIZ)) == NULL )
{
printf("out of memory\n");
return NULL;
}

int bufsize = sizeof(argv)/sizeof(argv[0]);
char argbuf[bufsize];
char *cp = argbuf;
char *end;
char *start = argbuf;
char * tempstr;
int len;
int i = 0;
while( *p != '\0' )

{
while( *p == ' ' || *p == '\t' )
p++; //去掉多余的空格
if( *p == '\0' )
break;
if( *p == '|') //管道,使命令数加1
{
if( *(p+1) == ' ' )
p +=2; //跳过空格
else
p++;
cmdnum++;
break;
}
if( *p == '<' ) //输入重定向
{
if( *(p+1) == ' ' )
p +=2;
else
p++;
getfile(infile);
break;
}
if( *p == '>' ) //输出重定向
{
if( *(p+1) == ' ' )
p +=2;
else
p++;
if( *p == '>' ) //追加到文件末尾
{
if( *(p+1) == ' ' )
p +=2;
else
p++;
append = TRUE;
}
getfile(outfile);
break;
}
if( *p == '&' ) //后台执行命令
{
if( *(p+1) == ' ' )
p +=2;
else
p++;
background = TRUE;
break;
}
*cp++ = *p++;
if( *p == ' ' || *p == '\0' )
{
*cp = '\0';
end = cp;
len = end - start;
if( (tempstr = (char *)malloc(len + 1)) == NULL )
{
printf("out of memory\n");
return NULL;
}
strncpy(tempstr, start, len);
tempstr[len] = '\0';
if( i + 1 > BUFSIZ )
{
if( (args = (void *)realloc(args, BUFSIZ * 2)) == NULL )
{
printf("out of memory\n");
return NULL;
}
}
args[i++] = tempstr;
start = end;
if( *p == '\0')
break;
p++;
}
}
args[i] = NULL;

return args;
}
void getfile(char *name)
{
int i;
for( i = 0; i < MAXNAME; ++i )
{
if( *p == ' ' || *p == '|' || *p == '>' || *p == '\0' ||
*p == '<' || *p == '&' || *p == '\t' )
{
*name = '\0';
return;
}
else
*name++ = *p++;

}
*name = '\0';
}

树控件的创建

MSDN上说,在MFC中树控件有三种创建方法:

1. 在对话框模版中添加一个TreeBox控件(假设其ID 为:IDR_TREE), 则自动生成一个树控件对象。通过在对话框类中的OnInitDlg()函数中, 使用CTreeCtrl *pTree = (CTreeCtrl *) GetDlgItem(IDR_TREE); 将得到CTreeCtrl对象的指针, 使用此指针就可以对树控件进行操作。

另外, 对树控件的消息以及消息处理函数都是在对话框类中添加。

2. 使用CTreeView。 这样AppWzard将自动创建一个相关的CTreeCtrl 对象。

3. 使用CTreeCtrl::Create()函数创建。



void CTreeCtrl::Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID )



其中dwStyle使用以下树控件专用的风格:

TVS_HASLINES 在父/子结点之间绘制连线

TVS_LINESATROOT 在根/子结点之间绘制连线

TVS_HASBUTTONS 在每一个结点前添加一个按钮,用于表示当前结点是否已被展开

TVS_EDITLABELS 结点的显示字符可以被编辑

TVS_SHOWSELALWAYS 在失去焦点时也显示当前选中的结点

TVS_DISABLEDRAGDROP 不允许Drag/Drop

TVS_NOTOOLTIPS 不使用ToolTip显示结点的显示字符

TVS_CHECKBOXES 在每项旁边添加一个选择框



此外, dwStyle在树控件创建时一定要加上WS_VISIBLE , 否则树控件将会被隐藏!!当然,如果没有加上这个WS_VISIBLE也可以在创建之后立即调用ShowWindow(SW_SHOWNORMAL),控件便显示出来了,若调用ShowWindow(SW_HIDE)控件便隐藏了。同时,一般情况下,树控件是作为子窗口使用,那么在创建时要加上WS_CHILD,表示控件是子窗口(那么在创建时当然要指定父窗口了)。

nID可以随便定义一个和其他ID值不冲突的数字便于标识(当然也可以直接用数字,这里用的是IDD_TREE)。 而pParentWnd则指向树控件的父窗口, 在对话框中是对话框类对象的指针, 在视图中, 是视图类对象的指针(这里使用的是this指针)。 我在视图类CScrollView中的OninitialUpdate()函数中加入一个树控件:

m_tree.Create(WS_VISIBLE | WS_TABSTOP | WS_CHILD | WS_BORDER

| TVS_HASBUTTONS | TVS_LINESATROOT | TVS_HASLINES

| TVS_DISABLEDRAGDROP, CRect(0,0,100,100),this,IDD_TREE);



HTREEITEM hItem,hSubItem;



hItem = m_tree.InsertItem(_T("Parent1"),TVI_ROOT);

//在根结点上添加Parent1



hSubItem = m_tree.InsertItem(_T("Child1_1"),hItem);

//在Parent1上添加一个子结点



hSubItem = m_tree.InsertItem(_T("Child1_2"),hItem,hSubItem);

//在Parent1上添加一个子结点,排在Child1_1后面



hSubItem = m_tree.InsertItem(_T("Child1_3"),hItem,hSubItem);



hItem = m_tree.InsertItem(_T("Parent2"),TVI_ROOT,hItem);

hItem = m_tree.InsertItem(_T("Parent3"),TVI_ROOT,hItem);



其中的m_tree在CMyScrollView头文件中声明为:

Private:

CTreeCtrl m_tree;





注意: 一定要在头文件中声明一个树控件类的对象, 不能在函数中声明(表示对象是临时的), 如果在函数中声明了类对象, 那么在函数返回后该对象会被消除, 这样就无法显示树控件了!!!



父窗口指定为this指针,即CMyScrollView指针,表示树控件将会向它发送命令消息(WM_COMMAND)和通知消息(WM_NOTIFY)。那么所有的树控件处理消息都将在这个父窗口进行处理。(在对话框中的情形相同)

例如:为了能对树控件进行操作, 必须添加消息和消息处理函数。 此时由于是用Create()函数创建的树控件, 无法用AppWzard 为它产生消息以及消息处理函数, 必须手动添加。

例如:在CMyScrollView的头文件中声明: (用于处理双击树控件项事件)



afx_msg void OnTreeDblClk(NMHDR * pNMHDR, LRESULT *pResult);



在CMyScrollView的实现文件中添加:

BEGIN_MESSAGE_MAP(CMyScrollView, CScrollView)

…。

…。

ON_NOTIFY(NM_DBLCLK, IDD_TREE, OnTreeDblClk)

…。

…。

END_MESSAGE_MAP()



void CMyScrollView::OnTreeDblClk(NMHDR* pNMHDR, LRESULT *pResult)

{

HTREEITEM hSelected = m_tree.GetSelectedItem();

if (hSelected != NULL) {



if(m_tree.GetItemText(hSelected) == "Child1_1"){

AfxMessageBox(_T("This Child1_1 is Clicked!"));

}



}



*pResult = 0;

}

树控件的结点插入

树控件结点的插入使用CTreeCtrl::InsertItem()函数。

HTREEITEM InsertItem( LPTVINSERTSTRUCT lpInsertStruct );



HTREEITEM InsertItem(UINT nMask, LPCTSTR lpszItem, int nImage, int nSelectedImage, UINT nState, UINT nStateMask, LPARAM lParam, HTREEITEM hParent, HTREEITEM hInsertAfter );



HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );



HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);





一般情况下使用最后两种形式,一种是用于插入结点,另一种增加了对图标的插入操作。

树控件可以用于树结构,其中有一个根结点(Root)然后下面有许多子结点,而每个子结点上允许有一个或多个或没有子结点。在树控件中每一个结点都有一个句柄(HTREEITEM),同时添加结点时必须提供的参数是该结点的父结点句柄,(其中根Root结点只有一个,既不可以添加也不可以删除)利用

HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );可以添加一个结点,pszItem为显示的字符,hParent代表父结点的句柄,当前添加的结点会排在hInsertAfter表示的结点的后面,返回值为当前创建的结点的句柄。如例子中,在调用了Create()函数之后进行了结点的插入操作它生成了如下形式的树结构:

+--- Parent1

+--- Child1_1

+--- Child1_2

+--- Child1_3

+--- Parent2

+--- Parent3



如果你希望在每个结点前添加一个小图像,就必需先调用CTreeCtrl:: SetImageList( CImageList * pImageList, int nImageListType );指明当前所使用的ImageList,nImageListType为TVSIL_NORMAL。在调用完成后控件中使用图片以设置的ImageList中图片为准。然后调用

HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);添加结点,nImage为结点没被选中时所使用图片序号,nSelectedImage为结点被选中时所使用图片序号。下面的代码演示了ImageList的设置。

/*m_list 为CImageList对象IDB_TREE 为16*(16*4)的位图,每个图片为16*16共4个图标*/

m_list.Create(IDB_TREE,16, 4, RGB(0,0,0));

m_tree.SetImageList(&m_list,TVSIL_NORMAL);

m_tree.InsertItem("Parent1" , 0, 1);//添加,选中时显示图像1,未选中时显示图像0





添加结点最复杂的函数是前两个,它们使用了各种结构以及字段值来标识结点所处的状态和属性。

第一个函数:

HTREEITEM InsertItem( LPTVINSERTSTRUCT lpInsertStruct );

要求传入一个TVINSERTSTRUCT结构的指针。它的定义为:(包括两个结点句柄,一个描述树控件结点的结构)



//TVINSERTSTRUCT





typedef struct tagTVINSERTSTRUCT {

HTREEITEM hParent; //父结点句柄

HTREEITEM hInsertAfter; //插入其后的结点句柄

#if (_WIN32_IE >= 0x0400)

union

{

TVITEMEX itemex;

TVITEM item;

} DUMMYUNIONNAME;

#else

TVITEM item; //描述树控件结点的结构

#endif

} TVINSERTSTRUCT, FAR *LPTVINSERTSTRUCT;





而TVITEM定义为:

//TVITEM





typedef struct tagTVITEM{

UINT mask;

HTREEITEM hItem;

UINT state;

UINT stateMask;

LPTSTR pszText;

int cchTextMax;

int iImage;

int iSelectedImage;

int cChildren;

LPARAM lParam;

} TVITEM, FAR *LPTVITEM;



mask

一组标志位,表明哪个成员数据是有效的。当此结构用于TVM_GETITEM消息时,mask成员表示获得的项属性。它可以是以下值中的一个或多个组合:

TVIF_CHILDREN :cChildren 成员有效;

TVIF_HANDLE :hItem 成员有效;

TVIF_IMAGE :iImage 成员有效;

TVIF_PARAM :lParam成员有效;

TVIF_SELECTEDIMAGE :iSelectedImage成员有效;

TVIF_STATE :state 和 stateMask成员有效;

TVIF_TEXT :pszText 和 cchTextMax成员有效。



hItem

表示相关项的句柄。

state

一组标志位以及图像列表索引,表示项状态。当设置项状态时,stateMask表示此成员的哪些位是有效的;当获取项状态时,此成员返回由stateMask指示的当前有效的项状态位。此成员的0到7位包含项状态标志。见附录。8到11位代表从1开始的覆盖图像索引。覆盖图像用来叠加在项图像上。如果这些位是0,那么项就没有覆盖图像。为了隔离这些位,使用TVIS_OVERLAYMASK掩码(mask)。要设置项覆盖图像索引,应该使用INDEXTOOVERLAYMASK宏。图像列表的覆盖图像使用ImageList::SetOverlayImage()函数设置。因为使用的从 1 开始的索引是 4 位的,所以覆盖图像必须在图像列表的前 15 位。12到15位代表项状态图像索引。状态图像显示在项图标的旁边,表示应用程序定义的状态。也就是说指定了状态图像后,树控件就在每项的图标左边为状态图像保留空间。应用程序可以用状态图像(如选定和未选定的复选框)指示应用程序定义的项状态。如果这些位是0,那么就没有状态图像。为了隔离这些位,使用TVIS_STATEIMAGEMASK掩码(mask)。要设置状态图像,应该使用INDEXTOSTATEIMAGEMASK宏。状态图像索引表示要画的图像列表的图像索引。状态图像列表由TVM_SETIMAGELIST消息指定。

附:树视图控件的状态标志

TVIS_BOLD 项标签文本(ItemText)是粗体

TVIS_CUT 所选择的项作为粘贴复制操作

TVIS_DROPHILITED 所选择的项作为拖拽标志

TVIS_EXPANDED 子项列表当前是展开的。也就是子项是可见的。此值仅用于父项。

TVIS_EXPANDEDONCE子项列表已经至少展开了一次。如果父项有此状态,并对TV_EXPAND消息作出了响应,就不会为该父项产生 TVN_ITEMEXPANDING和TVN_ITEMEXPANDED 通知消息。把 TVE_COLLAPSE 和 TVE_COLLAPSERESET同 TVM_EXPAND一起使用就会使此状态重置。此值仅用于父项。

TVIS_EXPANDPARTIAL 部分展开树视图项。在此状态中,只是部分并不是全部的子项是可见的,而且会显示父项的加号。

TVIS_SELECTED 此项被选中了。项的外观取决于是否有焦点。会使用系统颜色画出所选择的项。

要设置或获取项的覆盖图像索引或者状态图像索引,必须在stateMask成员中指定以下标志:(这些值也可以用于去掉不感兴趣的状态位)

TVIS_OVERLAYMASK 用来指定项覆盖图像索引的位掩码。

TVIS_STATEIMAGEMASK 用来指定项状态图像的位掩码。

TVIS_USERMASK 同TVIS_STATEIMAGEMASK相同。



stateMask

表示state成员的有效位。如果要获取项状态,则在stateMask成员中设置相应位来指示state成员所返回的值;如果想要设置项状态,则在stateMask成员中设置相应位来指示想在sate成员中设置的位。如果要设置或获取项覆盖图像索引,则设置TVIS_OVERLAYMASK位;如果要设置或获取项状态索引,则设置TVIS_STATEIMAGEMASK位。



pszText

以NULL结尾的字符串地址,如果TVITEM结构要求项属性,那么它代表项标签文本(Item Text)。如果此成员的值是LPSTR_TEXTCALLBACK,那么父窗口负责存储项标签文本的名称。此种情况下,当树控件需要显示项标签、对项标签排序、编辑项标签或者当项标签改变时需要发送TVN_SETDISPINFO通知消息时,它会给父窗口发送TVN_GETDISPINFO通知消息。

如果TVITEM正在接收项属性时,此成员表示接收到的项标签文本的缓冲区地址。



cchTextMax

由pszText指向的缓冲区地址的字节大小。如果用TVITEM设置项属性,此成员被忽略。

说明:通常在向树控件添加项时指定项标签文本。InsertItem 成员函数能传递 TVITEM 结构。该结构定义了项的属性,项属性中有一个包含标签文本的字符串。树控件分配存储各项的内存,其中大部分内存都被项标签文本占用。如果应用程序保存了树控件中字符串的副本,就可以减少控件所需的内存空间,方法是在 TV_ITEM 的 pszText 成员中或者在 lpszItem 参数中指定 LPSTR_TEXTCALLBACK 值,而不是将实际字符串传递给树控件。每当需要重绘某项时,LPSTR_TEXTCALLBACK 使树控件从应用程序中检索该项的标签文本。为了检索标签文本,树控件发送 TVN_GETDISPINFO 通知消息,该消息包括 NMTVDISPINFO 结构的地址。必须通过设置所含结构的适当成员来响应该通知消息。

树控件使用从创建树控件的进程堆分配的内存。树控件最多可以包含的项数取决于堆中可用的内存量。每个项占用 64 字节。

iImage

当树控件的项结点是非选中状态时所使用的图标在图像列表中的索引值。如果此成员的值是I_IMAGECALLBACK,则父窗口负责存储该索引值。在此情况下,当树控件需要显示项图像时它向父窗口发送TVN_GETDISPINFO通知消息来获得索引值。



iSelectedImage

当树控件的项结点是选中状态时所使用的图标在图像列表中的索引值。如果此成员的值是I_IMAGECALLBACK,则父窗口负责存储该索引值。在此情况下,当树控件需要显示项图像时它向父窗口发送VN_GETDISPINFO通知消息来获得索引值。



cChildren

表示树控件项是否有相关的子结点的标志位。此成员可以是以下值之一:

0 —— 表示此项没有子结点。

1 —— 表示此此项有一个或多个子结点。

I_CHILDRENCALLBACK —— 父窗口会始终跟踪确定此项是否有子结点。此时,如果树控件需要显示项时,它会给父窗口发送TVN_GETDISPINFO通知消息,决定此项是否有子结点。如果树控件含有TVS_HASBUTTONS风格,则它使用此成员来决定是否显示按钮以表明存在子结点。也可以使用此成员来强制树控件显示按钮,尽管没有在此项之下插入任何子结点。这样可以最小化由于在此项之下插入了子结点所占用的控件内存。



lParam

同控件相关的32位值。





下面这段代码显示了如何用此结构插入树控件项,同时也演示了如何为项插入图像。

HICON hIcon[8];

int n;

m_imageList.Create(16, 16, 0, 8, 8); // 32, 32 for large icons

hIcon[0] = AfxGetApp()->LoadIcon(IDI_WHITE);

hIcon[1] = AfxGetApp()->LoadIcon(IDI_BLACK);

hIcon[2] = AfxGetApp()->LoadIcon(IDI_RED);

hIcon[3] = AfxGetApp()->LoadIcon(IDI_BLUE);

hIcon[4] = AfxGetApp()->LoadIcon(IDI_YELLOW);

hIcon[5] = AfxGetApp()->LoadIcon(IDI_CYAN);

hIcon[6] = AfxGetApp()->LoadIcon(IDI_PURPLE);

hIcon[7] = AfxGetApp()->LoadIcon(IDI_GREEN);

for (n = 0; n < 8; n++) {

m_imageList.Add(hIcon[n]);

}

CTreeCtrl* pTree = (CTreeCtrl*) GetDlgItem(IDC_TREEVIEW1);

pTree->SetImageList(&m_imageList, TVSIL_NORMAL);

TV_INSERTSTRUCT tvinsert;

tvinsert.hParent = NULL;

tvinsert.hInsertAfter = TVI_LAST;

tvinsert.item.mask = TVIF_IMAGE | TVIF_SELECTEDIMAGE |

TVIF_TEXT;// | TVIF_STATE;

tvinsert.item.hItem = NULL;

tvinsert.item.state = 0;

//tvinsert.item.state = TVIS_EXPANDED;

tvinsert.item.stateMask = 0;

//tvinsert.item.stateMask = TVIS_OVERLAYMASK;

tvinsert.item.cchTextMax = 6;

tvinsert.item.iSelectedImage = 1;

tvinsert.item.cChildren = 0;

tvinsert.item.lParam = 0;

// top level

tvinsert.item.pszText = "Parent1";

tvinsert.item.iImage = 2;

HTREEITEM hDad = pTree->InsertItem(&tvinsert);

tvinsert.item.pszText = "Parent2";

HTREEITEM hMom = pTree->InsertItem(&tvinsert);

// second level

tvinsert.hParent = hDad;

tvinsert.item.pszText = "Children1-1";

tvinsert.item.iImage = 3;

pTree->InsertItem(&tvinsert);

tvinsert.item.pszText = "Children1-2";



pTree->InsertItem(&tvinsert);

// second level

tvinsert.hParent = hMom;

tvinsert.item.pszText = "Children2-1";

tvinsert.item.iImage = 4;

pTree->InsertItem(&tvinsert);

tvinsert.item.pszText = "Children2-2";

pTree->InsertItem(&tvinsert);

tvinser.item.pszText = "Children2-3";

HTREEITEM hOther = pTree->InsertItem(&tvinsert);

// third level

tvinsert.hParent = hOther;

tvinsert.item.pszText = "Children2-3-1";

tvinsert.item.iImage = 7;

pTree->InsertItem(&tvinsert);

tvinsert.item.pszText = "Children2-3-2";

pTree->InsertItem(&tvinsert);

―――――――――――――――――――――――――――――――――――

即:

+Parent1

--Children1-1

--Children1-2

+Parent2

--Children2-1

--Children2-2

+Children2-3

--Children2-3-1

--Children2-3-2