MFC 標準 CToolBar 僅支援單行文字,若需按鈕顯示「新增\n檔案」這樣的多行文字,必須自繪。本文提供一個完整的 CMTToolBar 類別,核心思路:**將文字渲染成位圖,合成至工具列位圖資源中,替換原位圖**,最終調用 ShowText(UINT nIDResource) 一行搞定。

圖一:工具列按鈕顯示多行文字效果 (如「新增\n檔案」)
RT_TOOLBAR) 取得原始按鈕位圖與尺寸。\n 分割多行文字。SetSizes 更新按鈕尺寸,AddReplaceBitmap 替換工具列位圖。BOOL CMTToolBar::ShowText(UINT nIDResource) 完整實現BOOL CMTToolBar::ShowText(UINT nIDResource)
{
// 1. 解析工具列資源結構 (CToolBarData) 取得原始圖示尺寸
HINSTANCE hInst = AfxFindResourceHandle(MAKEINTRESOURCE(nIDResource), RT_TOOLBAR);
HRSRC hRsrc = ::FindResource(hInst, MAKEINTRESOURCE(nIDResource), RT_TOOLBAR);
HGLOBAL hGlobal = LoadResource(hInst, hRsrc);
CToolBarData* pData = (CToolBarData*)LockResource(hGlobal);
CSize sizeImage(pData->wWidth, pData->wHeight);
UnlockResource(hGlobal); FreeResource(hGlobal);
// 2. 取得 ToolBarCtrl DC,建立相容 DC 用於繪製新位圖
CToolBarCtrl& bar = GetToolBarCtrl();
CDC* pDC = bar.GetDC();
CDC dcDst; dcDst.CreateCompatibleDC(pDC);
// 3. 建立繪文字字體 (宋體 12px)
LOGFONT lf = {0}; lf.lfHeight = 12; lf.lfCharSet = GB2312_CHARSET; _tcscpy(lf.lfFaceName, _T("宋體"));
CFont font; font.CreateFontIndirect(&lf);
CFont* pOldFont = dcDst.SelectObject(&font);
// 4. 讀取所有按鈕文字 (資源字串表),按 \n 分行,計算所需最大寬高
int nCount = bar.GetButtonCount();
CStringArray* pStrArrays = new CStringArray[nCount];
int nMaxLines = 0, nBtnCount = 0, nCharHeight = 0;
CSize sizeNewImage(sizeImage.cx, 0);
TBBUTTON tb;
for (int i = 0; i < nCount; ++i) {
bar.GetButton(i, &tb);
if (tb.fsStyle & TBSTYLE_SEP) continue; // 跳過分隔條
CString str; str.LoadString(tb.idCommand);
int nPos = 0;
while ((nPos = str.Find(_T('\n'), nPos)) != -1) { // 簡化分行邏輯
// ... 分割字串存入 pStrArrays[nBtnCount] ...
// 計算 nMaxLines, nCharHeight, sizeNewImage.cx
}
nBtnCount++;
}
sizeNewImage.cy = nMaxLines * nCharHeight + sizeImage.cy; // 新單按鈕高度 = 圖示高 + 文字區高
// 5. 載入原始工具列位圖資源 (LR_CREATEDIBSECTION 保證可寫入)
CBitmap bmpSrc;
BITMAP bm;
bmpSrc.Attach(LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(nIDResource),
IMAGE_BITMAP, 0, 0, LR_DEFAULTSIZE | LR_CREATEDIBSECTION | LR_LOADMAP3DCOLORS));
bmpSrc.GetBitmap(&bm);
int nWidth = bm.bmWidth, nHeight = bm.bmHeight;
// 6. 建立目標位圖 (總寬 = 單按鈕寬 * 按鈕數)
CBitmap bmpDst;
bmpDst.CreateCompatibleBitmap(pDC, sizeNewImage.cx * nBtnCount, sizeNewImage.cy);
CDC dcSrc; dcSrc.CreateCompatibleDC(pDC);
CBitmap* pOldSrc = dcSrc.SelectObject(&bmpSrc);
CBitmap* pOldDst = dcDst.SelectObject(&bmpDst);
// 7. 繪製合成:背景色 -> 原始圖示 (置中) -> 多行文字 (置中)
dcDst.FillSolidRect(0, 0, sizeNewImage.cx * nBtnCount, sizeNewImage.cy, ::GetSysColor(COLOR_BTNFACE));
dcDst.SetBkMode(TRANSPARENT);
int nStartX = (sizeNewImage.cx - sizeImage.cx) / 2;
for (int i = 0; i < nBtnCount; ++i) {
// 繪製原始圖示
dcDst.BitBlt(i * sizeNewImage.cx + nStartX, 0, sizeImage.cx, sizeImage.cy, &dcSrc, i * sizeImage.cx, 0, SRCCOPY);
// 繪製多行文字
for (int j = 0; j < pStrArrays[i].GetSize(); ++j) {
CSize sz = dcDst.GetTextExtent(pStrArrays[i][j]);
int x = i * sizeNewImage.cx + (sizeNewImage.cx - sz.cx) / 2;
int y = sizeImage.cy + j * nCharHeight;
dcDst.TextOut(x, y, pStrArrays[i][j]);
}
}
// 8. 清理 DC,更新工具列尺寸與位圖
dcSrc.SelectObject(pOldSrc); dcDst.SelectObject(pOldDst); dcDst.SelectObject(pOldFont);
bar.ReleaseDC(pDC); delete[] pStrArrays;
SetSizes(sizeNewImage + CSize(7, 7), sizeNewImage); // 更新按鈕/圖示尺寸 (含邊距)
AddReplaceBitmap((HBITMAP)bmpDst.Detach()); // 替換位圖 (關鍵!)
return TRUE;
}
\n 分隔 (如 新增\n檔案)。m_wndToolBar 類型從 CToolBar 改為 CMTToolBar,含標頭檔。OnCreate 中建立工具列後調用:m_wndToolBar.ShowText(IDR_MAINFRAME);
圖二:工具列按鈕文字屬性中使用 \n 換行
| 優點 | 缺點 |
|---|---|
| 無需自繪按鈕 (OwnerDraw),相容標準工具列邏輯 | 文字屬於位圖一部分,無法動態變更 (需重新呼叫 ShowText) |
| 支援任意字體、顏色、對齊、換行 | 高 DPI 下位圖可能模糊 (建議改用向量繪製或高解析度位圖) |
| 實現簡單,一行代碼接入 | 不支援圖示+文字混合佈局 (文字固定在圖示下方) |
此法適合靜態介面、快速原型、不需頻繁動態切換文字的場景。