對上一天學習的回顧:
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頭文件中, 並計算他到底有多少行以及最長的那行有多少字, 由於文字行數較多, 這里將它在代碼框里折疊顯示, 定義的頭文件如下:
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 }
編譯運行, 看下成果:

看起來還算不錯, 當滾動條向下翻時文字就隨着向上滾動, 使下面的文字能夠顯示出來, 水平的滾動條也是這樣, 下面詳細說說重點部分的代碼:
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 }
看一下成果:

嗯, 這樣看起來就好多了, 如果嫌行間距太擠的話我們可以調節字符的高度
cyChar = tm.tmHeight + tm.tmExternalLeading ; //字符高度
使行間距增大些, 這樣看起來會更舒服。
好了, 到這里, 一個較為完善的滾動條就完成了。
--------------------
wid, 2012.10.31
上一篇: C語言Windows程序設計->第七天->TextOut與系統字體
