中小型工控專案常面臨「買昂貴組態軟體授權」vs「自行開發圖形元件」的抉擇。本文實作一個可復用的 管道流動控件,支援:
// 管道模式
enum FLOWPATTERN { FP_HORIZONTAL = 0, FP_VERTICAL = 1 };
// 流向
enum DIRECTIONTYPE {
LEFT_TO_RIGHT = 0, RIGHT_TO_LEFT = 1,
TOP_TO_BOTTOM = 2, BOTTOM_TO_TOP = 3
};
// 單元外觀
enum UNITPATTERN {
UP_RECTANGLE = 0, UP_CIRCLE = 1,
UP_DLINE = 2, // >> 箭頭
UP_TRIANGLE = 3
};
原理:管道邊緣與中心使用同色系深/淺色交替(淺-深-淺 → 凹面;深-淺-深 → 凸面),產生視覺立體感。採用 單像素寬/高記憶體位圖 + StretchBlt 拉伸 的高效漸層算法:
void CPipeImpl::DrawGradient(CDC* pDC) {
CRect rcClient; m_pOwner->GetClientRect(&rcClient);
int nWidth = (m_pOwner->GetFlowPattern() == FP_HORIZONTAL) ? rcClient.Height() : rcClient.Width();
// 1. 產生漸層色表
RGBTRIPLE* pRGBTriple = new RGBTRIPLE[nWidth];
m_Gradient.MakeEntries(pRGBTriple, nWidth); // 自訂漸層產生器
// 2. 建立 1 x nWidth (或 nWidth x 1) 記憶體位圖
CBitmap MemBmp; CDC MemDC;
MemBmp.CreateCompatibleBitmap(pDC,
(m_pOwner->GetFlowPattern() == FP_HORIZONTAL) ? 1 : rcClient.Width(),
(m_pOwner->GetFlowPattern() == FP_HORIZONTAL) ? rcClient.Height() : 1);
MemDC.CreateCompatibleDC(pDC);
CBitmap* pOldBmp = MemDC.SelectObject(&MemBmp);
// 3. 逐像素寫入漸層色
for (int i = 0; i < nWidth; ++i) {
CPoint pt = (m_pOwner->GetFlowPattern() == FP_HORIZONTAL) ? CPoint(0, i) : CPoint(i, 0);
COLORREF cr = RGB(pRGBTriple[i].rgbtRed, pRGBTriple[i].rgbtGreen, pRGBTriple[i].rgbtBlue);
MemDC.SetPixelV(pt, cr);
}
// 4. 拉伸貼回實際客戶區
if (m_pOwner->GetFlowPattern() == FP_HORIZONTAL)
pDC->StretchBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
&MemDC, 0, 0, 1, nWidth, SRCCOPY);
else
pDC->StretchBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height(),
&MemDC, 0, 0, nWidth, 1, SRCCOPY);
MemDC.SelectObject(pOldBmp);
delete[] pRGBTriple;
}
優點:管道背景靜態,僅在屬性變更時重繪一次,極大降低動畫幀的繪圖負擔。
CFlowUnit::Draw()依 UNITPATTERN 切換繪圖邏輯,支援矩形、圓形、雙線箭頭(四向)、三角形(四向指向)。關鍵點:
NULL_PEN 避免邊框干擾DIRECTIONTYPE 動態計算,確保箭頭朝向正確>> 形狀CFlowImpl::CalculateFlowUnit()核心邏輯:
CList m_arrayFlowUnit 單元鏈表InitFlowUnit() 依管道長/寬、單元尺寸/間距預填滿管道DeflateRect 位移 UNIT_STEP 像素OnPaint 先畫管道背景到記憶體 DC,再疊加所有單元,最後 BitBlt 一次貼到螢幕,零閃爍
// 水平左→右範例
if (dir == LEFT_TO_RIGHT) {
rcUnit.DeflateRect(UNIT_STEP, 0, -UNIT_STEP, 0); // 右移
if (rcUnit.left >= rcClient.right) { // 移出右邊
// 取頭部單元座標作為新單元生成參考
CRect rcAdd = ...; // 計算左側新位置
CFlowUnit* pNew = new CFlowUnit(); pNew->SetRect(rcAdd); ...
m_arrayFlowUnit.AddHead(pNew); // 新單元加入頭部
m_arrayFlowUnit.RemoveAt(pos); // 移除舊尾部
}
}
// 垂直、反向邏輯對稱,詳見原始碼
CFlowCtrl : public CStatic,處理 WM_PAINT、WM_TIMER、雙緩衝 DCCPipeImpl(管道靜態繪製)與 CFlowImpl(動畫邏輯)兩組件,職責分離SetFlowPattern()、SetDirectionType()、SetUnitPattern()、SetUnitBackColor() 等,支援執行期動態切換外觀

未來可擴充:
完整原始碼已開源,歡迎交流改進。