| ¶Ô±ÈÐÂÎļþ |
| | |
| | | #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 | LVS_EX_DOUBLEBUFFER); |
| | | |
| | | return 0; |
| | | } |
| | | |
| | | void CExpandableListCtrl::PreSubclassWindow() |
| | | { |
| | | // æ¥è¡¨é£æ ¼åä¸¾ä¾ |
| | | SetExtendedStyle(GetExtendedStyle() |
| | | | LVS_EX_FULLROWSELECT | LVS_EX_HEADERDRAGDROP | LVS_EX_GRIDLINES | LVS_EX_DOUBLEBUFFER); |
| | | |
| | | CListCtrl::PreSubclassWindow(); |
| | | } |
| | | |
| | | 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 rcLabel; |
| | | if (!const_cast<CExpandableListCtrl*>(this)->GetSubItemRect(row, 0, LVIR_LABEL, rcLabel)) |
| | | return CRect(0, 0, 0, 0); |
| | | |
| | | Node* n = const_cast<CExpandableListCtrl*>(this)->GetNodeByVisibleIndex(row); |
| | | if (!n || n->children.empty()) |
| | | return CRect(0, 0, 0, 0); // å¶åä¸å ä½ï¼ææ¬å°±ä¸ä¼è¢«å¤æ¨ä¸æ ¼ |
| | | |
| | | const int indent = n->level; |
| | | const int left = rcLabel.left + m_expanderPadding + indent * 16; |
| | | |
| | | return CRect( |
| | | left, |
| | | rcLabel.CenterPoint().y - m_expanderSize / 2, |
| | | left + m_expanderSize, |
| | | rcLabel.CenterPoint().y + m_expanderSize / 2 |
| | | ); |
| | | } |
| | | |
| | | |
| | | 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: |
| | | { |
| | | const int row = (int)pCD->nmcd.dwItemSpec; |
| | | const int col = pCD->iSubItem; |
| | | CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc); |
| | | |
| | | if (col == 0) |
| | | { |
| | | CRect rc; GetSubItemRect(row, 0, LVIR_LABEL, rc); |
| | | Node* n = GetNodeByVisibleIndex(row); |
| | | if (!n) { *pResult = CDRF_DODEFAULT; return; } |
| | | |
| | | // 1) èæ¯/忝é¢è²ï¼ææ¯å¦éä¸ |
| | | const bool selected = (GetItemState(row, LVIS_SELECTED) & LVIS_SELECTED) != 0; |
| | | const bool focusOnCtrl = (GetSafeHwnd() == ::GetFocus()); |
| | | COLORREF bk = selected ? GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHT : COLOR_3DFACE) |
| | | : ListView_GetBkColor(m_hWnd); |
| | | COLORREF txt = selected ? GetSysColor(focusOnCtrl ? COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT) |
| | | : ListView_GetTextColor(m_hWnd); |
| | | |
| | | // ä»
å¨éè¦æ¶å¡«å
èæ¯ï¼é¿å
âé»ä¸çâï¼ |
| | | CBrush bkBrush(bk); |
| | | pDC->FillRect(rc, &bkBrush); |
| | | |
| | | // 2) å±å¼/æå æç¤ºï¼åèæ§é¡¹ç®çå³å¯¹é½åæ æ³ï¼ååç´ å¯¹é½ï¼çº¯GDIï¼ |
| | | if (!n->children.empty()) |
| | | { |
| | | CRect box = expanderRectForRow(row); |
| | | |
| | | // ---- å¯è°åæ°ï¼ä¸æ§ä»£ç å½åä¸è´ ---- |
| | | // å³ä¾§çç½ï¼ä¸ææ¬é´é/ç½æ ¼çº¿ä¿æè·ç¦»ï¼ |
| | | const int ROFFSET = 2; |
| | | // éå/å±å¼çâ宽度â设置ï¼å¥æ°æ´é¡ºç¼ï¼9/11 é½è¡ï¼ |
| | | const int WIDE = max(9, min(min(box.Width(), box.Height()), 13)); // â¶ çè¾¹é¿ |
| | | const int WIDE2 = WIDE / 2; // ä¸å |
| | | const int EXPANDED_WIDE = WIDE; // â¼ çè¾¹é¿ |
| | | |
| | | // 轻微å
缩ï¼é¿å
è´´è¾¹ï¼ä¸ä½ æ§ä»£ç âæé®è¦å·ä¸ä¸âåæï¼ |
| | | box.DeflateRect(1, 1); |
| | | |
| | | // ç»ä¸åå¶æ°å¯¹é½ï¼åå°ååç´ é¯é½¿ |
| | | auto even = [](int v) { return (v & 1) ? (v - 1) : v; }; |
| | | |
| | | // 计ç®âèªä¸åä¸âçåºååç§»ï¼ä¸æ§ TreeCtrl ä¸è´ |
| | | // è¿éç¨ box ä½ä¸º pRect |
| | | POINT pt[3]; |
| | | if (n->expanded) { |
| | | // â¼ |
| | | int nBottomOffset = (box.Height() - EXPANDED_WIDE) / 2; |
| | | pt[0].x = box.right - ROFFSET - EXPANDED_WIDE; |
| | | pt[0].y = box.bottom - nBottomOffset; |
| | | pt[1].x = box.right - ROFFSET; |
| | | pt[1].y = box.bottom - nBottomOffset; |
| | | pt[2].x = box.right - ROFFSET; |
| | | pt[2].y = box.bottom - nBottomOffset - EXPANDED_WIDE; |
| | | } |
| | | else { |
| | | // â¶ |
| | | int nBottomOffset = (box.Height() - WIDE) / 2; |
| | | |
| | | pt[0].x = box.right - ROFFSET - WIDE2; |
| | | pt[0].y = box.bottom - nBottomOffset - WIDE; |
| | | pt[1].x = box.right - ROFFSET - WIDE2; |
| | | pt[1].y = box.bottom - nBottomOffset; |
| | | pt[2].x = box.right - ROFFSET; |
| | | pt[2].y = box.bottom - nBottomOffset - WIDE2; |
| | | } |
| | | |
| | | // ä»
å¡«å
ï¼ä¸æè¾¹ï¼æè¾¹ä¼å éå°é¶æï¼ï¼é¢è²ç¨ txt ä¸ä¸»é¢ä¸è´ |
| | | HGDIOBJ oldPen = pDC->SelectObject(GetStockObject(NULL_PEN)); |
| | | HBRUSH hBrush = CreateSolidBrush(txt); |
| | | HGDIOBJ oldBrush = pDC->SelectObject(hBrush); |
| | | |
| | | pDC->Polygon(pt, 3); |
| | | |
| | | pDC->SelectObject(oldPen); |
| | | pDC->SelectObject(oldBrush); |
| | | DeleteObject(hBrush); |
| | | } |
| | | |
| | | |
| | | |
| | | // 3) ææ¬ï¼åºäºé¦ååºåå³ç§»ï¼åºåæ¯å¦æåèç¹ï¼ |
| | | const int indentPx = n->level * 14; |
| | | const int baseLeft = rc.left + m_expanderPadding + indentPx; |
| | | |
| | | CRect textRc = rc; |
| | | if (!n->children.empty()) { |
| | | // æå项ï¼é¢çæé®ä½ + ææ¬é´é |
| | | textRc.left = baseLeft + m_expanderSize + m_textGap; |
| | | } |
| | | else { |
| | | // å¶åè¡ï¼ä¸é¢çæé®ä½ï¼åªç»ä¸ç¹ç¹å¶åé´éï¼è®©å±çº§ç¼©è¿ä»ç¶çæï¼ |
| | | constexpr int kLeafGap = 2; // ä½ å¯è° 0~4 |
| | | textRc.left = baseLeft + kLeafGap; |
| | | } |
| | | |
| | | pDC->SetBkMode(TRANSPARENT); |
| | | pDC->SetTextColor(txt); |
| | | CString txt0 = n->cols.empty() ? _T("") : n->cols[0]; |
| | | pDC->DrawText(txt0, textRc, DT_SINGLELINE | DT_VCENTER | DT_NOPREFIX | DT_END_ELLIPSIS); |
| | | |
| | | |
| | | // ââ ç»å®ä¸è§ä¸ææ¬ä¹åï¼è¡¥ä¸æ¡è¯¥è¡çåºé¨æ¨ªåç½æ ¼çº¿ ââ |
| | | // ä»
å½å¼å¯äº LVS_EX_GRIDLINES æç»å¶ |
| | | if (GetExtendedStyle() & LVS_EX_GRIDLINES) |
| | | { |
| | | // ç¨æ´è¡ boundsï¼ä¿è¯æ¨ªçº¿è´¯ç©¿ææåçå¯è§å®½åº¦ |
| | | CRect rcRow; |
| | | GetSubItemRect(row, 0, LVIR_BOUNDS, rcRow); |
| | | |
| | | // åºè¾¹ y åæ ï¼ä¸ç³»ç»ç½æ ¼çº¿å¯¹é½ï¼ |
| | | const int y = rcRow.bottom - 1; |
| | | |
| | | // é¢è²ä¸ç³»ç»é£æ ¼æ¥è¿ï¼è¥è§å¾åæµ
ï¼å¯æ¢ COLOR_3DSHADOW |
| | | CPen pen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)); |
| | | CPen* oldPen = pDC->SelectObject(&pen); |
| | | |
| | | // 横线ä»è¡å·¦å°è¡å³ï¼å½åå¯è§åºåï¼ |
| | | pDC->MoveTo(rcRow.left, y); |
| | | pDC->LineTo(rcRow.right, y); |
| | | |
| | | pDC->SelectObject(oldPen); |
| | | } |
| | | |
| | | *pResult = CDRF_SKIPDEFAULT; |
| | | return; |
| | | } |
| | | |
| | | // å
¶ä»åé»è®¤ç»å¶ |
| | | *pResult = CDRF_DODEFAULT; |
| | | return; |
| | | } |
| | | |
| | | } |
| | | |
| | | *pResult = CDRF_DODEFAULT; |
| | | } |