From 52b626aa1918c58d2d27e82ac72db12536c13764 Mon Sep 17 00:00:00 2001
From: LAPTOP-SNT8I5JK\Boounion <Chenluhua@qq.com>
Date: 星期三, 20 八月 2025 11:47:48 +0800
Subject: [PATCH] 1.能展开的ListCtrll

---
 SourceCode/Bond/Servo/CExpandableListCtrl.h   |   57 ++++++++
 SourceCode/Bond/Servo/Servo.vcxproj           |    4 
 SourceCode/Bond/Servo/CControlJobDlg.cpp      |   73 ++++++++++
 SourceCode/Bond/Servo/CControlJobDlg.h        |   30 ++++
 SourceCode/Bond/Servo/Servo.vcxproj.filters   |    4 
 SourceCode/Bond/Servo/resource.h              |    0 
 SourceCode/Bond/Servo/Servo.rc                |    0 
 SourceCode/Bond/Servo/CExpandableListCtrl.cpp |  217 +++++++++++++++++++++++++++++++
 SourceCode/Bond/Servo/ServoDlg.cpp            |    4 
 9 files changed, 388 insertions(+), 1 deletions(-)

diff --git a/SourceCode/Bond/Servo/CControlJobDlg.cpp b/SourceCode/Bond/Servo/CControlJobDlg.cpp
new file mode 100644
index 0000000..ec9b31c
--- /dev/null
+++ b/SourceCode/Bond/Servo/CControlJobDlg.cpp
@@ -0,0 +1,73 @@
+锘�// CControlJobDlg.cpp: 瀹炵幇鏂囦欢
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "CControlJobDlg.h"
+#include "afxdialogex.h"
+
+
+// CControlJobDlg 瀵硅瘽妗�
+
+IMPLEMENT_DYNAMIC(CControlJobDlg, CDialogEx)
+
+CControlJobDlg::CControlJobDlg(CWnd* pParent /*=nullptr*/)
+	: CDialogEx(IDD_DIALOG_CONTROL_JOB, pParent)
+{
+
+}
+
+CControlJobDlg::~CControlJobDlg()
+{
+}
+
+void CControlJobDlg::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+	DDX_Control(pDX, IDC_LIST1, m_listCtrl);
+}
+
+
+BEGIN_MESSAGE_MAP(CControlJobDlg, CDialogEx)
+END_MESSAGE_MAP()
+
+
+// CControlJobDlg 娑堟伅澶勭悊绋嬪簭
+
+
+BOOL CControlJobDlg::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+
+    DWORD dwStyle = m_listCtrl.GetExtendedStyle();
+    dwStyle |= LVS_EX_FULLROWSELECT;
+    dwStyle |= LVS_EX_GRIDLINES;
+    m_listCtrl.SetExtendedStyle(dwStyle);
+
+    HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1);
+    ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL);
+
+    // m_list 宸茬粡鏄璇濇涓婄殑 CExpandableListCtrl 鎴愬憳锛堟嫋鎺т欢鏀圭被锛�
+    m_listCtrl.ModifyStyle(0, LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS);
+    m_listCtrl.InsertColumn(0, _T("鍚嶇О"), LVCFMT_LEFT, 260);
+    m_listCtrl.InsertColumn(1, _T("鐘舵��"), LVCFMT_LEFT, 120);
+    m_listCtrl.InsertColumn(2, _T("鎻忚堪"), LVCFMT_LEFT, 260);
+
+    auto* root1 = m_listCtrl.InsertRoot({ _T("EFEM"), _T("Ready"), _T("Front End Module") });
+    m_listCtrl.InsertChild(root1, { _T("Slot #1"), _T("OK"), _T("150mm wafer") });
+    m_listCtrl.InsertChild(root1, { _T("Slot #2"), _T("Empty"), _T("") });
+
+    auto* root2 = m_listCtrl.InsertRoot({ _T("Bonder"), _T("Run"), _T("G1+G2 Process") });
+    auto* ch21 = m_listCtrl.InsertChild(root2, { _T("Job A"), _T("Proc"), _T("Step 1") });
+    m_listCtrl.InsertChild(ch21, { _T("SubStep A1"), _T("Done"), _T("Align") });
+    m_listCtrl.InsertChild(ch21, { _T("SubStep A2"), _T("Run"),  _T("Bond") });
+
+    // 鍒濆璁╅《灞傚睍寮�
+    //root1->expanded = true;
+    //root2->expanded = true;
+
+    m_listCtrl.RebuildVisible();
+
+	return TRUE;  // return TRUE unless you set the focus to a control
+				  // 寮傚父: OCX 灞炴�ч〉搴旇繑鍥� FALSE
+}
diff --git a/SourceCode/Bond/Servo/CControlJobDlg.h b/SourceCode/Bond/Servo/CControlJobDlg.h
new file mode 100644
index 0000000..83763f0
--- /dev/null
+++ b/SourceCode/Bond/Servo/CControlJobDlg.h
@@ -0,0 +1,30 @@
+锘�#pragma once
+#include "CExpandableListCtrl.h"
+
+
+// CControlJobDlg 瀵硅瘽妗�
+
+class CControlJobDlg : public CDialogEx
+{
+	DECLARE_DYNAMIC(CControlJobDlg)
+
+public:
+	CControlJobDlg(CWnd* pParent = nullptr);   // 鏍囧噯鏋勯�犲嚱鏁�
+	virtual ~CControlJobDlg();
+
+protected:
+	CExpandableListCtrl m_listCtrl;
+
+
+// 瀵硅瘽妗嗘暟鎹�
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_DIALOG_CONTROL_JOB };
+#endif
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 鏀寔
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+};
diff --git a/SourceCode/Bond/Servo/CExpandableListCtrl.cpp b/SourceCode/Bond/Servo/CExpandableListCtrl.cpp
new file mode 100644
index 0000000..c15cf59
--- /dev/null
+++ b/SourceCode/Bond/Servo/CExpandableListCtrl.cpp
@@ -0,0 +1,217 @@
+锘�#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;
+}
+
diff --git a/SourceCode/Bond/Servo/CExpandableListCtrl.h b/SourceCode/Bond/Servo/CExpandableListCtrl.h
new file mode 100644
index 0000000..d4ecb51
--- /dev/null
+++ b/SourceCode/Bond/Servo/CExpandableListCtrl.h
@@ -0,0 +1,57 @@
+#pragma once
+#include <vector>
+#include <memory>
+
+class CExpandableListCtrl : public CListCtrl
+{
+    DECLARE_DYNAMIC(CExpandableListCtrl)
+
+public:
+    struct Node {
+        Node* parent = nullptr;
+        std::vector<std::unique_ptr<Node>> children;
+        std::vector<CString> cols; // 各列文本
+        bool expanded = false;
+        int level = 0; // 缩进层级
+
+        Node(int nCols = 1) : cols(nCols) {}
+    };
+
+    CExpandableListCtrl();
+    virtual ~CExpandableListCtrl();
+
+    // 数据构建
+    Node* InsertRoot(const std::vector<CString>& cols);
+    Node* InsertChild(Node* parent, const std::vector<CString>& cols);
+
+    // 展开/折叠
+    void Expand(Node* n);
+    void Collapse(Node* n);
+    void Toggle(Node* n);
+
+    // 刷新可见列表
+    void RebuildVisible();
+
+    // 便捷:通过可见行号取 Node*
+    Node* GetNodeByVisibleIndex(int i) const;
+
+protected:
+    // 消息
+    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
+    afx_msg void OnClick(NMHDR* pNMHDR, LRESULT* pResult);
+    afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
+
+    DECLARE_MESSAGE_MAP()
+
+private:
+    std::vector<std::unique_ptr<Node>> m_roots;   // 顶层节点
+    std::vector<Node*>                 m_visible; // 展开后的可见节点顺序
+    int  m_expanderPadding = 6;   // 首列内侧边距
+    int  m_expanderSize = 10;  // 小三角/方块大小
+    int  m_textGap = 6;
+
+    void appendVisible(Node* n);
+    CRect expanderRectForRow(int row) const; // 首列展开按钮区域
+};
+
+
diff --git a/SourceCode/Bond/Servo/Servo.rc b/SourceCode/Bond/Servo/Servo.rc
index 3e4c1c6..603b23b 100644
--- a/SourceCode/Bond/Servo/Servo.rc
+++ b/SourceCode/Bond/Servo/Servo.rc
Binary files differ
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj b/SourceCode/Bond/Servo/Servo.vcxproj
index b798ed7..ec95643 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj
+++ b/SourceCode/Bond/Servo/Servo.vcxproj
@@ -202,9 +202,11 @@
   <ItemGroup>
     <ClInclude Include="CBaseDlg.h" />
     <ClInclude Include="CControlJob.h" />
+    <ClInclude Include="CControlJobDlg.h" />
     <ClInclude Include="CCustomCheckBox.h" />
     <ClInclude Include="CCollectionEvent.h" />
     <ClInclude Include="CEquipmentPage3.h" />
+    <ClInclude Include="CExpandableListCtrl.h" />
     <ClInclude Include="CGlassPool.h" />
     <ClInclude Include="ChangePasswordDlg.h" />
     <ClInclude Include="CMyStatusbar.h" />
@@ -354,9 +356,11 @@
   <ItemGroup>
     <ClCompile Include="CBaseDlg.cpp" />
     <ClCompile Include="CControlJob.cpp" />
+    <ClCompile Include="CControlJobDlg.cpp" />
     <ClCompile Include="CCustomCheckBox.cpp" />
     <ClCompile Include="CCollectionEvent.cpp" />
     <ClCompile Include="CEquipmentPage3.cpp" />
+    <ClCompile Include="CExpandableListCtrl.cpp" />
     <ClCompile Include="CGlassPool.cpp" />
     <ClCompile Include="ChangePasswordDlg.cpp" />
     <ClCompile Include="CMyStatusbar.cpp" />
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj.filters b/SourceCode/Bond/Servo/Servo.vcxproj.filters
index 1d8a259..1d90814 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj.filters
+++ b/SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -178,6 +178,8 @@
     <ClCompile Include="CPageCollectionEvent.cpp" />
     <ClCompile Include="ProcessJob.cpp" />
     <ClCompile Include="CControlJob.cpp" />
+    <ClCompile Include="CExpandableListCtrl.cpp" />
+    <ClCompile Include="CControlJobDlg.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="AlarmManager.h" />
@@ -362,6 +364,8 @@
     <ClInclude Include="ProcessJob.h" />
     <ClInclude Include="CControlJob.h" />
     <ClInclude Include="SerializeUtil.h" />
+    <ClInclude Include="CExpandableListCtrl.h" />
+    <ClInclude Include="CControlJobDlg.h" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="Servo.rc" />
diff --git a/SourceCode/Bond/Servo/ServoDlg.cpp b/SourceCode/Bond/Servo/ServoDlg.cpp
index fed2ac6..91dc9ec 100644
--- a/SourceCode/Bond/Servo/ServoDlg.cpp
+++ b/SourceCode/Bond/Servo/ServoDlg.cpp
@@ -26,6 +26,7 @@
 #include "CPageVarialbles.h"
 #include "CPageReport.h"
 #include "CPageCollectionEvent.h"
+#include "CControlJobDlg.h"
 
 
 #ifdef _DEBUG
@@ -979,7 +980,8 @@
 		}
 	}
 	else if (id == IDC_BUTTON_JOBS) {
-		AfxMessageBox("IDC_BUTTON_CJOBS");
+		CControlJobDlg dlg;
+		dlg.DoModal();
 	}
 	else if (id == IDC_BUTTON_PORT_CONFIG) {
 		CPortConfigurationDlg dlg;
diff --git a/SourceCode/Bond/Servo/resource.h b/SourceCode/Bond/Servo/resource.h
index a16ac88..87072ee 100644
--- a/SourceCode/Bond/Servo/resource.h
+++ b/SourceCode/Bond/Servo/resource.h
Binary files differ

--
Gitblit v1.9.3