C语言Windows程序设计-> 第八天-> 滚动条

对上一天学习的回顾:

  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滚动条介绍

  滚动条由滚动滑块以及两端的滚动箭头组成, 滚动条的作用是当需要显示的内容超过窗口客户区大小时提供上下/左右的翻页使用户能够完整的阅读显示信息, 滚动条的图示:

C语言Windows程序设计-> 第八天-> 滚动条

 

 

滚动条理论基础

  1>. 上下滚动?

    以垂直方向的滚动条为例, 当用户向下滚动滚动条时目的是想看到下方更多的的信息, 因此我们需要将下方的信息显示出来, 如何显示更多的信息?

    解决方案: 将不需要被显示的信息显示到客户区外, 令信息自动被Windows截掉, 图示说明:

 

C语言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头文件中, 并计算他到底有多少行以及最长的那行有多少字, 由于文字行数较多, 这里将它在代码框里折叠显示, 定义的头文件如下:

View Code - 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 }

 

  编译运行, 看下成果:

C语言Windows程序设计-> 第八天-> 滚动条

 

  看起来还算不错, 当滚动条向下翻时文字就随着向上滚动, 使下面的文字能够显示出来, 水平的滚动条也是这样, 下面详细说说重点部分的代码:

    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函数折叠显示, 仅将窗口过程函数部分的代码全部显示出来:

View Code - Function - 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 }

 

看一下成果:

C语言Windows程序设计-> 第八天-> 滚动条

嗯, 这样看起来就好多了, 如果嫌行间距太挤的话我们可以调节字符的高度

        cyChar = tm.tmHeight + tm.tmExternalLeading ;                    //字符高度

使行间距增大些, 这样看起来会更舒服。

好了, 到这里, 一个较为完善的滚动条就完成了。

 

--------------------

 

wid, 2012.10.31

 

上一篇: C语言Windows程序设计->第七天->TextOut与系统字体

 

你可能感兴趣的:(windows)