对上一天学习的回顾:
1>. TextOut函数的使用
TextOut函数的作用是使用系统当前选择的字体、背景颜色以及正文颜色将一个字符串输出到指定位置, 函数原型:
BOOL TextOut( HDC hdc, //设备环境句柄 int nXStart, //字符串开始输出的x坐标 int nYStart, // 字符串开始输出的y坐标 LPCTSTR lpString, //需要输出的字符串 int cbString // 字符串的长度 );
当函数调用成功时返回一个非零的值, 调用失败时, 返回值为0。
2>. 取得当前系统字体信息:
使用GetTextMetrics函数可以取得当前字体信息, 函数原型如下:
BOOL GetTextMetrics( HDC hdc, // 设备环境句柄 LPTEXTMETRIC lptm // 指向一个TEXTMETRIC结构的指针, 该结构用于存放字体信息。 );
参数二LPTEXTMETRIC指向TEXTMETRIC结构, 在函数调用成功时, 函数将系统当前字体的各种信息复制到TEXTMETRIC结构中。
Windows滚动条介绍
滚动条由滚动滑块以及两端的滚动箭头组成, 滚动条的作用是当需要显示的内容超过窗口客户区大小时提供上下/左右的翻页使用户能够完整的阅读显示信息, 滚动条的图示:
滚动条理论基础
1>. 上下滚动?
以垂直方向的滚动条为例, 当用户向下滚动滚动条时目的是想看到下方更多的的信息, 因此我们需要将下方的信息显示出来, 如何显示更多的信息?
解决方案: 将不需要被显示的信息显示到客户区外, 令信息自动被Windows截掉, 图示说明:
由图示看出, 当用户向下翻动滚动条实际上我们是将起始输出部分的y坐标设为负数, 使已经显示过的信息输出到客户区的上部, 我们知道, 输出到客户区外部的信息会被Windows自动截掉, 所以用户不会再次看到已经显示过的信息, 取而代之的就是下方等待显示的信息, 上翻以及左右翻动的显示思路与下翻相同, 不再介绍。
2>. 如何创建一个带有滚动条的窗口?
创建带有水平/垂直的滚动条的窗口十分简单, 在CreateWindow函数中说明下即可, CreateWindow函数的原型回顾:
HWND CreateWindow( LPCTSTR lpClassName, //窗口类名称 LPCTSTR lpWindowName, //窗口标题 DWORD dwStyle, //窗口样式 int x, //窗口初始x坐标 int y, //窗口初始y坐标 int nWidth, //窗口初始x方向尺寸 int nHeight, //窗口初始y方向尺寸 HWND hWndParent, //父窗口句柄 HMENU hMenu, //窗口菜单句柄 HANDLE hlnstance, //程序实例句柄 LPVOID lpParam //创建参数 );
要窗口带有滚动条的窗口, 只需要在第三个参数
DWORD dwStyle, //窗口样式
也就是在窗口样式的属性中使用位或( | )运算对相关的标识符进行组合即可得到一个带有垂直/水平滚动条的窗口,
WS_HSCROLL //水平滚动条的标识符 WS_VSCROLL //垂直滚动条的标识符
例如要创建一个既含有垂直滚动条又含有水平滚动条的组合:
WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL
3>. 对于滚动条, Windows需要做哪些事?
当带有滚动条的窗口创建好后, Windows就会做一些必要的处理来协助我们更好的使用滚动条, Windows需要做的事情如下:
1>. 处理滚动条中的所有鼠标消息;
2>. 当用户单击滚动条时提供被单击部分的轻微闪烁;
3>. 当用户拖动滑块时在滚动条内移动滑块;
4>. 当窗口大小被调整时, 自动调整滚动条的长度;
5>. 向滚动条所在的窗口发送滚动条的相关消息。
4>. 我们要做的事情:
相对于系统, 我们需要做的事情已经较为轻松了, 主要有4项任务:
1>. 初始化滚动条的位置和滚动条的范围;
2>. 处理系统发来的消息;
3>. 根据发来的消息重置滑块的位置;
4>. 根据滚动条消息重绘客户区显示的内容。
5>. 将会收到哪些滚动条消息?
滚动条消息来源同其他消息一样, 伴随着wParam与lParam消息机制, 当窗口为父窗口时消息的来源为wParam, 此时可忽略lParam的值, lParam用于子窗口消息。
wParam参数分为两部分, 高位字与低位字, 其中高位字代表用户松开鼠标键时滑块的最终位置, 低位字上代表鼠标在滚动条上的动作, 以一个值的形式表现出来, 同样, 为了方便记忆, 有不同的标识符对这些值进行区分, 这些标识符定义在WINUSER.H头文件中, 以SB_开头, 有关滚动条的消息标识符如下:
#define SB_LINEUP 0 //上翻一个单位 #define SB_LINELEFT 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_BOTTOM 7 //滑块到了底端 #define SB_RIGHT 7 //滑块到了右端 #define SB_ENDSCROLL 8 //释放鼠标
6>. 需要使用到的新函数:
①. SetScrollRange
SetScrollRange函数的作用是设置所指定滚动条范围的最小值和最大值, 其函数的原型如下:
BOOL SetScrollRange( HWND hWnd, //窗口句柄 int nBar, //被设置的滚动条类型 int nMinPos, //滚动条的最小位置 int nMaxPos, //滚动条的最大位置 BOOL bRedraw //重绘标志 );
参数二int nBar为被设置的滚动条类型, SB_HORZ表示该窗口的水平滚动条, SB_VERT表示垂直滚动条;
参数四BOOL bRedraw指定滚动条是否被重绘以反映变化, 当参数为TRUE, 滚动条被重绘, FALSE则不被重绘。
②. SetScrollPos
SetScrollPos函数的作用是设置所指定滚动条中的滚动按钮的位置, 函数原型:
int SetScrollPos( HWND hWnd, //窗体句柄 int nBar, //被设置的滚动条类型 int nPos, //滚动条的新位置 BOOL bRedraw //重绘标志 );
实战滚动条
下面我们尝试着输出一些文字, 使其上下、左右均超过客户区的尺寸, 这样我们就可以实际练习下水平滚动条以及垂直滚动条了, 我们准备了很多行文字, 笔者也不知道到底有多少行, 而且最长的那行文字有多少个也不知道, 我们把他放在一个text.h头文件中, 并计算他到底有多少行以及最长的那行有多少字, 由于文字行数较多, 这里将它在代码框里折叠显示, 定义的头文件如下:
#include<string.h> #define NUMLINES ( (int)(sizeof(statement) / sizeof(statement[0]) ) ) //计算总行数 TCHAR *statement[] = { TEXT("没有一种不通过蔑视、忍受和奋斗就可以征服的命运。"), TEXT("伟人之所以伟大,是因为他与别人共处逆境时,别人失去了信心,他却下决心实现自己的目标。"), TEXT("世上没有绝望的处境,只有对处境绝望的人。"), TEXT("当你感到悲哀痛苦时,最好是去学些什么东西。学习会使你永远立于不败之地。"), TEXT("给自己定目标,一年,两年,五年,也许你出生不如别人好,通过努力,往往可以改变70%的命运。破罐子破摔只能和懦弱做朋友。"), TEXT("知道自己要干什么,夜深人静,问问自己,将来的打算,并朝着那个方向去实现。而不是无所事事和做一些无谓的事。"), TEXT("梦想无论怎么模糊,它总潜伏在我们心底,使我们的心境永远得不到宁静,直到梦想成为事实。"), TEXT("梦是一种欲望,想是一种行动。梦想是梦与想的结晶。"), TEXT("生气,就是拿别人的过错来惩罚自己。原谅别人,就是善待自己。"), TEXT("一个实现梦想的人,就是一个成功的人。"), TEXT("大事坚持原则,小事学会变通。"), TEXT("一个人有钱没钱不一定,但如果这个人没有了梦想,这个人穷定了。"), TEXT("不要急于表态不急于表态或发表意见,可以使人对你揣摸猜测。谨慎的沉默是精明人的帮手。一旦表态,你的决定就容易受到批评和非议。如果这些决定是以失败告终,你就会更加倒霉。"), TEXT("平凡朴实的梦想,我们用那唯一的坚持信念去支撑那梦想。"), TEXT("不要总谈论自己你若总是谈论自己,那么不是吹嘘自己,就是贬低自己;前者是虚荣的表现,后者是卑微的表现。"), TEXT("千万不要抱怨,抱怨会使你丢丑。抱怨会使人对你傲慢无礼,并促使别人如你所抱怨的那么做。要赢得别人的帮助,最好的办法是表扬他人。"), TEXT("凡事终了时务必小心谨慎,顺利抽身退出要比顺利地进入时更难。"), TEXT("很难说什么是办不到的事情,因为昨天的梦想,可以是今天的希望,并且还可以成为明天的现实。"), TEXT("当你再也没有什么可以失去的时候,就是你开始得到的时候。"), TEXT("生命犹如一片绿叶,随着时间的流逝,慢慢变的枯黄,但他的叶脉还是那么清晰可见。"), TEXT("我们可以躲开大家,却躲不开一只苍蝇。生活中使我们不快乐的常是一些芝麻小事。"), TEXT("人生的意义不在于拿一手好牌,而在于打好一手坏牌。"), TEXT("一个人想平庸,阻拦者很少;一个人想出众,阻拦者很多。不少平庸者与周围人关系融洽,不少出众者与周围人关系紧张。"), TEXT("“危机”两个字,一个意味着危险,另外一个意味着机会,不要放弃任何一次努力。"), TEXT("世上没有绝望的处境,只有对处境绝望的人。"), TEXT("再长的路,一步步也能走完,再短的路,不迈开双脚也无法到达。"), TEXT("有志者自有千计万计,无志者只感千难万难。"), TEXT("成功与不成功之间有时距离很短只要后者再向前几步。"), TEXT("世界会向那些有目标和远见的人让路。"), TEXT("世界青睐有雄心壮志的人。成功所依靠的惟一条件就是思考。当你的思维以最高速度运转时,乐观欢快的情绪就会充斥全身。一个人最完美的作品都是在充满愉快、乐观、深情的状态下完成的。学会让自己安静,把思维沉浸下来,慢慢降低对事物的欲望。把自我经常归零,每天都是新的起点,没有年龄的限制,只要你对事物的欲望适当的降低,会赢得更多的求胜机会。"), TEXT("学会让自己安静,把思维沉浸下来,慢慢降低对事物的欲望。把自我经常归零,每天都是新的起点,没有年龄的限制,只要你对事物的欲望适当的降低,会赢得更多的求胜机会。"), TEXT("杨澜:年轻时候应该能够作一些冒险。年轻时候最大的财富,不是你的青春,不是你的美貌,也不是你充沛的精力,而是你有犯错误的机会。"), TEXT("没有十全十美的东西,没有十全十美的人,关键是清楚到底想要什么。得到想要的,肯定会失去另外一部分。如果什么都想要,只会什么都得不到。"), TEXT("做自己的决定。然后准备好承担后果。从一开始就提醒自己,世上没有后悔药吃。"), TEXT("在你内心深处,还有无穷的潜力,有一天当你回首看时,你就会知道这绝对是真的。"), TEXT("不为模糊不清的未来担忧,只为清清楚楚的现在努力。"), TEXT("没有一种不通过蔑视、忍受和奋斗就可以征服的命运。"), TEXT("伟人之所以伟大,是因为他与别人共处逆境时,别人失去了信心,他却下决心实现自己的目标。"), TEXT("世上没有绝望的处境,只有对处境绝望的人。"), TEXT("当你感到悲哀痛苦时,最好是去学些什么东西。学习会使你永远立于不败之地。"), TEXT("给自己定目标,一年,两年,五年,也许你出生不如别人好,通过努力,往往可以改变70%的命运。破罐子破摔只能和懦弱做朋友。"), TEXT("知道自己要干什么,夜深人静,问问自己,将来的打算,并朝着那个方向去实现。而不是无所事事和做一些无谓的事。"), TEXT("梦想无论怎么模糊,它总潜伏在我们心底,使我们的心境永远得不到宁静,直到梦想成为事实。"), TEXT("梦是一种欲望,想是一种行动。梦想是梦与想的结晶。"), TEXT("生气,就是拿别人的过错来惩罚自己。原谅别人,就是善待自己。生气,就是拿别人的过错来惩罚自己。原谅别人,就是善待自己。生气,就是拿别人的过错来惩罚自己。"), TEXT("一个实现梦想的人,就是一个成功的人。"), TEXT("大事坚持原则,小事学会变通。"), TEXT("一个人有钱没钱不一定,但如果这个人没有了梦想,这个人穷定了。"), TEXT("不要急于表态不急于表态或发表意见,可以使人对你揣摸猜测。谨慎的沉默是精明人的帮手。一旦表态,你的决定就容易受到批评和非议。"), TEXT("平凡朴实的梦想,我们用那唯一的坚持信念去支撑那梦想。"), TEXT("不要总谈论自己你若总是谈论自己,那么不是吹嘘自己,就是贬低自己;前者是虚荣的表现,后者是卑微的表现。"), TEXT("千万不要抱怨,抱怨会使你丢丑。抱怨会使人对你傲慢无礼,并促使别人如你所抱怨的那么做。要赢得别人的帮助,最好的办法是表扬他人。"), TEXT("凡事终了时务必小心谨慎,顺利抽身退出要比顺利地进入时更难。"), TEXT("很难说什么是办不到的事情,因为昨天的梦想,可以是今天的希望,并且还可以成为明天的现实。"), TEXT("当你再也没有什么可以失去的时候,就是你开始得到的时候。"), TEXT("生命犹如一片绿叶,随着时间的流逝,慢慢变的枯黄,但他的叶脉还是那么清晰可见。"), TEXT("我们可以躲开大家,却躲不开一只苍蝇。生活中使我们不快乐的常是一些芝麻小事。"), TEXT("人生的意义不在于拿一手好牌,而在于打好一手坏牌。"), TEXT("一个人想平庸,阻拦者很少;一个人想出众,阻拦者很多。不少平庸者与周围人关系融洽,不少出众者与周围人关系紧张。"), TEXT("“危机”两个字,一个意味着危险,另外一个意味着机会,不要放弃任何一次努力。"), TEXT("世上没有绝望的处境,只有对处境绝望的人。"), TEXT("再长的路,一步步也能走完,再短的路,不迈开双脚也无法到达。"), TEXT("有志者自有千计万计,无志者只感千难万难。"), TEXT("成功与不成功之间有时距离很短只要后者再向前几步。"), TEXT("世界会向那些有目标和远见的人让路。"), TEXT("世界青睐有雄心壮志的人。成功所依靠的惟一条件就是思考。当你的思维以最高速度运转时,乐观欢快的情绪就会充斥全身。一个人最完美的作品都是在充满愉快、乐观、深情的状态下完成的。"), TEXT("学会让自己安静,把思维沉浸下来,慢慢降低对事物的欲望。把自我经常归零,每天都是新的起点,没有年龄的限制,只要你对事物的欲望适当的降低,会赢得更多的求胜机会。"), TEXT("杨澜:年轻时候应该能够作一些冒险。年轻时候最大的财富,不是你的青春,不是你的美貌,也不是你充沛的精力,而是你有犯错误的机会。"), TEXT("没有十全十美的东西,没有十全十美的人,关键是清楚到底想要什么。得到想要的,肯定会失去另外一部分。如果什么都想要,只会什么都得不到。"), TEXT("做自己的决定。然后准备好承担后果。从一开始就提醒自己,世上没有后悔药吃。"), TEXT("在你内心深处,还有无穷的潜力,有一天当你回首看时,你就会知道这绝对是真的。"), TEXT("不为模糊不清的未来担忧,只为清清楚楚的现在努力。"), TEXT("没有一种不通过蔑视、忍受和奋斗就可以征服的命运。"), TEXT("伟人之所以伟大,是因为他与别人共处逆境时,别人失去了信心,他却下决心实现自己的目标。"), TEXT("世上没有绝望的处境,只有对处境绝望的人。"), TEXT("当你感到悲哀痛苦时,最好是去学些什么东西。学习会使你永远立于不败之地。"), TEXT("给自己定目标,一年,两年,五年,也许你出生不如别人好,通过努力,往往可以改变70%的命运。破罐子破摔只能和懦弱做朋友。"), TEXT("知道自己要干什么,夜深人静,问问自己,将来的打算,并朝着那个方向去实现。而不是无所事事和做一些无谓的事。"), TEXT("梦想无论怎么模糊,它总潜伏在我们心底,使我们的心境永远得不到宁静,直到梦想成为事实。"), TEXT("梦是一种欲望,想是一种行动。梦想是梦与想的结晶。"), TEXT("生气,就是拿别人的过错来惩罚自己。原谅别人,就是善待自己。"), TEXT("一个实现梦想的人,就是一个成功的人。"), TEXT("大事坚持原则,小事学会变通。"), TEXT("一个人有钱没钱不一定,但如果这个人没有了梦想,这个人穷定了。"), TEXT("不要急于表态不急于表态或发表意见,可以使人对你揣摸猜测。谨慎的沉默是精明人的帮手。一旦表态,你的决定就容易受到批评和非议。如果这些决定是以失败告终,你就会更加倒霉。"), TEXT("平凡朴实的梦想,我们用那唯一的坚持信念去支撑那梦想。"), TEXT("不要总谈论自己你若总是谈论自己,那么不是吹嘘自己,就是贬低自己;前者是虚荣的表现,后者是卑微的表现。"), TEXT("千万不要抱怨,抱怨会使你丢丑。抱怨会使人对你傲慢无礼,并促使别人如你所抱怨的那么做。要赢得别人的帮助,最好的办法是表扬他人。"), TEXT("凡事终了时务必小心谨慎,顺利抽身退出要比顺利地进入时更难。"), TEXT("很难说什么是办不到的事情,因为昨天的梦想,可以是今天的希望,并且还可以成为明天的现实。"), TEXT("当你再也没有什么可以失去的时候,就是你开始得到的时候。"), TEXT("生命犹如一片绿叶,随着时间的流逝,慢慢变的枯黄,但他的叶脉还是那么清晰可见。"), TEXT("我们可以躲开大家,却躲不开一只苍蝇。生活中使我们不快乐的常是一些芝麻小事。"), TEXT("人生的意义不在于拿一手好牌,而在于打好一手坏牌。"), TEXT("一个人想平庸,阻拦者很少;一个人想出众,阻拦者很多。不少平庸者与周围人关系融洽,不少出众者与周围人关系紧张。"), TEXT("“危机”两个字,一个意味着危险,另外一个意味着机会,不要放弃任何一次努力。"), TEXT("世上没有绝望的处境,只有对处境绝望的人。"), TEXT("再长的路,一步步也能走完,再短的路,不迈开双脚也无法到达。"), TEXT("有志者自有千计万计,无志者只感千难万难。"), TEXT("成功与不成功之间有时距离很短只要后者再向前几步。"), TEXT("世界会向那些有目标和远见的人让路。"), TEXT("世界青睐有雄心壮志的人。成功所依靠的惟一条件就是思考。当你的思维以最高速度运转时,乐观欢快的情绪就会充斥全身。一个人最完美的作品都是在充满愉快、乐观、深情的状态下完成的。"), TEXT("学会让自己安静,把思维沉浸下来,慢慢降低对事物的欲望。把自我经常归零,每天都是新的起点,没有年龄的限制,只要你对事物的欲望适当的降低,会赢得更多的求胜机会。"), TEXT("杨澜:年轻时候应该能够作一些冒险。年轻时候最大的财富,不是你的青春,不是你的美貌,也不是你充沛的精力,而是你有犯错误的机会。"), TEXT("没有十全十美的东西,没有十全十美的人,关键是清楚到底想要什么。得到想要的,肯定会失去另外一部分。如果什么都想要,只会什么都得不到。"), TEXT("做自己的决定。然后准备好承担后果。从一开始就提醒自己,世上没有后悔药吃。"), TEXT("在你内心深处,还有无穷的潜力,有一天当你回首看时,你就会知道这绝对是真的。"), TEXT("不为模糊不清的未来担忧,只为清清楚楚的现在努力。") }; //计算statement所有句子中最长语句的长度 int GetMaxLength() { /* *计算statement所有句子中最长语句的长度 *返回值: int GetMaxLength(void) -> int */ int maxLength = 0 ; int i ; for( i = 0; i < NUMLINES; i++ ) { if( wcslen(statement[i]) > maxLength ) maxLength = wcslen(statement[i]) ; } return maxLength ; }
在这个头文件中, 其中有两句是十分重要的, 一是计算总行数:
#define NUMLINES ( (int)(sizeof(statement) / sizeof(statement[0]) ) ) //计算总行数
另一个是计算最长串字符个数的函数GetMaxLength, 该函数的定义如下:
int GetMaxLength() { /* *计算statement所有句子中最长语句的长度 *返回值: int GetMaxLength(void) -> int */ int maxLength = 0 ; int i ; for( i = 0; i < NUMLINES; i++ ) { if( wcslen(statement[i]) > maxLength ) maxLength = wcslen(statement[i]) ; } return maxLength ; }
下面编写我们的源文件, LearnScroll.c, 先看一下完整的代码, 稍后我们详细解释, 代码如下:
1 #include<windows.h> 2 #include"text.h" 3 4 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; //声明窗口过程函数 5 6 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) 7 { 8 static TCHAR szAppName[] = TEXT("LearnScroll") ; 9 HWND hwnd ; 10 MSG msg ; 11 WNDCLASS wndclass ; 12 13 //窗口类成员属性 14 wndclass.lpfnWndProc = WndProc ; 15 wndclass.style = CS_HREDRAW | CS_VREDRAW ; 16 wndclass.hInstance = hInstance ; 17 wndclass.lpszClassName = szAppName ; 18 wndclass.lpszMenuName = NULL ; 19 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH) ; 20 wndclass.hCursor = LoadCursor(NULL, IDI_APPLICATION) ; 21 wndclass.hIcon = LoadIcon(NULL, IDC_ARROW) ; 22 wndclass.cbClsExtra = 0 ; 23 wndclass.cbWndExtra = 0 ; 24 25 //注册窗口类 26 if( !RegisterClass(&wndclass) ) 27 { 28 MessageBox( NULL, TEXT("无法注册窗口类!"), TEXT("错误"), MB_OK | MB_ICONERROR ) ; 29 return 0 ; 30 } 31 32 //创建窗口 33 hwnd = CreateWindow( 34 szAppName, TEXT("滚动条示例"), 35 WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, 36 CW_USEDEFAULT, CW_USEDEFAULT, 37 CW_USEDEFAULT, CW_USEDEFAULT, 38 NULL, NULL, hInstance, NULL 39 ) ; 40 41 //显示窗口 42 ShowWindow( hwnd, iCmdShow ) ; 43 UpdateWindow( hwnd ) ; 44 45 //获取、翻译、分发消息 46 while( GetMessage( &msg, NULL, 0, 0 ) ) 47 { 48 TranslateMessage( &msg ) ; 49 DispatchMessage( &msg ) ; 50 } 51 52 return msg.wParam ; 53 } 54 55 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 56 { 57 static int cxChar, cxCaps, cyChar, cyClient, cxClient, iVscrollPos, iHscrollPos ; 58 //cxChar:平均字符宽度; cxCaps: 大写字母平均宽度; cyChar: 字符高; cyClient、cxClient: 客户区y、x方向尺寸; 59 //iVscrollPos: 竖直方向滚动条滑块位置; iHscrollPos: 水平方向滚动条滑块位置 60 61 HDC hdc ; 62 RECT rect ; //记录客户区RECT结构 63 int i, x, y; //i循环控制, x记录水平方向坐标, y竖直方向坐标 64 PAINTSTRUCT ps ; 65 TEXTMETRIC tm ; 66 67 switch(message) 68 { 69 case WM_CREATE: //处理WM_CREATE消息 70 hdc = GetDC(hwnd) ; 71 GetTextMetrics( hdc, &tm ) ; //获取系统字体信息 72 cxChar = tm.tmAveCharWidth ; //获取平均宽度 73 cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ; //大写字母平均宽度 74 cyChar = tm.tmHeight + tm.tmExternalLeading ; //字符高度 75 ReleaseDC( hwnd, hdc ); 76 77 SetScrollRange( hwnd, SB_VERT, 0, NUMLINES - 1, FALSE ) ; //设置竖直滚动条范围的最小值和最大值 78 SetScrollRange( hwnd, SB_HORZ, 0, GetMaxLength() - 1, FALSE ) ; //设置水平滚动条范围的最小值和最大值 79 SetScrollPos( hwnd, SB_VERT, iVscrollPos, TRUE ) ; //设置竖直滚动条中的滚动按钮的位置 80 SetScrollPos( hwnd, SB_HORZ, iHscrollPos, TRUE ) ; //设置水平定滚动条中的滚动按钮的位置 81 82 return 0 ; 83 84 case WM_SIZE: //处理WM_SIZE 85 GetClientRect( hwnd, &rect ) ; 86 cyClient = rect.bottom ; //得到客户区y方向尺寸 87 cxClient = rect.right ; //得到客户区x方向尺寸 88 return 0 ; 89 90 case WM_VSCROLL: //处理垂直滚动条消息 91 switch( LOWORD(wParam) ) 92 { 93 case SB_LINEUP: //上翻一行 94 iVscrollPos -= 1 ; 95 break ; 96 97 case SB_LINEDOWN: //下翻一行 98 iVscrollPos += 1 ; 99 break ; 100 101 case SB_PAGEUP: //向上翻一整页 102 iVscrollPos -= cyClient / cyChar ; 103 break ; 104 105 case SB_PAGEDOWN: //向下翻一整页 106 iVscrollPos += cyClient / cyChar ; 107 break ; 108 109 case SB_THUMBPOSITION: //拖动滑块滑块被放下时 110 iVscrollPos = HIWORD(wParam) ; 111 break ; 112 113 default: 114 break; 115 } 116 iVscrollPos = max( 0, min(iVscrollPos, NUMLINES -1) ) ; 117 if( iVscrollPos != GetScrollPos(hwnd, SB_VERT) ) //当滑块位置改变时重置滑块位置 118 { 119 SetScrollPos( hwnd, SB_VERT, iVscrollPos, TRUE ) ; 120 InvalidateRect( hwnd, NULL, TRUE ) ; //使客户区无效等待重绘 121 } 122 return 0 ; 123 124 case WM_HSCROLL: //处理水平滚动条消息 125 switch( LOWORD(wParam) ) 126 { 127 case SB_LINELEFT: //左翻一行 128 iHscrollPos -= 1 ; 129 break ; 130 131 case SB_LINERIGHT: //右翻一行 132 iHscrollPos += 1 ; 133 break ; 134 135 case SB_PAGELEFT: //左翻一页 136 iHscrollPos -= cxClient / cxCaps ; 137 break ; 138 139 case SB_PAGERIGHT: //右翻一页 140 iHscrollPos += cxClient / cxCaps ; 141 break ; 142 143 case SB_THUMBPOSITION: //拖动滑块滑块被放下时 144 iHscrollPos = HIWORD(wParam) ; 145 break ; 146 147 default: 148 break ; 149 } 150 iHscrollPos = max( 0, min( iHscrollPos, GetMaxLength() -1 ) ) ; 151 if( iHscrollPos != GetScrollPos( hwnd, SB_HORZ ) ) 152 { 153 SetScrollPos( hwnd, SB_HORZ, iHscrollPos, TRUE ) ; 154 InvalidateRect( hwnd, NULL, TRUE ) ; 155 } 156 return 0 ; 157 158 case WM_PAINT: //处理WM_PAINT消息 159 hdc = BeginPaint( hwnd, &ps ) ; 160 161 for( i= 0; i < NUMLINES; i++ ) 162 { 163 y = cyChar * ( i -iVscrollPos ) ; 164 x = cxCaps * ( 0 - iHscrollPos ) ; 165 TextOut( hdc, x, y, statement[i], lstrlen(statement[i]) ) ; //输出文字 166 } 167 168 EndPaint( hwnd, &ps ) ; 169 return 0 ; 170 171 case WM_DESTROY: //处理WM_DESTROY消息 172 PostQuitMessage( 0 ) ; 173 return 0 ; 174 } 175 176 return DefWindowProc( hwnd, message, wParam, lParam ) ; 177 }
编译运行, 看下成果:
看起来还算不错, 当滚动条向下翻时文字就随着向上滚动, 使下面的文字能够显示出来, 水平的滚动条也是这样, 下面详细说说重点部分的代码:
1>. 创建一个带有垂直滚动条以及水平滚动条的窗口:
hwnd = CreateWindow( szAppName, TEXT("滚动条示例"), WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ) ;
2>. 设置垂直滚动条、水平滚动条的范围以及初始位置:
SetScrollRange( hwnd, SB_VERT, 0, NUMLINES - 1, FALSE ) ; //设置垂直滚动条范围的最小值和最大值 SetScrollRange( hwnd, SB_HORZ, 0, GetMaxLength() - 1, FALSE ) ; //设置水平滚动条范围的最小值和最大值 SetScrollPos( hwnd, SB_VERT, iVscrollPos, TRUE ) ; //设置垂直滚动条中的滚动按钮的位置 SetScrollPos( hwnd, SB_HORZ, iHscrollPos, TRUE ) ; //设置水平定滚动条中的滚动按钮的位置
可以看出, 垂直滚动条的范围为0到行数-1, 这就意味这, 每下翻/上翻一个单位, 客户区显示的文字就会向上//向下滚动一行;
水平滚动条的范围为0到最长那行文字的长度-1, 每左翻/右翻一个单位, 客户区显示的文字就会向右//向左滚动一个字符的宽度。
3>. 当窗口大小调整时重新获取客户区尺寸数据:
case WM_SIZE: //处理WM_SIZE GetClientRect( hwnd, &rect ) ; cyClient = rect.bottom ; //得到客户区y方向尺寸 cxClient = rect.right ; //得到客户区x方向尺寸 return 0 ;
4>. 处理滚动条消息:
case WM_VSCROLL: //处理水平滚动条消息 switch( LOWORD(wParam) ) { case 滚动条消息: [处理滚动条消息] }
5>. 重置滚动条滑块位置:
iVscrollPos = max( 0, min(iVscrollPos, NUMLINES -1) ) ; //确保滚动条的位置在设置的范围内。 if( iVscrollPos != GetScrollPos(hwnd, SB_VERT) ) //当滑块位置改变时重置滑块位置 { SetScrollPos( hwnd, SB_VERT, iVscrollPos, TRUE ) ; InvalidateRect( hwnd, NULL, TRUE ) ; //使客户区无效等待重绘 } return 0 ;
这一句注释上已经描述的是否清楚了, 当滑块位置改变时重置滑块位置并使客户区无效等待重绘。
6>. 处理重绘消息:
case WM_PAINT: //处理WM_PAINT消息 hdc = BeginPaint( hwnd, &ps ) ; for( i= 0; i < NUMLINES; i++ ) { y = cyChar * ( i -iVscrollPos ) ; x = cxCaps * ( 0 - iHscrollPos ) ; TextOut( hdc, x, y, statement[i], lstrlen(statement[i]) ) ; //输出文字 } EndPaint( hwnd, &ps ) ; return 0 ;
目的是重绘客户区内容并使其有效, 注意这里的
y = cyChar * ( i -iVscrollPos ) ; x = cxCaps * ( 0 - iHscrollPos ) ;
这是计算从起始输出的坐标, 每行对应一个y值, 当滑块的位置向下滚动1时, y的值就会减去一个字符的高度, 使该行显示到窗口外部, 这样新的一行就会被显示出来, 同样, 翻动一整夜的计算思路同一行; x是指水平起始输出位置, 计算思路相同。
这样, 一个简单的带有滚动条的窗口就完成了! 看起来挺不错的, 不是吗? 可以先稍微休息一下, 下面我们还有事要做。
但这还不够好!
在上面我们使用的滚动条中, 虽说能够滚动文字, 但是依然存在许多小问题:
问题一: 滑块的大小是固定的, 而我见到的应用软件滑块能够根据内容的多少自动调整滑块大小;
问题二: 当我拖动滑块时只有当滑块释放时页面才会滚动, 我想要的是当滑块被拖动时页面也同样跟着滚动;
问题三: 当滑块滚动到底部时最后一行显示到了客户区顶部, 下面留有一大片的空白, 而我并不需要保留下面的空白, 也就是说最后一行在滑块拖动到底部后它只显示在底部就行。
幸运的是, 我们依然有解决方案:
问题一: 自行设置滑块的大小;
问题二: 根据SB_THUMBTRACK消息处理页面的滚动, SB_THUMBTRACK消息是当滑块被拖动时就会源源不断的发来;
问题三: 重新设置滚动的范围。
更好的滚动条:
在使用更好的滚动条之前我们首先要认识三个新函数: SetScrollInfo、GetScrollInfo以及ScrollWindow.
1>. SetScrollInfo
函数功能: 用于设置滚动条的相关参数, 包括滚动范围的最大值和最小值, 页面大小, 滑块的位置, 函数的原型:
int SetScrollInfo( HWND hWnd; //窗口句柄 int fnBar, //指定被设定参数的滚动条的类型 LPSCROLLINFO lpsi, //指向一个SCROLLINFO结构 BOOL fRedraw //重绘标志 ) ;
穿插讲述: 什么是SCROLLINFO结构?
SCROLLINFO的成员记录有关滚动条的信息, 其结构定义如下:
typedef struct tagSCROLLINFO { UINT cbSize ; //设置为sizeof (SCROLLINFO), 表示该结构的大小 UINT fMask ; //要设置或获取的值 int nMin ; //滚动条范围的最小值 int nMax ; //滚动条范围的最大值 UINT nPage ; //页面大小 int nPos ; //当前位置 int nTrackPos ; //当前追踪位置 }SCROLLINFO;
成员一UINT cbSize : 该参数必须在函数调用之前设置, cbSize表示该结构的大小, 用sizeof (SCROLLINFO)表示即可。
成员二UINT fMask: 用于指定指定结构中的哪些成员是有效的, 通过位或运算进行组合, 可组合的标识符如下:
SIF_ALL //整个结构都有效
SIF_DISABLENOSCROLL //禁用滚动条 SIF_PAGE //用于指定或获取页面的大小, 在SetScrollInfo中用于设定页面的大小, 在GetScrollInfo用于获取页面的大小
SIF_POS //设置/取得滚动条滑块当前的位置
SIF_RANGE //滚动条的范围 SIF_TRACKPOS //仅在GetScrollInfo函数中使用, 并且仅用在处理SB_THUMBTRACK或者SB_THUMBPOSITION的WM_VSCROLL消息或WM_HSCROLL消息时使用。取得当前滑块的跟踪位置。
穿插讲述完毕! 继续讲解第二个新函数。
2>. GetScrollInfo
用于取得滚动条的相关参数, 包括滚动范围的最大值和最小值, 页面大小, 滑块的位置, 函数的原型:
BOOL GetScrollInfo( HWND hWnd, //窗口句柄 int fnBar, //指定被设定参数的滚动条的类型 LPSCROLLINFO lpsi //指向一个SCROLLINFO结构 );
3>. ScrollWindow
该函数的作用是滚动所指定的窗口客户区域内容, 原型如下:
BOOL ScrollWindow( HWND hWnd, //窗口句柄 int XAmount, //指定水平滚动的距离 int YAmount, //指定垂直滚动的距离 CONST RECT *IpRect, //指向RECT结构的指针, 该结构指定了将要滚动的客户区范围。若此参数为NULL,则整个客户区域将被滚动。 CONST RECT *lpClipRect //指向RECT结构的指针, 该结构指定了要滚动的裁剪区域。只有这个矩形中才会被滚动。 );
好了, 说的差不多够多了, 研究代码才是更好的沟通方式, 下面我们实际实践一下更好的滚动条, 更多的细节请在代码中体会, 限于篇幅的长度, 这里将WinMain函数折叠显示, 仅将窗口过程函数部分的代码全部显示出来:
1 #include<windows.h> 2 #include"text.h" 3 4 LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ; //声明窗口过程函数 5 6 int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow ) 7 { 8 static TCHAR szAppName[] = TEXT("LearnScroll") ; 9 HWND hwnd ; 10 MSG msg ; 11 WNDCLASS wndclass ; 12 13 //窗口类成员属性 14 wndclass.lpfnWndProc = WndProc ; 15 wndclass.style = CS_HREDRAW | CS_VREDRAW ; 16 wndclass.hInstance = hInstance ; 17 wndclass.lpszClassName = szAppName ; 18 wndclass.lpszMenuName = NULL ; 19 wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH) ; 20 wndclass.hCursor = LoadCursor(NULL, IDI_APPLICATION) ; 21 wndclass.hIcon = LoadIcon(NULL, IDC_ARROW) ; 22 wndclass.cbClsExtra = 0 ; 23 wndclass.cbWndExtra = 0 ; 24 25 //注册窗口类 26 if( !RegisterClass(&wndclass) ) 27 { 28 MessageBox( NULL, TEXT("无法注册窗口类!"), TEXT("错误"), MB_OK | MB_ICONERROR ) ; 29 return 0 ; 30 } 31 32 //创建窗口 33 hwnd = CreateWindow( 34 szAppName, TEXT("滚动条示例"), 35 WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, 36 CW_USEDEFAULT, CW_USEDEFAULT, 37 CW_USEDEFAULT, CW_USEDEFAULT, 38 NULL, NULL, hInstance, NULL 39 ) ; 40 41 //显示窗口 42 ShowWindow( hwnd, iCmdShow ) ; 43 UpdateWindow( hwnd ) ; 44 45 //获取、翻译、分发消息 46 while( GetMessage( &msg, NULL, 0, 0 ) ) 47 { 48 TranslateMessage( &msg ) ; 49 DispatchMessage( &msg ) ; 50 } 51 52 return msg.wParam ; 53 }
窗口过程部分的代码:
1 LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) 2 { 3 static int cxChar, cxCaps, cyChar, cyClient, cxClient, iVscrollPos, iHscrollPos ; 4 //cxChar:平均字符宽度; cxCaps: 大写字母平均宽度; cyChar: 字符高; cyClient、cxClient: 客户区y、x方向尺寸; 5 //iVscrollPos: 竖直方向滚动条滑块位置; iHscrollPos: 水平方向滚动条滑块位置 6 HDC hdc ; 7 RECT rect ; //记录客户区RECT结构 8 int i, x, y; //i循环控制, x记录水平方向坐标, y竖直方向坐标 9 PAINTSTRUCT ps ; 10 TEXTMETRIC tm ; 11 SCROLLINFO si ; //SCROLLINFO结构对象 12 int iMaxLength ; //所有语句中的最大长度 13 14 iMaxLength = GetMaxLength() ; //取得最大长度 15 16 switch(message) 17 { 18 case WM_CREATE: //处理WM_CREATE消息 19 hdc = GetDC(hwnd) ; 20 GetTextMetrics( hdc, &tm ) ; //获取系统字体信息 21 cxChar = tm.tmAveCharWidth ; //获取平均宽度 22 cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2 ; //大写字母平均宽度 23 cyChar = tm.tmHeight + tm.tmExternalLeading ; //字符高度 24 ReleaseDC( hwnd, hdc ); 25 26 return 0 ; 27 28 case WM_SIZE: //处理WM_SIZE 29 GetClientRect( hwnd, &rect ) ; 30 cxClient = LOWORD (lParam) ; 31 cyClient = HIWORD (lParam) ; 32 33 //设置垂直滚动条相关参数 34 si.cbSize = sizeof (si) ; 35 si.fMask = SIF_RANGE | SIF_PAGE ; 36 si.nMin = 0 ; 37 si.nMax = NUMLINES - 1 ; 38 si.nPage = cyClient / cyChar ; 39 SetScrollInfo(hwnd, SB_VERT, &si, TRUE) ; 40 41 //设置水平滚动条相关参数 42 si.cbSize = sizeof (si) ; 43 si.fMask = SIF_RANGE | SIF_PAGE ; 44 si.nMin = 0 ; 45 si.nMax = 2 + iMaxLength ; 46 si.nPage = cxClient / cxCaps ; 47 SetScrollInfo(hwnd, SB_HORZ, &si, TRUE) ; 48 49 return 0 ; 50 51 case WM_VSCROLL: //处理垂直滚动条消息 52 si.cbSize = sizeof (si) ; 53 si.fMask = SIF_ALL ; 54 GetScrollInfo(hwnd, SB_VERT, &si) ; 55 56 iVscrollPos = si.nPos ; //记录当前滑块位置 57 58 switch( LOWORD(wParam) ) //处理滚动条消息 59 { 60 case SB_TOP: //到达顶部 61 si.nPos = si.nMin ; 62 break ; 63 64 case SB_BOTTOM: //到达底部 65 si.nPos = si.nMax ; 66 break ; 67 68 case SB_LINEUP: //上翻一行 69 si.nPos -= 1 ; 70 break ; 71 72 case SB_LINEDOWN: //下翻一行 73 si.nPos += 1 ; 74 break ; 75 76 case SB_PAGEUP: //向上翻一整页 77 si.nPos -= si.nPage ; 78 break ; 79 80 case SB_PAGEDOWN: //向下翻一整页 81 si.nPos += si.nPage ; 82 break ; 83 84 case SB_THUMBTRACK: //移动滑块时 85 si.nPos = si.nTrackPos ; 86 break ; 87 88 default: 89 break; 90 } 91 si.fMask = SIF_POS ; 92 SetScrollInfo(hwnd, SB_VERT, &si, TRUE) ; //重置滑块位置 93 GetScrollInfo(hwnd, SB_VERT, &si) ; 94 if( si.nPos != iVscrollPos ) 95 { 96 ScrollWindow(hwnd, 0, cyChar * (iVscrollPos - si.nPos), NULL, NULL) ; //滚动内容 97 UpdateWindow(hwnd) ; 98 } 99 return 0 ; 100 101 case WM_HSCROLL: //处理水平滚动条消息 102 si.cbSize = sizeof (si) ; 103 si.fMask = SIF_ALL ; 104 GetScrollInfo (hwnd, SB_HORZ, &si) ; 105 106 iHscrollPos = si.nPos ; //记录当前滑块位置 107 switch( LOWORD(wParam) ) 108 { 109 case SB_TOP: //到达顶部 110 si.nPos = si.nMin ; 111 break ; 112 113 case SB_BOTTOM: //到达底部 114 si.nPos = si.nMax ; 115 break ; 116 117 case SB_LINELEFT: //左翻一行 118 si.nPos -= 1 ; 119 break ; 120 121 case SB_LINERIGHT: //右翻一行 122 si.nPos += 1 ; 123 break ; 124 125 case SB_PAGELEFT: //左翻一页 126 si.nPos -= si.nPage ; 127 break ; 128 129 case SB_PAGERIGHT: //右翻一页 130 si.nPos += si.nPage ; 131 break ; 132 133 case SB_THUMBTRACK: //移动滑块时 134 si.nPos = si.nTrackPos ; 135 break ; 136 137 default: 138 break ; 139 } 140 si.fMask = SIF_POS ; 141 SetScrollInfo(hwnd, SB_HORZ, &si, TRUE) ; //重置滑块位置 142 GetScrollInfo(hwnd, SB_HORZ, &si) ; 143 if( si.nPos != iHscrollPos ) 144 { 145 ScrollWindow(hwnd, cxCaps * (iHscrollPos - si.nPos), 0, NULL, NULL) ; //滚动内容 146 UpdateWindow(hwnd) ; 147 } 148 return 0 ; 149 150 case WM_PAINT: //处理WM_PAINT消息 151 hdc = BeginPaint( hwnd, &ps ) ; 152 153 si.cbSize = sizeof (si) ; 154 si.fMask = SIF_POS ; 155 GetScrollInfo(hwnd, SB_VERT, &si) ; 156 iVscrollPos = si.nPos ; //获取当前垂直滑块位置 157 158 GetScrollInfo(hwnd, SB_HORZ, &si) ; 159 iHscrollPos = si.nPos ; //获取当前水平滑块位置 160 161 for( i= 0; i < NUMLINES; i++ ) 162 { 163 y = cyChar * ( i - iVscrollPos ) ; 164 x = cxCaps * ( 0 - iHscrollPos ) ; 165 TextOut( hdc, x, y, statement[i], lstrlen(statement[i]) ) ; //输出文字 166 } 167 168 EndPaint( hwnd, &ps ) ; 169 return 0 ; 170 171 case WM_DESTROY: //处理WM_DESTROY消息 172 PostQuitMessage( 0 ) ; 173 return 0 ; 174 } 175 176 return DefWindowProc( hwnd, message, wParam, lParam ) ; 177 }
看一下成果:
嗯, 这样看起来就好多了, 如果嫌行间距太挤的话我们可以调节字符的高度
cyChar = tm.tmHeight + tm.tmExternalLeading ; //字符高度
使行间距增大些, 这样看起来会更舒服。
好了, 到这里, 一个较为完善的滚动条就完成了。
--------------------
wid, 2012.10.31
上一篇: C语言Windows程序设计->第七天->TextOut与系统字体