界面控件 - 滾動條ScrollBar(對滾動條消息和鼠標消息結合講的不錯)

界面是人機交互的門戶,對產品相當重要。在界面開發中只有想不到沒有作不到的,有好的想法,固然要嘗試着作出來。對滾動條的擴展,如今有不少類是的例子。php

VS2015的代碼編輯是很是強大的,其中有一個功能能夠把滾動態變成MinMap,能夠經過Options->Text Editor->C/C++->Scroll Bars中的Behavior選項分類進行打開。html

sublime也有這個功能,但沒有VS的好用。變成MinMap後整個代碼文檔變成一個完整的縮微圖,在你對代碼比較熟悉的狀況下,能夠很是容易判斷並定位到大體的函數位置。同時對特殊狀態,如:錯誤信息,更新信息等進行標識。只要看一眼滾動條就能知道當前文檔的狀況,能夠快速定位到相關文檔位置。git

優勢多多: 當一個文件內容比較長時,在上面標註相關重要內容是很是方便的。用於只要一看滾動條上的相關標示,就能知道相關信息。不用再開闢窗口告訴用戶,他想獲得的有關信息。github

VS可做參考,作這個就比較容易。不知道微軟有沒有註冊版權-_-!!。所以想到擴展Scroll Bar是個不錯的主意,但windows系統並無給這個機會,通常的窗口類都自帶系統滾動條。系統自帶的徹底沒法作出這種效果,只能本身作一個覆蓋系統默認滾動條。windows

1、滾動條結構SCROLLINFO

typedef struct tagSCROLLINFO { UINT cbSize; // 結構尺寸 UINT fMask; // 須要處理或返回的相關選項參數 int nMin; // 滾動條的最小值 int nMax; // 最大滾動範圍 UINT nPage; // 一頁有效行數 int nPos; // 當前滾動條 int nTrackPos; // 拖拽滾筒條時的位置 } SCROLLINFO, *LPCSCROLLINFO; 

正常咱們用到的的滾動條有下面一些元素,畫了張圖更容易理解點。緩存

能夠看到若是咱們默認最小值=0時,實際須要的只有三個滾動條參數,最大能夠滾動的範圍nMax,一頁可顯示的行數nPage和 當前行的顯示位置nPos 這三個參數。函數

一、滾動數量計算

如滾動條能正常滾動到最後一行的位置。有100行記錄,滾動最下面界面顯示最後一行。爲方便計算沒條記錄的行高固定。post

  • nPage = ClientHeight / rowHeight
  • nMax = rowCount - 1 + nPage - 1

正常狀況計算是總行數-1, 就是 nMax的值,應爲要容許滾動到記錄最後一條,所以須要多加一頁的滾動數量。就產生了上面的nMax的公式。ui

// 只要超過1行就能滾動,所以須要多加一頁的行數。 si.nMax = rowCount - 1 + yClient / rowHeight - 1; si.nPage = yClient / rowHeight; 

二、滑塊尺寸計算

滑塊的尺寸的尺寸按照上圖的定義,滾動條的高度除以滾動範圍再乘上每頁數量能夠獲得。spa

滑塊尺寸 = 滾動條高度 / 有效範圍 * 每頁數量

上述計算公式存在另一個問題,若是記錄量很大時就杯具,滑塊會很是小,用戶很難用鼠標抓到滑塊。眼神犀利和伸手敏捷的才能完成這種艱鉅任務。所以爲滑容易定位的最小值。

s = max((float)((r.bottom - r.top) / (si.nMax - si.nMin) * si.nPage), 20.0f); 

三、滑塊位置計算

在圖上看滑塊有效的滑動位置是須要扣除滑塊的大小的。

實際滑動位置 = (滾動條高度 - 滑塊尺寸) / (有效範圍 - 每頁數量 + 1) * 當前行位置

有除法,就會存一些精度問題,因此算出位置後要檢查一下位置是否超出客戶區。

v = 0; if (si.nPos > 0) v = (int)((r.bottom - r.top - s) / (float)(si.nMax - si.nMin + 1 - si.nPage) * si.nPos); // 因爲精度問題,可能滑塊位置會超界。超界就取最大值 if (v && v + (int)s > r.bottom) v = r.bottom - (int)s; 

2、滾動條信息

作滾動條確定須要記錄上述說的相關信息。爲簡易期間直接使用這個結構體記錄static SCROLLINFO si;。在設置和獲取時直接使用。

自定義滾動條不能使用SetScrollInfo 或 GetScrollInfo ,這兩個是給系統提供的。須要另外增長2個消息,系統已經提供了兩個消息,能夠直接使用。SBM_SETSCROLLINFO 和 SBM_GETSCROLLINFO 處理滾動信息。

參數:

  • wParam --- SBM_SETSCROLLINFO 是否須要刷新, SBM_GETSCROLLINFO 無效。
  • lParam --- *SCROLLINFO 結構體指針。

前面在SCROLLINFO 結構中有提到fMask,在設置和獲取中須要使用。

Mask 值:

  • SIF_RANGE --- 設置行數範圍參數 nMin ~ nMax 這兩個參數值有效。
  • SIF_PAGE --- 每頁行數 nPage 參數值有效
  • SIF_POS --- 位置值 nPos 參數值有效
  • SIF_ALL --- 全部參數值都有效。

其餘對應值能夠參考MSDN文檔。

case SBM_SETSCROLLINFO: if (!lParam) return 0; // 設置滾動條信息 psrcsi = (SCROLLINFO *)lParam; if (psrcsi->fMask & SIF_RANGE) { // 設置內容行數 si.nMax = psrcsi->nMax; si.nMin = psrcsi->nMin; } if (psrcsi->fMask & SIF_PAGE) // 每頁能顯示多少行 si.nPage = psrcsi->nPage; if (psrcsi->fMask & SIF_POS) // 行顯示位置 si.nPos = psrcsi->nPos; // wParam = true 刷新滾動區域 if (wParam) InvalidateRect(hwnd, NULL, false); return 0; case SBM_GETSCROLLINFO: //這裏簡單處理,應該和SBM_SETSCROLLINFO同樣判斷獲取的信息。 if (lParam) *(SCROLLINFO *)lParam = si; return 0; 

3、滾動條繪製

有基本的滾動條結構信息後,要繪製出本身的滾動條就很簡單了。須要主意的一點,當滾動條尺寸小到比滑塊尺寸還小時,就不要再繪製滑塊了。

技巧: 在填充色塊時可使用ExtTextOut函數進行填充,在不使用雙緩存的狀況下,能減小閃爍,並且不須要申請畫刷。

static void mlCGFillColor(HDC hdc, RECT *r, unsigned int color) { // 實際使用ExtTextOut要比FillRect填充顏色效果好,能減小繪製中閃爍問題。並且不要申請畫刷。 SetBkColor(hdc, color); ExtTextOut(hdc, 0, 0, ETO_OPAQUE, r, 0, 0, 0); } // WM_PAINT Code // 繪製背景色 GetClientRect(hwnd, &r); mlCGFillColor(hdc, &r, 0xcccccc); // 計算滑塊大小,太小時不繪製 if (r.bottom - r.top > 30 && si.nMax && (si.nMax - si.nMin) >= (int)si.nPage) { // 滑塊計算 // 大小 = 滾動條高度 / 有效範圍 * 每頁數量 // 最小20, 內容比較多,下降用於鼠標定位到滾動條拖動的難度。 s = max((float)((r.bottom - r.top) / (si.nMax - si.nMin) * si.nPage), 20.0f); // 實際滑動位置 // = (滾動條高度 - 滑塊尺寸) / (有效範圍 - 每頁數量 + 1) * 當前行位置 // 實際滾動的位置會比實際少一頁的數量。 // v = 0; if (si.nPos > 0) v = (int)((r.bottom - r.top - s) / (float)(si.nMax - si.nMin + 1 - si.nPage) * si.nPos); // 因爲精度問題,可能滑塊位置會超界。超界就取最大值 if (v && v + (int)s > r.bottom) v = r.bottom - (int)s; // 繪製滑塊 r.left++; r.right--; r.top = v ; r.bottom = r.top + (int)s; // 拖拽時滑塊顏色反一下 mlCGFillColor(hdc, &r, dragState ? 0x999999 : 0x666666e); InflateRect(&r, -1, -1); mlCGFillColor(hdc, &r, dragState ? 0x666666e : 0x999999); } 

4、響應鼠標消息

鼠標消息,主要是左鍵點擊定位、拖拽和滾輪滾動翻頁。

一、點擊定位響應

鼠標左擊滾動條位置,能夠直接定位到對應行。這個功能就是第一個需求,根據標識快速定位到記錄行。實際的核心就是,鼠標所在滾動條的實際位置,計算出對應的行數。

計算方法:
nPos = (鼠標位置 - 半滑塊尺寸) / 每象素滑動量

  • 每象素滑動量 = 有效高度 / 可滑動數量
  • 有效高度 = 總高度 - 滑塊尺寸
  • 可滑動數量 = 總量 - 每頁數量

根據上述公式提取出一個通用方法,計算像素位置位置對應行數的關係函數。

// 計算滾動條位置 static int calcVertThumPos(int postion, SCROLLINFO *si, int s) { float thumsize; // 滑塊的大小 float pxSize; // 每象素可滑動量 int scrollCnt; // 可滑動數量 // 滑塊尺寸 = 滾動條高度 / 有效範圍 * 每頁數量 // 最小 20 thumsize = max((float)((s) / (si->nMax - si->nMin) * si->nPage), 20.0f); if (postion <= (int)(thumsize / 2)) return 0; if (postion >= s - (thumsize / 2)) return (si->nMax - si->nMin + 1) - si->nPage; // 計算方法: // 每象素滑動量 = 有效高度 / 可滑動數量 // 有效高度 = 總高度 - 滑塊尺寸 // 可滑動數量 = 總量 - 每頁數量 scrollCnt = (si->nMax - si->nMin + 1) - si->nPage; pxSize = (float)(s - thumsize) / (float)scrollCnt; // // 計算方法: // 位置 = (鼠標位置 - 半滑塊尺寸) / 每象素滑動量 return (int)((postion - thumsize / 2.0) / pxSize); } 

鼠標左擊事件響應WM_LBUTTONDOWN消息。

si.nTrackPos = calcVertThumPos(GET_Y_LPARAM(lParam), &si, height);
if (si.nPos != si.nTrackPos) PostMessage(GetParent(hwnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hwnd); 

使用calcVertThumPos 函數計算出滾動位置,並經過WM_VSCROLL消息請求滾動。

注意:MSDN中 WM_VSCROLL的lParam參數約定,當自定義控件是一般傳自定義控件的hWnd。用於區分系統默認的仍是自定義控件的。

點擊定位就搞定了。

二、拖拽滑塊滾響應

拖拽滾動須要同時用到3消息WM_LBUTTONDOWNWM_MOUSEMOVE 和WM_LBUTTONUP。當鼠標按下時準備拖拽,拖拽中滾動記錄,鼠標釋放結束拖拽。

正常狀況鼠標按下就須要鎖定鼠標SetCapture,但有時只是點擊一下,並無須要拖拽。或內部處理更多的控制時,直接鎖定不太好。所以在拖拽時加了個dragState的狀態,只有準備拖拽了才進行鎖定鼠標操做。

另一種狀況,某些異常狀況,鎖定的鼠標會丟失。如斷點調試必定會打斷,這時候會出現不想要的狀況。沒在拖拽狀態,鼠標已經鬆開了,滑塊仍是會跟着鼠標移動。

case WM_LBUTTONDOWN: ... dragState = 1; // 準備拖動滾動條 InvalidateRect(hwnd, NULL, false); return 0; case WM_MOUSEMOVE: if (dragState == 1) { SetCapture(hwnd); // 有拖拽準備,鎖定鼠標鼠標 dragState = 2; } else if (dragState == 2) { if (!(wParam & MK_LBUTTON)) { dragState = 0; // 防止中間中斷,致使出現無效拖拽 if (GetCapture() == hwnd) ReleaseCapture(); } else { // 在鎖定狀態下拖動滾動條定位。 si.nTrackPos = calcVertThumPos(GET_Y_LPARAM(lParam), &si, height); if (si.nTrackPos != si.nPos) PostMessage(GetParent(hwnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hwnd); } } return 0; case WM_LBUTTONUP: if (dragState == 2) ReleaseCapture(); // 釋放鼠標鎖定 if (dragState) { dragState = 0; // 清除狀態 InvalidateRect(hwnd, NULL, false); } return 0; 

鼠標滾輪響應

滾動事件WM_MOUSEWHEEL,鼠標滾動翻頁時會有一個精度。慢慢滾動滾輪會上移或下移3行左右,當快速滾動時,會很容易到文檔末尾。這個就是通常說的精度。

這裏默認處理,滾動一次滾輪移動三行。得到滾動行數量後就能夠直接在當前數量nPos上累加。注意一下,不要超界。

// 鼠標滾動支持 accumDelta = GET_WHEEL_DELTA_WPARAM(wParam) / WHEEL_DELTA; // 滾動精度120一個單位 si.nTrackPos = si.nPos - accumDelta * 3; // 每滾一次3行 if (si.nTrackPos < 0) si.nTrackPos = 0; else if (si.nTrackPos > (int)((si.nMax - si.nMin + 1) - si.nPage)) si.nTrackPos = (si.nMax - si.nMin + 1) - si.nPage; if (si.nPos != si.nTrackPos) PostMessage(GetParent(hwnd), WM_VSCROLL, SB_THUMBTRACK, (LPARAM)hwnd); 

~OK 啦

簡單的滾動條就完成,而後後面的標識之類的就能夠在這個基礎上盡情發揮。隨你徹底由你掌控滾動條。

最終的效果:

完整代碼:

蘑菇房 moguf.com

相關消息和API:

http://www.cnblogs.com/gleam/p/5433719.html

相關文章
相關標籤/搜索