#include "stdafx.h"
|
#include "CExpandableListCtrl.h"
|
|
IMPLEMENT_DYNAMIC(CExpandableListCtrl, CListCtrl)
|
|
CExpandableListCtrl::CExpandableListCtrl() {}
|
CExpandableListCtrl::~CExpandableListCtrl() {}
|
|
BEGIN_MESSAGE_MAP(CExpandableListCtrl, CListCtrl)
|
ON_WM_CREATE()
|
ON_NOTIFY_REFLECT(NM_CLICK, &CExpandableListCtrl::OnClick)
|
ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CExpandableListCtrl::OnCustomDraw)
|
END_MESSAGE_MAP()
|
|
int CExpandableListCtrl::OnCreate(LPCREATESTRUCT lpCreateStruct)
|
{
|
if (CListCtrl::OnCreate(lpCreateStruct) == -1)
|
return -1;
|
|
// 报表风格列举例
|
SetExtendedStyle(GetExtendedStyle()
|
| LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES);
|
|
// 示例列(可在外部设置)
|
if (GetHeaderCtrl() == nullptr || GetHeaderCtrl()->GetItemCount() == 0) {
|
InsertColumn(0, _T("名称"), LVCFMT_LEFT, 260);
|
InsertColumn(1, _T("状态"), LVCFMT_LEFT, 120);
|
InsertColumn(2, _T("描述"), LVCFMT_LEFT, 260);
|
}
|
|
return 0;
|
}
|
|
CExpandableListCtrl::Node* CExpandableListCtrl::InsertRoot(const std::vector<CString>& cols)
|
{
|
auto n = std::make_unique<Node>((int)max(1, (int)cols.size()));
|
for (size_t i = 0; i < cols.size(); ++i) n->cols[i] = cols[i];
|
n->level = 0;
|
Node* raw = n.get();
|
m_roots.emplace_back(std::move(n));
|
return raw;
|
}
|
|
CExpandableListCtrl::Node* CExpandableListCtrl::InsertChild(Node* parent, const std::vector<CString>& cols)
|
{
|
ASSERT(parent);
|
auto n = std::make_unique<Node>((int)max(1, (int)cols.size()));
|
for (size_t i = 0; i < cols.size(); ++i) n->cols[i] = cols[i];
|
n->parent = parent;
|
n->level = parent->level + 1;
|
Node* raw = n.get();
|
parent->children.emplace_back(std::move(n));
|
return raw;
|
}
|
|
void CExpandableListCtrl::appendVisible(Node* n)
|
{
|
m_visible.push_back(n);
|
if (n->expanded) {
|
for (auto& ch : n->children) {
|
appendVisible(ch.get());
|
}
|
}
|
}
|
|
void CExpandableListCtrl::RebuildVisible()
|
{
|
// 1) 重建可见序列
|
m_visible.clear();
|
for (auto& r : m_roots) appendVisible(r.get());
|
|
// 2) 重绘/重填数据
|
SetRedraw(FALSE);
|
DeleteAllItems();
|
|
// 插入可见行
|
for (int i = 0; i < (int)m_visible.size(); ++i) {
|
Node* n = m_visible[i];
|
LVITEM lvi{};
|
lvi.mask = LVIF_TEXT;
|
lvi.iItem = i;
|
lvi.iSubItem = 0;
|
lvi.pszText = const_cast<LPTSTR>((LPCTSTR)(n->cols.empty() ? _T("") : n->cols[0]));
|
InsertItem(&lvi);
|
|
for (int col = 1; col < GetHeaderCtrl()->GetItemCount(); ++col) {
|
CString txt = (col < (int)n->cols.size()) ? n->cols[col] : _T("");
|
SetItemText(i, col, txt);
|
}
|
}
|
SetRedraw(TRUE);
|
Invalidate();
|
}
|
|
void CExpandableListCtrl::Expand(Node* n)
|
{
|
if (!n || n->children.empty()) return;
|
if (!n->expanded) { n->expanded = true; RebuildVisible(); }
|
}
|
|
void CExpandableListCtrl::Collapse(Node* n)
|
{
|
if (!n || n->children.empty()) return;
|
if (n->expanded) { n->expanded = false; RebuildVisible(); }
|
}
|
|
void CExpandableListCtrl::Toggle(Node* n)
|
{
|
if (!n || n->children.empty()) return;
|
n->expanded = !n->expanded;
|
RebuildVisible();
|
}
|
|
CExpandableListCtrl::Node* CExpandableListCtrl::GetNodeByVisibleIndex(int i) const
|
{
|
if (i < 0 || i >= (int)m_visible.size()) return nullptr;
|
return m_visible[i];
|
}
|
|
CRect CExpandableListCtrl::expanderRectForRow(int row) const
|
{
|
CRect rc;
|
// 取首列矩形
|
if (!GetSubItemRect(row, 0, LVIR_BOUNDS, rc))
|
return CRect(0, 0, 0, 0);
|
|
Node* n = const_cast<CExpandableListCtrl*>(this)->GetNodeByVisibleIndex(row);
|
int indent = (n ? n->level : 0);
|
|
// 缩进:每级给 16px
|
int left = rc.left + m_expanderPadding + indent * 16;
|
CRect box(left, rc.CenterPoint().y - m_expanderSize / 2,
|
left + m_expanderSize, rc.CenterPoint().y + m_expanderSize / 2);
|
return box;
|
}
|
|
void CExpandableListCtrl::OnClick(NMHDR* pNMHDR, LRESULT* pResult)
|
{
|
LPNMITEMACTIVATE pia = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
|
if (pia->iItem >= 0) {
|
CPoint pt = pia->ptAction;
|
|
// 命中展开按钮?
|
CRect expRc = expanderRectForRow(pia->iItem);
|
if (expRc.PtInRect(pt)) {
|
Node* n = GetNodeByVisibleIndex(pia->iItem);
|
if (n && !n->children.empty()) {
|
Toggle(n);
|
}
|
}
|
}
|
*pResult = 0;
|
}
|
|
void CExpandableListCtrl::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
|
{
|
LPNMLVCUSTOMDRAW pCD = reinterpret_cast<LPNMLVCUSTOMDRAW>(pNMHDR);
|
|
switch (pCD->nmcd.dwDrawStage)
|
{
|
case CDDS_PREPAINT:
|
*pResult = CDRF_NOTIFYITEMDRAW | CDRF_NOTIFYSUBITEMDRAW;
|
return;
|
|
case CDDS_ITEMPREPAINT:
|
*pResult = CDRF_NOTIFYSUBITEMDRAW;
|
return;
|
|
case CDDS_ITEMPREPAINT | CDDS_SUBITEM:
|
{
|
int row = (int)pCD->nmcd.dwItemSpec;
|
int col = pCD->iSubItem;
|
CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc);
|
|
// 仅在首列绘制展开按钮与缩进引导
|
if (col == 0) {
|
CRect rc;
|
GetSubItemRect(row, 0, LVIR_BOUNDS, rc);
|
|
// 默认文本让系统画:我们先画按钮和缩进背景,再返回 CDRF_DODEFAULT
|
Node* n = GetNodeByVisibleIndex(row);
|
if (n) {
|
// 绘制展开三角/方块
|
if (!n->children.empty()) {
|
CRect box = expanderRectForRow(row);
|
// 小方框
|
pDC->Rectangle(box);
|
|
// 画“+”或“-”
|
CPen pen(PS_SOLID, 1, RGB(0, 0, 0));
|
CPen* oldPen = pDC->SelectObject(&pen);
|
// 横线
|
pDC->MoveTo(box.left + 2, box.CenterPoint().y);
|
pDC->LineTo(box.right - 2, box.CenterPoint().y);
|
if (!n->expanded) {
|
// 竖线(表示 + 号)
|
pDC->MoveTo(box.CenterPoint().x, box.top + 2);
|
pDC->LineTo(box.CenterPoint().x, box.bottom - 2);
|
}
|
pDC->SelectObject(oldPen);
|
}
|
|
// 把文本左边界右移,留出缩进与按钮空间
|
// 这里不改系统绘制的文本起点,而是通过在文本前置空格的方式处理更简单:
|
// 我们直接改显示文本(性能足够):在 RebuildVisible 时已经填了纯文本。
|
// 如果你要精准控制文本位置,可以改 OWNERDRAW 或自绘文本。
|
}
|
}
|
|
*pResult = CDRF_DODEFAULT;
|
return;
|
}
|
}
|
|
*pResult = CDRF_DODEFAULT;
|
}
|