介面控制項 - 捲軸ScrollBar

來源:互聯網
上載者:User

標籤:

介面是人機互動的門戶,對產品至關重要。在介面開發中只有想不到沒有做不到的,有好的想法,當然要嘗試著做出來。對捲軸的擴充,現在有很多類是的例子。

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

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

優點多多: 當一個檔案內容比較長時,在上面標註相關重要內容是非常方便的。用於只要一看捲軸上的相關標示,就能知道相關資訊。不用再開闢視窗告訴使用者,他想得到的有關資訊。

有VS可作參考,做這個就比較容易。不知道微軟有沒有註冊著作權-_-!!。因此想到擴充Scroll Bar是個不錯的主意,但windows系統並沒有給這個機會,一般的視窗類別都內建系統捲軸。系統內建的完全無法做出這種效果,只能自己做一個覆蓋系統預設捲軸。

    • 一捲軸結構SCROLLINFO
      • 1滾動數量計算
      • 2滑塊尺寸計算
      • 3滑塊位置計算
    • 二捲軸資訊
    • 三捲軸繪製
    • 四響應滑鼠訊息
      • 1點擊定位響應
      • 2拖拽滑塊滾響應
      • 滑鼠滾輪響應
    • OK 啦

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

正常我們用到的的捲軸有下面一些元素,畫了張圖更容易理解點。

可以看到如果我們預設最小值=0時,實際需要的只有三個捲軸參數,最大可以滾動的範圍nMax,一頁可顯示的行數nPage 和 當前行的顯示位置nPos 這三個參數。

1、滾動數量計算

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

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

正常情況計算是總行數-1, 就是 nMax的值,應為要允許滾動到記錄最後一條,因此需要多加一頁的滾動數量。就產生了上面的nMax的公式。

// 只要超過1行就能滾動,因此需要多加一頁的行數。si.nMax   = rowCount - 1 + yClient / rowHeight - 1;si.nPage  = yClient / rowHeight;
2、滑塊尺寸計算

滑塊的尺寸的尺寸按照的定義,捲軸的高度除以滾動範圍再乘上每頁數量可以得到。

滑塊尺寸 = 捲軸高度 / 有效範圍 * 每頁數量

上述計算公式存在另外一個問題,如果記錄量很大時就杯具,滑塊會非常小,使用者很難用滑鼠抓到滑塊。眼神犀利和伸手敏捷的才能完成這種艱巨任務。因此為滑容易定位的最小值。

 s = max((float)((r.bottom - r.top) / (si.nMax - si.nMin) * si.nPage), 20.0f);
3、滑塊位置計算

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

實際滑動位置 = (捲軸高度 - 滑塊尺寸) / (有效範圍 - 每頁數量 + 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;
二、捲軸資訊

做捲軸肯定需要記錄上述說的相關資訊。為簡易期間直接使用這個結構體記錄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;
三、捲軸繪製

有基本的捲軸結構資訊後,要繪製出自己的捲軸就很簡單了。需要主意的一點,當捲軸尺寸小到比滑塊尺寸還小時,就不要再繪製滑塊了。

技巧: 在填充色塊時可以使用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);}
四、響應滑鼠訊息

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

1、點擊定位響應

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

計算方法:
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 Function Compute出滾動位置,並通過WM_VSCROLL訊息請求滾動。

注意:MSDN中 WM_VSCROLL的lParam參數約定,當自訂控制項是通常傳自訂控制項的hWnd。用於區分系統預設的還是自訂控制項的。

點擊定位就搞定了。

2、拖拽滑塊滾響應

拖拽滾動需要同時用到3訊息WM_LBUTTONDOWN、WM_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 啦

簡單的捲軸就完成,然後後面的標識之類的就可以在這個基礎上盡情發揮。隨你完全由你掌控捲軸。

最終的效果:

完整代碼:
- github/cmacro simple/gdi_win/testscrollcalc/

蘑菇房 moguf.com

相關訊息和API:

  • SBM_SETSCROLLINFO — 設定捲軸資訊
  • SBM_GETSCROLLINFO — 擷取捲軸資訊
  • WM_VSCROLL — 滾動訊息
  • WM_LBUTTONDOWN — 滑鼠左擊按下訊息
  • WM_LBUTTONUP — 滑鼠左擊彈起訊息
  • WM_MOUSEMOVE — 滑鼠移動訊息
  • WM_MOUSEWHEEL — 滑鼠滾輪訊息
  • SetCapture — 滑鼠捕獲
  • ReleaseCapture – 釋放滑鼠
  • ExtTextOut — 文本繪製,有用於底色填充。

介面控制項 - 捲軸ScrollBar

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.