在本教程中,将介绍如何在 MFC 应用程序中使用树控件 (CTreeCtrl
) 进行高级定制,包括设置字体、颜色、徽章、图标、节点的高度等。通过这些自定义设置,可以显著提升用户界面的交互性和视觉效果。
1. 树控件基本设置
首先,我们需要初始化树控件并进行一些基础的设置。以下代码展示了如何设置树控件的字体、背景色、选中项、悬停项等。
// 设置树控件的背景色、选中项、悬停项及文本颜色
m_treeCtrl.UpdateFont(FontType::FONT_SEGOE_UI, 16); // 设置字体
m_treeCtrl.SetDefaultBkColor(RGB(240, 240, 240)); // 浅灰色背景
m_treeCtrl.SetSelectedBkColor(RGB(0, 0, 255)); // 蓝色选中项
m_treeCtrl.SetHoverBkColor(RGB(255, 255, 0)); // 黄色悬停项
m_treeCtrl.SetDefaultTextColor(RGB(0, 0, 0)); // 默认黑色文本
m_treeCtrl.SetSelectedTextColor(RGB(255, 0, 255)); // 白色选中文本
m_treeCtrl.SetHoverTextColor(RGB(255, 0, 0)); // 红色悬停项文本
在这里,我们设置了树控件的字体为 Segoe UI,字号为 16。然后定义了不同状态下的背景色和文本颜色,包括默认状态、选中状态和悬停状态的颜色。
2. 设置树项的高度
通过调用 SetItemHeight
方法,我们可以设置树控件中每个树项的高度。此方法允许我们对树项进行自定义布局,使界面更符合设计要求。
// 设置树项高度
m_treeCtrl.SetItemHeight(50);
这段代码将树项的高度设置为 50,使得每个节点的显示区域更大,适合显示更多内容。
3. 插入节点并设置样式
树控件允许我们动态插入节点,并为每个节点设置不同的样式。以下是如何插入根节点和子节点,并为其设置背景色、图标、徽章等。
插入根节点
// 插入根节点及其子节点
HTREEITEM hRoot = m_treeCtrl.InsertItem(_T("Root Node"));
m_treeCtrl.SetNodeBkColor(hRoot, RGB(100, 100, 255)); // 蓝色背景
m_treeCtrl.SetItemIcon(hRoot, m_hIcon); // 设置图标
在此,我们插入了一个名为“Root Node”的根节点,并设置了它的背景色为蓝色,以及图标。
插入子节点并设置徽章
// 插入子节点及设置徽章
HTREEITEM hChild = m_treeCtrl.InsertItem(_T("Child Node"), hRoot);
m_treeCtrl.SetItemBadge(hChild, 20, 20, RGB(255, 0, 0), RGB(255, 255, 255)); // 红色徽章背景,白色徽章前景
m_treeCtrl.ShowItemBadgeNumber(hChild, 5); // 显示数字 5
m_treeCtrl.SetNodeTextColor(hChild, RGB(255, 0, 0));
此段代码展示了如何插入一个子节点并为其设置一个徽章,徽章的背景色为红色,前景色为白色,并且显示了数字 5。此外,还修改了子节点的文本颜色为红色。
插入圆点徽章节点
// 插入圆点徽章节点
HTREEITEM hDotNode = m_treeCtrl.InsertItem(_T("Dot Node"), hRoot);
m_treeCtrl.SetItemBadge(hDotNode, 20, 20, RGB(255, 0, 0), RGB(255, 255, 255)); // 设置徽章背景色和前景色
m_treeCtrl.ShowItemBadgeDotMode(hDotNode, 100, RGB(255, 0, 0), RGB(255, 255, 255)); // 显示圆点徽章
在这里,我们插入了一个带圆点徽章的节点,并为其设置了一个红色的圆点徽章,背景色为红色,前景色为白色。
插入加粗字体节点
// 插入加粗字体节点
HTREEITEM hBoldNode = m_treeCtrl.InsertItem(_T("Bold Node"), hRoot);
m_treeCtrl.SetItemBold(hBoldNode); // 设置加粗字体
我们还展示了如何将某个节点的字体设置为加粗样式,突出显示该节点。
4. 展开根节点
插入完所有节点后,我们可以使用 Expand
方法展开根节点,显示所有子节点。
// 展开根节点
m_treeCtrl.Expand(hRoot, TVE_EXPAND);
这会将根节点展开,显示其子节点,使得树结构更加清晰可见。
5. 头文件和源文件
以下是一些需要添加的代码结构,用于实现上述功能:
头文件代码
#if !defined(AFX_APREDTREECTRL_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_)
#define AFX_APREDTREECTRL_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_
#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
#include <afxcmn.h>
#include <map>
#include <vector>
/**
* @brief 消息宏,用于向父窗口发送树项目单击的通知。
*
* 当用户单击树控件的项目时,父窗口会收到该消息。
*/
#define ID_MSG_TREE_CLICK_ITEM WM_USER + 2080
/**
* @brief 字体类型枚举。
*/
enum class FontType {
FONT_TAHOMA,
FONT_SEGOE_UI,
FONT_MS_SANS_SERIF,
FONT_ARIAL,
FONT_WINGDINGS
// 可以继续扩展其他字体
};
/**
* @brief CApredTreeCtrl 类
*
* CApredTreeCtrl 是一个继承自 MFC 的 CTreeCtrl 的自定义控件,扩展了以下功能:
* - 为树控件的每个项目添加徽章(数字徽章、小圆点等)
* - 支持项目的加粗显示
* - 支持设置项目图标
* - 自定义项目的背景颜色、文本颜色和悬停效果
*/
class CApredTreeCtrl : public CTreeCtrl {
public:
/**
* @brief 树项目徽章的定义结构体。
*
* 描述了项目上的徽章类型、颜色及显示时间。
*/
typedef struct tagBADGE {
HTREEITEM hTreeItem; ///< 树项目的句柄
COLORREF badgeBackground; ///< 徽章背景颜色
COLORREF badgeForeground; ///< 徽章前景(文字)颜色
int width; ///< 徽章宽度
int height; ///< 徽章高度
int type; ///< 徽章类型:0(隐藏),1(小圆点),2(数字)
int number; ///< 数字徽章显示的数字(仅当类型为数字时有效)
int showTime; ///< 徽章的显示时间,单位为秒(0 表示永久显示)
} BADGE;
/**
* @brief 动态声明宏,用于支持动态创建和 RTTI。
*/
DECLARE_DYNAMIC(CApredTreeCtrl)
/**
* @brief 构造函数:初始化控件的默认样式和资源。
*/
CApredTreeCtrl();
/**
* @brief 析构函数:释放分配的资源。
*/
virtual ~CApredTreeCtrl();
/**
* @brief 设置树项目的徽章。
*
* @param hItem 树项目句柄
* @param width 徽章的宽度
* @param height 徽章的高度
* @param badgeBackground 徽章的背景颜色
* @param badgeForeground 徽章的前景(文字)颜色
*/
void SetItemBadge(HTREEITEM hItem, int width = 20, int height = 20, COLORREF badgeBackground = RGB(255, 255, 255), COLORREF badgeForeground = RGB(0, 0, 0));
/**
* @brief 显示树项目的数字徽章。
*
* @param hItem 树项目句柄
* @param number 徽章中显示的数字
* @param badgeBackground 徽章的背景颜色,默认为白色
* @param badgeForeground 徽章的前景(字体)颜色,默认为黑色
*/
void ShowItemBadgeNumber(HTREEITEM hItem, int number, COLORREF badgeBackground = RGB(255, 255, 255), COLORREF badgeForeground = RGB(0, 0, 0));
/**
* @brief 显示树项目的小圆点徽章。
*
* @param hItem 树项目句柄
* @param nSecond 徽章显示的持续时间,单位为秒,0 表示永久显示
* @param badgeBackground 徽章的背景颜色,默认为白色
* @param badgeForeground 徽章的前景(字体)颜色,默认为黑色
*/
void ShowItemBadgeDotMode(HTREEITEM hItem, int nSecond = 0, COLORREF badgeBackground = RGB(255, 255, 255), COLORREF badgeForeground = RGB(0, 0, 0));
/**
* @brief 隐藏树项目的徽章。
*
* @param hItem 树项目句柄
*/
void HideItemBadge(HTREEITEM hItem);
/**
* @brief 将树项目设置为加粗显示。
*
* @param item 树项目句柄
*/
void SetItemBold(HTREEITEM item);
/**
* @brief 取消树项目的加粗显示。
*
* @param item 树项目句柄
*/
void CancelItemBold(HTREEITEM item);
/**
* @brief 检查树项目是否为加粗状态。
*
* @param item 树项目句柄
* @return BOOL 如果项目是加粗的,返回 TRUE;否则返回 FALSE。
*/
BOOL FindBoldItem(HTREEITEM item);
/**
* @brief 设置树项目的图标。
*
* @param hItem 树项目句柄
* @param hIcon 图标句柄
*/
void SetItemIcon(HTREEITEM hItem, HICON hIcon);
/**
* @brief 设置默认背景色。
*
* @param color 背景色值
*/
void SetDefaultBkColor(COLORREF color);
/**
* @brief 设置选中项的背景色。
*
* @param color 选中项的背景色值
*/
void SetSelectedBkColor(COLORREF color);
/**
* @brief 设置悬停项的背景色。
*
* @param color 悬停项的背景色值
*/
void SetHoverBkColor(COLORREF color);
/**
* @brief 设置默认文本颜色。
*
* @param color 文本颜色值
*/
void SetDefaultTextColor(COLORREF color);
/**
* @brief 设置选中文本的颜色。
*
* @param color 选中文本的颜色值
*/
void SetSelectedTextColor(COLORREF color);
/**
* @brief 设置悬停项的文本颜色。
*
* @param color 悬停项文本的颜色值
*/
void SetHoverTextColor(COLORREF color);
/**
* @brief 为特定节点设置背景色。
*
* @param hItem 树项目句柄
* @param color 节点的背景色
*/
void SetNodeBkColor(HTREEITEM hItem, COLORREF color);
/**
* @brief 为特定节点设置文本颜色。
*
* @param hItem 树项目句柄
* @param color 节点的文本颜色
*/
void CApredTreeCtrl::SetNodeTextColor(HTREEITEM hItem, COLORREF color);
/**
* @brief 刷新树控件,更新颜色显示。
*/
void Refresh();
/**
* @brief 刷新指定的树项目,更新颜色显示。
*
* @param hItem 树项目句柄
*/
void RefreshItem(HTREEITEM hItem);
/**
* @brief 设置字体。
*
* @param eFont 字体类型
* @param nSize 字体大小
*/
void UpdateFont(FontType eFont, int nSize);
protected:
/**
* @brief 绘制树项目右侧的展开/收起按钮。
*
* @param hItem 树项目句柄
* @param hDC 设备上下文句柄
* @param pRect 按钮的绘制区域
*/
void DrawItemButton(HTREEITEM hItem, HDC hDC, CRect* pRect);
/**
* @brief 绘制树控件中的所有可见项目。
*
* @param hDC 设备上下文句柄
*/
void DrawItems(HDC hDC);
/**
* @brief 绘制树项目的背景。
*
* @param hDC 设备上下文句柄
* @param hItem 树项目句柄
* @param pRect 绘制区域
*/
void DrawBadge(HTREEITEM hItem, HDC hDC, CRect* pRect, const BADGE& badge);
// 私有数据成员
std::map<HTREEITEM, COLORREF> m_itemTextColors;///< 节点文本颜色存储
std::map<HTREEITEM, COLORREF> m_itemBkColors; ///< 节点背景色存储
std::map<HTREEITEM, BADGE> m_badges; ///< 存储每个树项目的徽章信息
std::map<HTREEITEM, HICON> m_icons; ///< 存储每个树项目的图标信息
std::vector<HTREEITEM> m_itemBolds; ///< 存储需要加粗显示的树项目
HBRUSH m_hBrushItem[3]; ///< 树项目背景刷子(默认、选中、悬停)
COLORREF m_crItemBk[3]; ///< 树项目背景颜色(默认、选中、悬停)
COLORREF m_crText[3]; ///< 树项目文本颜色(默认、选中、悬停)
HBRUSH m_hBrushBtn[3]; ///< 按钮的背景刷子
HPEN m_hPenItem[3]; ///< 树项目边框画笔(默认、选中、悬停)
HTREEITEM m_hHoverItem; ///< 当前鼠标悬停的树项目句柄
BOOL m_bTracking; ///< 鼠标是否正在跟踪树项目(用于悬停效果)
CFont m_font; ///< 树控件的字体
public:
// MFC 消息映射
DECLARE_MESSAGE_MAP()
/**
* @brief 重载的 WM_PAINT 消息处理函数,用于自定义绘制树控件。
*/
afx_msg void OnPaint();
/**
* @brief 重载的 WM_MOUSEMOVE 消息处理函数,用于处理鼠标移动事件。
*/
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
/**
* @brief 重载的 WM_MOUSEHOVER 消息处理函数,用于处理鼠标悬停事件。
*/
afx_msg void OnMouseHover(UINT nFlags, CPoint point);
/**
* @brief 重载的 WM_MOUSELEAVE 消息处理函数,用于处理鼠标移出控件的事件。
*/
afx_msg void OnMouseLeave();
/**
* @brief 重载的 WM_TIMER 消息处理函数,用于徽章的显示时间控制。
*/
afx_msg void OnTimer(UINT_PTR nIDEvent);
/**
* @brief 重载的 WM_LBUTTONDOWN 消息处理函数,用于处理鼠标左键单击事件。
*/
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
/**
* @brief 重载的 WM_CREATE 消息处理函数,用于初始化定时器。
*/
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
/**
* @brief 重载的 PreSubclassWindow 函数,用于初始化定时器和其他资源。
*/
virtual void PreSubclassWindow();
/**
* @brief 重载的 WM_DESTROY 消息处理函数,用于释放资源。
*/
afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
};
#endif // !defined(AFX_APREDTREECTRL_H__A4EABEC5_2E8C_11D1_B79F_00805F9ECE10__INCLUDED_)
源文件代码
#include "pch.h"
#include "ApredTreeCtrl.h"
#define ROFFSET 7 // 按钮距离右侧的偏移量
#define WIDE 10 // 按钮宽度
#define WIDE2 5 // 按钮宽度的一半
#define EXPANDED_WIDE 8 // 按钮展开时的宽度
// 徽章类型定义
#define BADGE_HIDE 0 // 不显示徽章
#define BADGE_DOT 1 // 小圆点徽章
#define BADGE_NUMBER 2 // 数字徽章
#define BADGE_RECTANGLE 3 // 矩形徽章
#define BADGE_TRIANGLE 4 // 三角形徽章
IMPLEMENT_DYNAMIC(CApredTreeCtrl, CTreeCtrl)
/**
* @brief 构造函数:初始化控件默认样式和资源。
*/
CApredTreeCtrl::CApredTreeCtrl() {
// 初始化背景刷子、边框画笔和颜色
for (int i = 0; i < 3; ++i) {
m_hBrushItem[i] = nullptr;
m_hBrushBtn[i] = nullptr;
m_hPenItem[i] = nullptr;
}
// 默认、选中、悬停的背景和文本颜色
m_crItemBk[0] = RGB(255, 255, 255);
m_crItemBk[1] = RGB(228, 229, 234);
m_crItemBk[2] = RGB(236, 237, 241);
m_crText[0] = RGB(28, 28, 28);
m_crText[1] = RGB(28, 28, 28);
m_crText[2] = RGB(28, 28, 28);
m_hHoverItem = nullptr; // 当前没有悬停的项目
m_bTracking = FALSE; // 鼠标未开始跟踪
}
/**
* @brief 析构函数:释放资源。
*/
CApredTreeCtrl::~CApredTreeCtrl() {
for (int i = 0; i < 3; ++i) {
if (m_hBrushItem[i] != nullptr) ::DeleteObject(m_hBrushItem[i]);
if (m_hBrushBtn[i] != nullptr) ::DeleteObject(m_hBrushBtn[i]);
if (m_hPenItem[i] != nullptr) ::DeleteObject(m_hPenItem[i]);
}
}
BEGIN_MESSAGE_MAP(CApredTreeCtrl, CTreeCtrl)
ON_WM_PAINT()
ON_WM_MOUSEMOVE()
ON_WM_MOUSEHOVER()
ON_WM_MOUSELEAVE()
ON_WM_TIMER()
ON_WM_LBUTTONDOWN()
ON_WM_CREATE()
ON_WM_LBUTTONDBLCLK()
END_MESSAGE_MAP()
/**
* @brief 绘制展开/收起按钮。
*/
void CApredTreeCtrl::DrawItemButton(HTREEITEM hItem, HDC hDC, CRect* pRect) {
POINT pt[3]; // 三角形顶点
if ((GetItemState(hItem, TVIS_EXPANDED) & TVIS_EXPANDED) == TVIS_EXPANDED) {
// 项目已展开,绘制向下的三角形
int nBottomOffset = (pRect->Height() - EXPANDED_WIDE) / 2;
pt[0].x = pRect->right - ROFFSET - EXPANDED_WIDE;
pt[0].y = pRect->bottom - nBottomOffset;
pt[1].x = pRect->right - ROFFSET;
pt[1].y = pRect->bottom - nBottomOffset;
pt[2].x = pRect->right - ROFFSET;
pt[2].y = pRect->bottom - nBottomOffset - EXPANDED_WIDE;
} else {
// 项目未展开,绘制向右的三角形
int nBottomOffset = (pRect->Height() - WIDE) / 2;
pt[0].x = pRect->right - ROFFSET - WIDE2;
pt[0].y = pRect->bottom - nBottomOffset - WIDE;
pt[1].x = pRect->right - ROFFSET - WIDE2;
pt[1].y = pRect->bottom - nBottomOffset;
pt[2].x = pRect->right - ROFFSET;
pt[2].y = pRect->bottom - nBottomOffset - WIDE2;
}
::Polygon(hDC, pt, 3); // 绘制三角形
}
/**
* @brief 自定义绘制树控件。
*/
void CApredTreeCtrl::OnPaint() {
HDC hDC, hMemDC;
HBITMAP hBitmap;
RECT rcClient;
for (int i = 0; i < 3; i++) {
m_hBrushItem[i] = CreateSolidBrush(m_crItemBk[i]);
m_hBrushBtn[i] = CreateSolidBrush(m_crText[i]);
m_hPenItem[i] = ::CreatePen(PS_SOLID, 1, m_crText[i]);
}
PAINTSTRUCT ps;
hDC = ::BeginPaint(m_hWnd, &ps); // 开始绘制
GetClientRect(&rcClient);
// 创建内存设备上下文和兼容位图
hMemDC = ::CreateCompatibleDC(hDC);
hBitmap = ::CreateCompatibleBitmap(hDC, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);
::SelectObject(hMemDC, hBitmap);
// 设置背景色
HBRUSH hBrushBK = CreateSolidBrush(m_crItemBk[0]);
::FillRect(hMemDC, &rcClient, hBrushBK);
DeleteObject(hBrushBK);
// 绘制子项
DrawItems(hMemDC);
// 将内存 DC 中的内容复制到窗口 DC
::BitBlt(hDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, hMemDC, 0, 0, SRCCOPY);
// 清理资源
::EndPaint(m_hWnd, &ps);
::DeleteObject(hBitmap);
::DeleteDC(hMemDC);
}
/**
* @brief 绘制树控件的所有可见子项。
*/
void CApredTreeCtrl::DrawItems(HDC hDC) {
HTREEITEM hCurrentItem;
int itemState;
CRect rcClient, rcItem, rcText;
GetClientRect(&rcClient);
//Gdiplus::Graphics graphics(hDC);
//graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
// 统一设置透明背景
::SetBkMode(hDC, TRANSPARENT);
// 获取第一个可见项
hCurrentItem = GetFirstVisibleItem();
do {
if (GetItemRect(hCurrentItem, &rcItem, FALSE) && GetItemRect(hCurrentItem, &rcText, TRUE)) {
rcText.right = rcItem.right - 16;
rcText.left += 16;
CRect fillRect(0, rcItem.top, rcClient.right, rcItem.bottom);
if (rcItem.top > rcClient.bottom) {
break;
}
// 背景颜色
COLORREF nodeBackgroundColor = m_crItemBk[0]; // 默认背景色
if (m_itemBkColors.find(hCurrentItem) != m_itemBkColors.end()) {
nodeBackgroundColor = m_itemBkColors[hCurrentItem]; // 特定节点背景色
}
// 绘制背景:根据选中、悬停或特定节点背景色填充
itemState = GetItemState(hCurrentItem, TVIF_STATE);
COLORREF crText = m_crText[0];
if (m_itemTextColors.find(hCurrentItem) != m_itemTextColors.end()) {
crText = m_itemTextColors[hCurrentItem]; // 特定节点背景色
}
if (itemState & TVIS_SELECTED) {
crText = m_crText[1];
HRGN hRgn = CreateRoundRectRgn(rcItem.left, rcItem.top, rcItem.right, rcItem.bottom, 4, 4);
::FillRgn(hDC, hRgn, m_hBrushItem[1]);
::DeleteObject(hRgn);
}
else if (hCurrentItem == m_hHoverItem) {
crText = m_crText[2];
HRGN hRgn = CreateRoundRectRgn(rcItem.left, rcItem.top, rcItem.right, rcItem.bottom, 4, 4);
::FillRgn(hDC, hRgn, m_hBrushItem[2]);
::DeleteObject(hRgn);
}
else if (nodeBackgroundColor != m_crItemBk[0]) {
// 为特定节点设置背景色
HRGN hRgn = CreateRoundRectRgn(rcItem.left, rcItem.top, rcItem.right, rcItem.bottom, 4, 4);
::FillRgn(hDC, hRgn, ::CreateSolidBrush(nodeBackgroundColor));
::DeleteObject(hRgn);
}
// 处理根节点的三角形按钮和文本
if (hCurrentItem == GetRootItem()) {
::SetBkMode(hDC, TRANSPARENT); // 确保背景透明
CRect rcBtn = rcText;
rcBtn.right = rcText.left - 2;
rcBtn.left = rcBtn.right - 30;
rcBtn.left -= 8;
DrawItemButton(hCurrentItem, hDC, &rcBtn); // 绘制展开/收起按钮
}
// 处理其他节点的展开按钮
else if (ItemHasChildren(hCurrentItem)) {
CRect rcBtn = rcText;
rcBtn.right = rcText.left - 2;
rcBtn.left = rcBtn.right - 30;
rcBtn.left -= 8;
DrawItemButton(hCurrentItem, hDC, &rcBtn); // 绘制展开/收起按钮
}
// 绘制图标(如果存在)
if (m_icons.find(hCurrentItem) != m_icons.end()) {
HICON hIcon = m_icons[hCurrentItem];
DrawIconEx(hDC, 16, (rcItem.bottom - rcItem.top - 32) / 2, hIcon, 32, 32, 0, 0, DI_NORMAL);
rcText.left += 32;
}
// 创建加粗字体
LOGFONT lf;
::GetObject(m_font, sizeof(LOGFONT), &lf);
lf.lfWeight = FW_BOLD; // 设置加粗
HFONT hFontBold = CreateFontIndirect(&lf); // 创建加粗字体
// 绘制文本
::SetTextColor(hDC, crText);
CString strText = GetItemText(hCurrentItem);
// 如果是加粗文本,选择加粗字体
if (FindBoldItem(hCurrentItem)) {
::SelectObject(hDC, hFontBold);
::DrawText(hDC, strText, strText.GetLength(), &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
::DeleteObject(hFontBold); // 释放加粗字体资源
}
else {
::SelectObject(hDC, m_font);
::DrawText(hDC, strText, strText.GetLength(), &rcText, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
}
// 是否有小圆点(如徽章)
if (m_badges.find(hCurrentItem) != m_badges.end()) {
BADGE& badge = m_badges[hCurrentItem];
int x = rcItem.right - 18 - badge.width;
int y = rcItem.top + (rcItem.Height() - badge.height) / 2;
CRect rcBadge;
rcBadge.left = x;
rcBadge.right = rcBadge.left + badge.width;
rcBadge.top = y;
rcBadge.bottom = rcBadge.top + badge.height;
if (badge.type == BADGE_NUMBER) {
::SetTextColor(hDC, badge.badgeForeground);
char szBuffer[32];
sprintf_s(szBuffer, 32, "%d%s", min(badge.number, 9), badge.number > 9 ? "+" : "");
DrawText(hDC, CString(szBuffer), (int)strlen(szBuffer), &rcBadge, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
else {
DrawBadge(hCurrentItem, hDC, &rcBadge, badge);
}
}
}
} while ((hCurrentItem = GetNextVisibleItem(hCurrentItem)) != NULL);
}
void CApredTreeCtrl::DrawBadge(HTREEITEM hItem, HDC hDC, CRect* pRect, const BADGE& badge) {
int x = pRect->left; // 徽章的 X 坐标
int y = pRect->top; // 徽章的 Y 坐标
// 调试输出徽章位置
TRACE("Badge position: x = %d, y = %d, width = %d, height = %d\n", x, y, badge.width, badge.height);
// 启用抗锯齿(GDI中的高级图形模式)
SetGraphicsMode(hDC, GM_ADVANCED); // 启用高级图形模式
SetBkMode(hDC, TRANSPARENT); // 背景透明
// 设置笔和画刷,创建抗锯齿效果
HBRUSH hBrush = CreateSolidBrush(badge.badgeBackground);
HPEN hPen = CreatePen(PS_SOLID, 1, badge.badgeForeground); // 设置边框颜色
SelectObject(hDC, hBrush);
SelectObject(hDC, hPen);
// GDI 绘制抗锯齿设置
SetGraphicsMode(hDC, GM_ADVANCED); // 启用高级图形模式
SetBkMode(hDC, TRANSPARENT); // 背景透明
// 根据徽章的类型绘制图形
switch (badge.type) {
case BADGE_DOT:
{
// 绘制圆形徽章
Ellipse(hDC, x, y, x + badge.width, y + badge.height); // 绘制一个圆形
// 使用 m_font 绘制文本
::SelectObject(hDC, m_font.GetSafeHandle()); // 设置字体
::SetTextColor(hDC, badge.badgeForeground);
RECT rcText = { x, y, x + badge.width, y + badge.height };
::SetBkMode(hDC, TRANSPARENT); // 设置透明背景
char szBuffer[32];
sprintf_s(szBuffer, 32, "%d", badge.showTime); // 显示时间或其他数字
DrawText(hDC, CString(szBuffer), -1, &rcText, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
break;
case BADGE_RECTANGLE:
{
// 绘制矩形徽章
Rectangle(hDC, x, y, x + badge.width, y + badge.height); // 绘制矩形
}
break;
case BADGE_TRIANGLE:
{
// 绘制三角形徽章
DrawItemButton(hItem, hDC, pRect);
}
break;
default:
// 如果没有匹配的类型,可以选择默认行为
break;
}
// 清理资源
DeleteObject(hBrush);
DeleteObject(hPen);
// 恢复图形模式
SetGraphicsMode(hDC, GM_COMPATIBLE); // 恢复为兼容模式,关闭抗锯齿
}
/**
* @brief 鼠标移动事件处理:更新当前悬停的项目。
*/
void CApredTreeCtrl::OnMouseMove(UINT nFlags, CPoint point) {
HTREEITEM hHoverItem = HitTest(point); // 根据鼠标位置获取悬停的树项目
// 检查是否需要更新悬停项
if (m_hHoverItem != hHoverItem) {
// 如果之前有悬停项,恢复它的状态
if (m_hHoverItem != NULL) {
// 恢复之前悬停项的状态(比如取消高亮)
RefreshItem(m_hHoverItem);
}
m_hHoverItem = hHoverItem; // 更新当前悬停项目
if (m_hHoverItem != NULL) {
// 更新当前悬停项的状态(比如高亮)
RefreshItem(m_hHoverItem);
}
}
// 追踪鼠标事件(只在第一次进入时设置)
if (!m_bTracking) {
TRACKMOUSEEVENT tme = {};
tme.cbSize = sizeof(tme);
tme.dwFlags = TME_HOVER | TME_LEAVE; // 追踪鼠标悬停和离开事件
tme.hwndTrack = m_hWnd;
tme.dwHoverTime = 10; // 鼠标停留时间阈值
m_bTracking = _TrackMouseEvent(&tme);
}
CTreeCtrl::OnMouseMove(nFlags, point); // 调用基类的默认处理
}
/**
* @brief 鼠标悬停事件处理:可在此添加需要悬停时的逻辑。
*/
void CApredTreeCtrl::OnMouseHover(UINT nFlags, CPoint point) {
// 此处可以添加鼠标悬停特定逻辑
CTreeCtrl::OnMouseHover(nFlags, point);
}
/**
* @brief 鼠标移出控件事件处理:重置悬停状态。
*/
void CApredTreeCtrl::OnMouseLeave() {
if (m_hHoverItem != nullptr) {
// 清空悬停项目并刷新
HTREEITEM hPreviousHoverItem = m_hHoverItem;
m_hHoverItem = nullptr; // 清除悬停项
// 刷新之前悬停项的区域
CRect rcItem;
if (GetItemRect(hPreviousHoverItem, &rcItem, FALSE)) {
InvalidateRect(&rcItem, FALSE); // 仅刷新该项的区域
}
}
m_bTracking = FALSE; // 停止鼠标事件追踪
CTreeCtrl::OnMouseLeave(); // 调用基类的处理方法
}
/**
* @brief 定时器事件处理:控制徽章显示时间。
*/
void CApredTreeCtrl::OnTimer(UINT_PTR nIDEvent) {
if (nIDEvent == 1) { // 检测定时器 ID
for (auto& item : m_badges) {
if (item.second.showTime > 0) { // 如果有计时的徽章
item.second.showTime--; // 减少显示时间
if (item.second.showTime == 0) { // 时间到达时隐藏徽章
item.second.type = BADGE_HIDE;
}
// 只刷新当前徽章所在的项
RefreshItem(item.first);
}
}
}
CTreeCtrl::OnTimer(nIDEvent);
}
/**
* @brief 鼠标左键点击事件处理:选中项目并通知父窗口。
*/
void CApredTreeCtrl::OnLButtonDown(UINT nFlags, CPoint point) {
HTREEITEM hItem = HitTest(point); // 根据鼠标位置获取点击的树项目
if (hItem != nullptr) {
SelectItem(hItem); // 选中点击的项目
GetParent()->SendMessage(ID_MSG_TREE_CLICK_ITEM, (WPARAM)hItem, 0); // 通知父窗口
}
CTreeCtrl::OnLButtonDown(nFlags, point); // 调用基类的默认处理
}
/**
* @brief 鼠标左键双击事件处理:展开或收起树项目。
*/
void CApredTreeCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)
{
HTREEITEM hItem = HitTest(point);
if (hItem != NULL && ItemHasChildren(hItem)) {
// 获取该节点的矩形区域
CRect rcItem;
GetItemRect(hItem, &rcItem, TRUE); // 获取包含文本区域的矩形
if (!rcItem.PtInRect(point)) {
// 检查节点是否已经展开
UINT nState = GetItemState(hItem, TVIS_EXPANDED); // 获取项的状态
if (nState & TVIS_EXPANDED) {
Expand(hItem, TVE_COLLAPSE);
}
else {
Expand(hItem, TVE_EXPAND);
}
// 在展开或收起后,刷新该项
RefreshItem(hItem);
}
}
// 调用父类的处理方法
CTreeCtrl::OnLButtonDblClk(nFlags, point);
}
/**
* @brief 设置树项目的徽章(背景色和前景色)。
*/
void CApredTreeCtrl::SetItemBadge(HTREEITEM hItem, int width /*= 20*/, int height /*= 20*/, COLORREF badgeBackground /*= RGB(255, 255, 255)*/, COLORREF badgeForeground /*= RGB(255, 255, 255)*/) {
if (m_badges.find(hItem) == m_badges.end()) {
// 如果项目没有徽章,创建一个新的徽章记录
BADGE badge = {
hItem, // 树项目句柄
badgeBackground, // 背景颜色
badgeForeground, // 前景颜色
width, // 徽章宽度
height, // 徽章高度
BADGE_DOT, // 徽章类型(小圆点)
0, // 数字徽章的数字
0 // 显示时间(0 表示永久显示)
};
m_badges[hItem] = badge;
} else {
// 更新现有徽章的信息
BADGE& badge = m_badges[hItem];
badge.badgeBackground = badgeBackground;
badge.badgeForeground = badgeForeground;
badge.width = width;
badge.height = height;
}
}
/**
* @brief 显示树项目的数字徽章。
*/
void CApredTreeCtrl::ShowItemBadgeNumber(HTREEITEM hItem, int number, COLORREF badgeBackground /*= RGB(255, 255, 255)*/, COLORREF badgeForeground /*= RGB(0, 0, 0)*/) {
if (m_badges.find(hItem) != m_badges.end()) {
BADGE& badge = m_badges[hItem];
badge.type = BADGE_NUMBER;
badge.number = number;
badge.badgeBackground = badgeBackground;
badge.badgeForeground = badgeForeground;
InvalidateRect(NULL, TRUE); // 刷新控件
}
}
/**
* @brief 显示树项目的小圆点徽章。
*/
void CApredTreeCtrl::ShowItemBadgeDotMode(HTREEITEM hItem, int nSecond /*= 0*/, COLORREF badgeBackground /*= RGB(255, 255, 255)*/, COLORREF badgeForeground /*= RGB(0, 0, 0)*/) {
if (m_badges.find(hItem) != m_badges.end()) {
BADGE& badge = m_badges[hItem];
badge.type = BADGE_DOT;
badge.showTime = nSecond; // 设置显示时长
badge.badgeBackground = badgeBackground;
badge.badgeForeground = badgeForeground;
InvalidateRect(NULL, TRUE);
}
}
/**
* @brief 隐藏树项目的徽章。
*/
void CApredTreeCtrl::HideItemBadge(HTREEITEM hItem) {
if (m_badges.find(hItem) != m_badges.end()) {
BADGE& badge = m_badges[hItem];
badge.type = BADGE_HIDE;
badge.showTime = 0;
InvalidateRect(NULL, TRUE);
}
}
/**
* @brief 将树项目设置为加粗显示。
*/
void CApredTreeCtrl::SetItemBold(HTREEITEM item) {
if (!FindBoldItem(item)) {
m_itemBolds.push_back(item); // 加入加粗列表
InvalidateRect(NULL, TRUE); // 刷新控件
}
}
/**
* @brief 取消树项目的加粗显示。
*/
void CApredTreeCtrl::CancelItemBold(HTREEITEM item) {
for (auto iter = m_itemBolds.begin(); iter != m_itemBolds.end(); ++iter) {
if (*iter == item) {
m_itemBolds.erase(iter); // 从加粗列表中移除
break;
}
}
InvalidateRect(NULL, TRUE); // 刷新控件
}
/**
* @brief 检查树项目是否为加粗状态。
*/
BOOL CApredTreeCtrl::FindBoldItem(HTREEITEM item) {
return std::find(m_itemBolds.begin(), m_itemBolds.end(), item) != m_itemBolds.end();
}
/**
* @brief 初始化时钟或其他资源。
*/
int CApredTreeCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct) {
if (CTreeCtrl::OnCreate(lpCreateStruct) == -1) {
return -1;
}
SetTimer(1, 1000, nullptr); // 设置定时器,间隔 1 秒
return 0;
}
/**
* @brief 设置图标到树项目。
*/
void CApredTreeCtrl::SetItemIcon(HTREEITEM hItem, HICON hIcon) {
m_icons[hItem] = hIcon; // 存储图标
InvalidateRect(NULL, TRUE); // 刷新控件
}
/**
* @brief 子类化窗口前的初始化。
*/
void CApredTreeCtrl::PreSubclassWindow() {
SetTimer(1, 1000, nullptr); // 初始化定时器
CTreeCtrl::PreSubclassWindow();
}
/**
* @brief 设置默认背景色。
*/
void CApredTreeCtrl::SetDefaultBkColor(COLORREF color) {
m_crItemBk[0] = color;
if (m_hBrushItem[0]) {
::DeleteObject(m_hBrushItem[0]);
}
m_hBrushItem[0] = CreateSolidBrush(color);
Refresh();
}
/**
* @brief 设置选中项背景色。
*/
void CApredTreeCtrl::SetSelectedBkColor(COLORREF color) {
m_crItemBk[1] = color;
if (m_hBrushItem[1]) {
::DeleteObject(m_hBrushItem[1]);
}
m_hBrushItem[1] = CreateSolidBrush(color);
Refresh();
}
/**
* @brief 设置悬停项背景色。
*/
void CApredTreeCtrl::SetHoverBkColor(COLORREF color) {
m_crItemBk[2] = color;
if (m_hBrushItem[2]) {
::DeleteObject(m_hBrushItem[2]);
}
m_hBrushItem[2] = CreateSolidBrush(color);
Refresh();
}
/**
* @brief 设置默认文本色。
*/
void CApredTreeCtrl::SetDefaultTextColor(COLORREF color) {
m_crText[0] = color;
Refresh();
}
/**
* @brief 设置选中文本色。
*/
void CApredTreeCtrl::SetSelectedTextColor(COLORREF color) {
m_crText[1] = color;
Refresh();
}
/**
* @brief 设置悬停文本色。
*/
void CApredTreeCtrl::SetHoverTextColor(COLORREF color) {
m_crText[2] = color;
Refresh();
}
/**
* @brief 为特定节点设置背景色。
*/
void CApredTreeCtrl::SetNodeBkColor(HTREEITEM hItem, COLORREF color) {
m_itemBkColors[hItem] = color;
Refresh();
}
/**
* @brief 为特定节点设置文本颜色。
*/
void CApredTreeCtrl::SetNodeTextColor(HTREEITEM hItem, COLORREF color)
{
m_itemTextColors[hItem] = color; // 设置指定节点的文本颜色
Refresh(); // 刷新树控件以应用更改
}
/**
* @brief 刷新控件。
*/
void CApredTreeCtrl::Refresh() {
InvalidateRect(NULL, TRUE); // 强制重绘控件
}
void CApredTreeCtrl::RefreshItem(HTREEITEM hItem)
{
CRect rcItem;
if (GetItemRect(hItem, &rcItem, FALSE)) { // 改为 FALSE,获取整个项的矩形区域
InvalidateRect(&rcItem, FALSE); // 刷新整个项的区域
}
}
void CApredTreeCtrl::UpdateFont(FontType eFont, int nSize)
{
LOGFONT logFont = { 0 };
// 根据 FontType 枚举选择对应的字体
switch (eFont)
{
case FontType::FONT_TAHOMA:
_tcscpy_s(logFont.lfFaceName, _T("Tahoma"));
break;
case FontType::FONT_SEGOE_UI:
_tcscpy_s(logFont.lfFaceName, _T("Segoe UI"));
break;
case FontType::FONT_MS_SANS_SERIF:
_tcscpy_s(logFont.lfFaceName, _T("MS Sans Serif"));
break;
case FontType::FONT_ARIAL:
_tcscpy_s(logFont.lfFaceName, _T("Arial"));
break;
case FontType::FONT_WINGDINGS:
_tcscpy_s(logFont.lfFaceName, _T("Wingdings"));
break;
// 可以继续添加其他字体
default:
_tcscpy_s(logFont.lfFaceName, _T("Segoe UI")); // 默认字体
break;
}
// 设置字体的大小
logFont.lfHeight = -nSize; // 字体大小为负值表示像素大小
logFont.lfQuality = CLEARTYPE_QUALITY; // 清晰字体质量
// 删除旧字体并创建新字体
m_font.DeleteObject();
m_font.CreateFontIndirect(&logFont);
// 设置新的字体到控件
SetFont(&m_font);
}
总结
通过上述代码,我们可以轻松实现树控件的个性化定制,设置节点的字体、颜色、徽章、图标等样式。这些功能不仅提升了用户体验,还能够增强界面的可操作性和美观度。你可以根据项目需求灵活调整设置,以达到最佳效果。如果 GDI 抗锯齿效果不理想,考虑使用 GDI+(Graphics)来绘制圆形和文本,它提供了更高质量的抗锯齿效果。