前面讨论过windows的WM_PAINT消息和文本显示的部分内容, 下午用了两个小时来学习滚动条的相关内容, 滚动条在大部分的win32应用程序中均有应用,在此学习一下将有助于深入了解windows程序滚动条的实现机制。
我发现其实windows程序设计主要的是熟悉各个API的用法和windows处理窗口应用程序各种组件的机制就基本上可以开发一个具有各种特性的小程序。
下面是我下午的总结,贴出来供大家拍砖.......
4.6 滚动条
windows应用程序使用两种滚动条:
垂直滚动条: 将显示区域/文件的内容向上或者向下移动
水平滚动条: 将显示区域/文件的内容向左或者向右移动
滚动条的工作方式:
使用者向下卷动是想看文件下面的内容,而程序实际需要将文件的内容向上移动
windows文件和表头标识符依据的是使用者观点定义的:
向上卷动意味着朝文件的开头移动,而向下卷动则意味着朝文件尾部移动
给窗口添加滚动条:
利用CreateWindow函数生成窗口的时候,将窗口样式WS_VSCROLL和WS_HSCROLL位旗标加上即可生成具有垂直滚动条和
水平滚动条的窗口。
垂直滚动条将延伸为显示区域的整个高度,而水平滚动条则延伸为显示区域的整个宽度
对于特定的显示驱动程序和显示分辨率;垂直滚到条的宽度和水平滚到条的高度是固定不变的。
可以通过:
GetSystemMetrics呼叫来获取垂直滚动条的宽度和水平滚动条的高度信息。
滚动条的消息处理:
windows系统负责处理对滚动条的所有鼠标操作
窗口滚动条没有自动的键盘接口,如果滚动条需要键盘接口,则需要编制响应的消息响应程序
滚动条的范围和位置
滚动条具有两个常用属性:
滚动条的范围: 分别代表最大值和最小值
当垂直滚动条方块在卷动列的顶部的时候,卷动方块具有最小值
当垂直滚动条方块在卷动列的底部的时候,卷动方块具有最大值
当水平滚动条方块在卷动列的左部的时候,卷动方块具有最小值
当水平滚动条方块在卷动列的右部的时候,卷动方块具有最大值
缺省情况下: 滚动条的范围从0都100, 可以通过呼叫系统提供的函数来改变滚动条范围。
通过下面函数改变这些值:
BOOL SetScrollRange(HWND,int iBar,int iMinPos,int iMaxPos,BOOL bRedraw)
参数:hwnd 需要改变的滚动体属性的窗口的句柄
iBar 确定是垂直方向还是水平方向需要重新设置,可以取值:
SB_CTL:设置滚动条控制的范围
SB_HORZ: 表示设置水平滚动条的范围
SB_VERT: 表示设置垂直滚动条的范围
iMinPos: 设置的最小范围值
iMaxPos 设置的最大范围值
bRedraw 指定滚动条是否重画以反映设置改变,如果为TRUE则需要重画,为FALSE则不需要重画
如果在SetScrollRage函数之后还调用了其他的影响滚动条位置的函数则应将bRedraw
设置为FALSE,避免过多的重画。
滚动条的位置: 代表卷动方块在滚动条范围内的具体位置。
滚动条位置总是离散值,例如范围从0到4的滚动条具有5个卷动方块位置
可以通过下面的函数设置滚动条的位置:
BOOL SetScrollPos(HWND hwnd,int iBar, int iPos,BOOL bRedraw);
参数:hwnd 同SetScrollRange
iBar 同SetScrollRange
iPos 设定的滚动条的新的位置,在iMaxPos和iMinPos范围之间
bRedraw 同SetScrollRange
获取滚动条的信息:
GetScrollRange和GetScrollPos函数
处理机制:
在程序内使用滚动条时,由系统和程序共同管理滚动条及其卷动方块的位置
windows的处理:
1、当使用者在滚动条内单击鼠标的时候,提供反相显示的闪烁提示
2、当使用者在滚动条内拖动卷动方块时移动卷动方块
3、为包含滚动条窗口的窗口消息处理程序发送滚动条消息
4、处理所有的滚动鼠标事件
应用程序需处理:
1、初始化滚动条范围和位置
2、处理窗口消息处理程序的滚动条消息
3、更新滚动条内卷动方块的位置
4、更改显示区域的内容以响应对滚动条的更改
滚动条消息
在用鼠标单击滚动条或者拖动卷动方块时会产生滚动条消息:
WM_VSCROLL 上下移动
WM_HSCROLL 左右移动
在滚动条上的每个鼠标动过都将产生两个消息: 一个在按下鼠标按钮时产生,一个在释放按钮是产生
滚动条消息的附加信息: wParam和lParam, 对于窗口而建立的滚动条,其lParam可以忽略;lParam
只用于作为子窗口而建立的滚动条(通常是对话框)
wParam消息
wParam消息参数被分为低字组和高字组,wParam的低字组是一个数值,他指出了鼠标对滚动条进行的操作
低字组数值被看做一个通知码。 通知码值以SB_开头定义了一些预定义标识符;例如:
#define SB_LINEUP 0 //垂直向上卷动一行
#define SB_LINELST 0
#define SB_LINEDOWN 1
#define SB_LINERIGHT 1
#define SB_PAGEUP 2
#define SB_PAGELEFT 2
#define SB_PAGEDOWN 3
#define SB_PAGERIGHT 3
#define SB_THUMBPOSITION 4
#define SB_THUMBTRACK 5
#define SB_TOP 6
#define SB_LEFT 6
#define SB_BUTTON 7
#define SB_RIGHT 7
#define SB_ENDSCROLL 8
windows关于滚动条的消息
在滚动条的各个部位按住鼠标键,程序就能收到多个滚动条消息,当释放鼠标键后程序会收到一个带有SB_ENDSCROLL通知码的消息
通常可以忽略这个消息,windows不会改变卷动方块的位置。可以在程序中通过呼叫SetScrollPos来改变卷动块的位置。
SB_THUMBTRACK和SB_THUMBPOSITION通知码消息
产生时机: 把鼠标的光标放在卷动方块上并按住鼠标键移动时就会产生带有通知码SB_THUMBTRACK和SB_THUMBPOSITION的滚动条消息
wParam消息参数:
如果产生了SB_THUMBTRACK的通知码消息,那么wParam参数的高字组是使用者在拖动卷动方块时的当前位置,该值在iMaxPos和
iMinPos之间。
如果产生了SB_THUMBPOSITION时,wParam参数的高字组是使用者释放鼠标键后卷动方块的最终位置。
在处理其他通知码滚动条消息时wParam参数的高字组应该不处理。
在程序中通常不同时处理这两个通知码消息;
处理SB_THUMBTRACK通知码时,使用者拖动卷动块时,程序需要实时移动显示区域的内容。而处理SB_THUMBPOSITION则可以在
使用者停止卷动方块时移动显示区域的内容。
SetScrollPos函数
在程序收到SB_THUMBTRACK消息时如果不调用SetScrollPos函数处理,则释放鼠标后卷动块会回到原来的位置。
使用SetScrollRange的时候使用int 32位的数值是有效的,但是wParam的高字组是16位的,这样可能造成不能完全正确的反映
滚动条的鼠标消息,需要调用:
GetScrollInfo函数来得到消息。
作为一个小总结下面给出一个简单的应用的实例代码:
*
* 滚动条消息处理函数实例
*
*/
#include <windows.h>
#include <winuser.h>
#include <stdio.h>
LRESULT CALLBACK MainWndProc(HWND, UINT,WPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szPstr,
int iCmdShow)
{
HWND MainHwnd;
TCHAR *szAppName=TEXT("Scroll");
TCHAR *wndClassName=TEXT("Scroll");
MSG msg;
WNDCLASS mainwndclass;
mainwndclass.cbClsExtra=0;
mainwndclass.cbWndExtra=0;
mainwndclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
mainwndclass.hCursor=LoadCursor(NULL,IDC_ARROW);
mainwndclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
mainwndclass.hInstance=hInstance;
mainwndclass.lpfnWndProc=MainWndProc;
mainwndclass.lpszClassName=wndClassName;
mainwndclass.lpszMenuName=NULL;
mainwndclass.style=CS_VREDRAW | CS_HREDRAW;
if(!RegisterClass(&mainwndclass))
{
MessageBox(NULL,TEXT("You need a NT system to run this program!"),TEXT("Warnging"),MB_OK);
return0;
}
MainHwnd=CreateWindow(wndClassName,
TEXT("Scroll"),
WS_OVERLAPPEDWINDOW|WS_VSCROLL|WS_HSCROLL,
100,
100,
640,
480,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(MainHwnd,iCmdShow);
UpdateWindow(MainHwnd);
while(GetMessage(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT message,WPARAM wParam,LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
TEXTMETRIC tm;
TCHAR szBuffer[10];
staticint cxChar,
cxCaps,
cyChar,
cyClient,
iVscrollPos;
int i,
y;
switch(message)
{
case WM_CREATE:
hdc=GetDC(hwnd);
GetTextMetrics(hdc,&tm);
cxChar=tm.tmAveCharWidth;
cxCaps=(tm.tmPitchAndFamily &1?3 : 2 )*cxChar/2;
cyChar=tm.tmHeight+tm.tmExternalLeading;
ReleaseDC(hwnd,hdc);
SetScrollRange(hwnd,SB_CTL,100,100,FALSE);
SetScrollPos(hwnd,SB_VERT,0,TRUE);
return0;
case WM_SIZE:
cyClient=HIWORD(lParam);
return0;
case WM_VSCROLL:
switch(LOWORD(wParam))
{
case SB_LINEUP:
iVscrollPos-=1;
break;
case SB_LINEDOWN:
iVscrollPos+=1;
break;
case SB_PAGEUP:
iVscrollPos-=cyClient/cyChar; //cyClient是显示区域的高度,
break;
case SB_PAGEDOWN:
iVscrollPos+=cyClient/cyChar;
break;
case SB_THUMBPOSITION:
iVscrollPos=HIWORD(wParam);
break;
default :
break;
} //switch(wParam)
iVscrollPos=max(0,min(iVscrollPos,1000));
if(iVscrollPos != GetScrollPos(hwnd,SB_VERT))
{
SetScrollPos(hwnd,SB_VERT,iVscrollPos,TRUE);
InvalidateRect(hwnd,NULL,TRUE);
}
return0;
case WM_PAINT:
hdc=BeginPaint(hwnd,&ps);
TextOut(hdc,0,0,TEXT("My first Scroll Pronram!"),strlen("My first Scroll Pronram!"));
EndPaint(hwnd,&ps);
return0;
case WM_DESTROY:
PostQuitMessage(0);
return0;
}
return DefWindowProc(hwnd,message,wParam,lParam);
}
本来要加些注释的,不过这个代码比较简单,因此就没有加了........
弄到这里下面就该学习GDI的东西了, 粗略的看了一下,感觉GDI的东西比较复杂,希望自己自学时可以看懂...............
借这个随笔弄几个C语言的小问题....欢迎各位跟帖讨论:
1、指针与字符串的指向问题
分析:
char *pchA="Hello";
char *pchB="Hello";
if(a==b)
{
printf("Yes");
}
else
{
printf("No");
}
请问输出是Yes还是No ?
在WinTC中运行时输出的是Yes。
为什么呢?
这里我们需要知道的一个问题是: 在C语言实体开始执行开始后,操作系统会为C的执行实体分配内存空间, 这些分配的空间大体可以有四种空间:
(1)静态数据存储区:就是static存储的区域
(2)堆栈区域: 我们通常说的程序调用时需要传递实参, 那么实参传递后存放在那呢? 实参传递给被调用函数后就存放在堆栈区域。
(3)堆: 这个区域是我们用内存申请函数申请内存的区域,就是我们用malloc函数申请的空间来自这个地方
(4)代码区: 这个区域是用来存放执行代码的地方, 我们的常量也存放在这个地方。
从上面的实例可以看出现在的编译器在处理“Hello” 这样的字符串时,如果有两个地方引用,那这两个引用指向一个地方。
2、两个字符串,s和t; 把t字符串插入到s字符串中,s字符串有足够的空间存放t字符串
实现一:
void InsertStr(char *strTarget; const char *strSource)
{
int iStrLen;
if(NULL==strSource)
return ;
iStrLen=strlen(strSource);
while('\0' != *strTarget)
strTarget++;
while(iStrLen>0)
{
* ++strTarget = * ++strSource;
iStrLen--;
}
*strTarget++ = '\0';
return ;
}
实现二:
void InsertStr(char *strTarget; const char *strSource)
{
if(NULL==strSource)
return ;
while('\0' != *strTarget)
strTarget++;
while('\0'!= *strSource)
{
*++strTarget=*++strSource;
}
return ;
}
3、写出删除链表中所有节点的程序,
void DelAllNode(struct node *head)
{
struct node *pTemp;
while(NULL!=head)
{
pTemp=head->next;
free(head);
head=pTemp;
}
return ;
}
上面的代码除第一个实际运行过外,其他都没有验证过, 如有不当的地方请拍砖.........