From 4ef35bf238fc6f7217e4b6de4aee37192ec503ec Mon Sep 17 00:00:00 2001
From: LAPTOP-SNT8I5JK\Boounion <Chenluhua@qq.com>
Date: 星期三, 17 九月 2025 13:36:27 +0800
Subject: [PATCH] 1.自绘CListCtrl用于ProcessJob的Carrier选择
---
SourceCode/Bond/Servo/CCjPage2.cpp | 54 +++
SourceCode/Bond/Servo/Servo.vcxproj | 2
SourceCode/Bond/Servo/Servo.vcxproj.filters | 2
SourceCode/Bond/Servo/resource.h | 0
SourceCode/Bond/Servo/CCarrierSlotSelector.cpp | 673 ++++++++++++++++++++++++++++++++++++++++++++
SourceCode/Bond/Servo/CCarrierSlotSelector.h | 122 ++++++++
SourceCode/Bond/Servo/CCjPage2.h | 4
SourceCode/Bond/Servo/Servo.rc | 0
SourceCode/Bond/Servo/CControlJobManagerDlg.cpp | 19 +
9 files changed, 876 insertions(+), 0 deletions(-)
diff --git a/SourceCode/Bond/Servo/CCarrierSlotSelector.cpp b/SourceCode/Bond/Servo/CCarrierSlotSelector.cpp
new file mode 100644
index 0000000..20be4d4
--- /dev/null
+++ b/SourceCode/Bond/Servo/CCarrierSlotSelector.cpp
@@ -0,0 +1,673 @@
+#include "stdafx.h"
+#include "CCarrierSlotSelector.h"
+
+#define SAFE_PORT(p) ((p) >= 0 && (p) < (int)m_ports.size())
+#define SAFE_SLOT(s) ((s) >= 0 && (s) < m_nSlots)
+
+#ifndef LVS_EX_DOUBLEBUFFER
+#define LVS_EX_DOUBLEBUFFER 0x00010000
+#endif
+
+BEGIN_MESSAGE_MAP(CCarrierSlotSelector, CListCtrl)
+ ON_WM_SHOWWINDOW()
+ ON_WM_WINDOWPOSCHANGED()
+ ON_WM_SIZE()
+ ON_WM_ERASEBKGND()
+ ON_WM_PAINT() // ★ 新增
+ ON_WM_LBUTTONDOWN()
+ ON_WM_MOUSEMOVE()
+ ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, &CCarrierSlotSelector::OnCustomDraw)
+END_MESSAGE_MAP()
+
+CCarrierSlotSelector::CCarrierSlotSelector() {}
+CCarrierSlotSelector::~CCarrierSlotSelector()
+{
+ if ((HFONT)m_fntText) m_fntText.DeleteObject();
+ if ((HFONT)m_fntBold) m_fntBold.DeleteObject();
+ if ((HFONT)m_fntSmall) m_fntSmall.DeleteObject();
+ if ((HIMAGELIST)m_ilRowHeight) m_ilRowHeight.DeleteImageList();
+}
+
+void CCarrierSlotSelector::PreSubclassWindow()
+{
+ CListCtrl::PreSubclassWindow();
+
+ ModifyStyle(LVS_TYPEMASK, LVS_REPORT | LVS_SHOWSELALWAYS);
+ ModifyStyle(LVS_OWNERDRAWFIXED | LVS_OWNERDATA, 0);
+
+ DWORD ex = GetExtendedStyle();
+ ex |= LVS_EX_FULLROWSELECT | LVS_EX_DOUBLEBUFFER;
+ ex &= ~LVS_EX_GRIDLINES; // 关闭系统网格,改自绘
+ SetExtendedStyle(ex);
+
+ // 让默认绘制用我们的底色,进一步降低白底机会(即使哪处走了默认路径)
+ ListView_SetBkColor(m_hWnd, m_colBgNorm);
+ ListView_SetTextBkColor(m_hWnd, m_colBgNorm);
+
+ EnsureFonts();
+ SetRowHeight(m_rowHeight);
+}
+
+void CCarrierSlotSelector::OnShowWindow(BOOL bShow, UINT nStatus)
+{
+ CListCtrl::OnShowWindow(bShow, nStatus);
+ if (bShow && !m_bFirstShown)
+ {
+ m_bFirstShown = TRUE;
+ RedrawWindow(nullptr, nullptr,
+ RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN | RDW_UPDATENOW);
+ }
+}
+
+void CCarrierSlotSelector::OnWindowPosChanged(WINDOWPOS* wp)
+{
+ CListCtrl::OnWindowPosChanged(wp);
+ if (wp && (wp->flags & SWP_SHOWWINDOW))
+ {
+ RedrawWindow(nullptr, nullptr,
+ RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN | RDW_UPDATENOW);
+ }
+}
+
+void CCarrierSlotSelector::EnsureFonts()
+{
+ if (!(HFONT)m_fntText)
+ {
+ LOGFONT lf = { 0 };
+ CFont* pSys = GetFont();
+ if (pSys) pSys->GetLogFont(&lf);
+ else { lf.lfHeight = -14; lstrcpy(lf.lfFaceName, _T("Segoe UI")); }
+ m_fntText.CreateFontIndirect(&lf);
+
+ lf.lfWeight = FW_SEMIBOLD;
+ m_fntBold.CreateFontIndirect(&lf);
+
+ lf.lfWeight = FW_NORMAL; lf.lfHeight = -12;
+ m_fntSmall.CreateFontIndirect(&lf);
+ }
+}
+
+void CCarrierSlotSelector::InitGrid(int nPorts, int nSlots)
+{
+ ASSERT(nPorts >= 1 && nSlots >= 1);
+ m_ports.clear();
+ m_ports.resize(nPorts);
+ m_nSlots = nSlots;
+ for (auto& pc : m_ports) pc.slots.resize(m_nSlots);
+
+ SetRedraw(FALSE);
+ DeleteAllItems();
+ while (GetHeaderCtrl() && GetHeaderCtrl()->GetItemCount() > 0)
+ DeleteColumn(0);
+
+ InsertColumn(0, _T("Slot"), LVCFMT_LEFT, m_slotColWidth);
+ for (int c = 0; c < nPorts; ++c)
+ {
+ CString col; col.Format(_T("Port %d"), c + 1);
+ InsertColumn(c + 1, col, LVCFMT_LEFT, m_portColWidth);
+ m_ports[c].portName = col;
+ m_ports[c].carrierName.Empty();
+ }
+
+ UpdateRowCount();
+ RebuildTexts();
+ SetRedraw(TRUE);
+ if (IsWindowVisible()) Invalidate(FALSE);
+}
+
+void CCarrierSlotSelector::SetColumnWidths(int slotColWidth, int portColWidth)
+{
+ if (slotColWidth > 0) m_slotColWidth = slotColWidth;
+ if (portColWidth > 0) m_portColWidth = portColWidth;
+ SetColumnWidth(0, m_slotColWidth);
+ for (int c = 0; c < (int)m_ports.size(); ++c) SetColumnWidth(c + 1, m_portColWidth);
+ if (IsWindowVisible()) Invalidate(FALSE);
+}
+
+void CCarrierSlotSelector::SetRowHeight(int cy)
+{
+ cy = max(18, min(cy, 64));
+ m_rowHeight = cy;
+
+ if ((HIMAGELIST)m_ilRowHeight) m_ilRowHeight.DeleteImageList();
+ m_ilRowHeight.Create(1, m_rowHeight, ILC_COLOR32, 1, 1);
+
+ // 1×cy 透明位图
+ CBitmap bmp; bmp.CreateBitmap(1, m_rowHeight, 1, 32, nullptr);
+ m_ilRowHeight.Add(&bmp, RGB(0, 0, 0));
+ SetImageList(&m_ilRowHeight, LVSIL_SMALL);
+
+ if (IsWindowVisible()) Invalidate(FALSE);
+}
+
+void CCarrierSlotSelector::UpdateRowCount()
+{
+ int cur = GetItemCount();
+ if (cur < m_nSlots)
+ {
+ for (int i = cur; i < m_nSlots; ++i)
+ {
+ CString s; s.Format(_T("Slot %d"), i + 1);
+ InsertItem(i, s, 0); // 设 iImage=0,行高来自 small image list
+ }
+ }
+ else if (cur > m_nSlots)
+ {
+ for (int i = cur - 1; i >= m_nSlots; --i) DeleteItem(i);
+ }
+
+ // 已有行也统一设 iImage=0
+ LVITEM lvi = { 0 };
+ lvi.mask = LVIF_IMAGE; lvi.iImage = 0;
+ for (int i = 0; i < m_nSlots; ++i) { lvi.iItem = i; SetItem(&lvi); }
+}
+
+void CCarrierSlotSelector::RebuildTexts()
+{
+ // 列头:显示 “已选数/总槽数 + [x]/[ ]”
+ for (int c = 0; c < (int)m_ports.size(); ++c)
+ {
+ int selected = 0;
+ int total = m_nSlots;
+
+ bool anyCheckable = false;
+ bool allChecked = true;
+
+ for (int r = 0; r < m_nSlots; ++r)
+ {
+ const auto& cell = m_ports[c].slots[r];
+ if (cell.hasGlass)
+ {
+ anyCheckable = true;
+ if (cell.checked) ++selected; else allChecked = false;
+ }
+ }
+ if (!anyCheckable) allChecked = false;
+
+ CString head;
+ CString chk = allChecked ? _T("[x]") : _T("[ ]");
+
+ if (!m_ports[c].carrierName.IsEmpty())
+ head.Format(_T("%s (%s) %d/%d %s%s"),
+ m_ports[c].portName.GetString(), m_ports[c].carrierName.GetString(),
+ selected, total,
+ m_ports[c].allocated ? _T("[LOCK] ") : _T(""),
+ chk.GetString());
+ else
+ head.Format(_T("%s %d/%d %s%s"),
+ m_ports[c].portName.GetString(), selected, total,
+ m_ports[c].allocated ? _T("[LOCK] ") : _T(""),
+ chk.GetString());
+
+ LVCOLUMN lvc = { 0 }; lvc.mask = LVCF_TEXT; lvc.pszText = head.GetBuffer();
+ SetColumn(c + 1, &lvc);
+ head.ReleaseBuffer();
+ }
+
+ // 行头文本
+ for (int r = 0; r < m_nSlots; ++r)
+ {
+ CString s; s.Format(_T("Slot %d"), r + 1);
+ SetItemText(r, 0, s);
+ }
+
+ // 单元格临时文本
+ for (int r = 0; r < m_nSlots; ++r)
+ for (int c = 0; c < (int)m_ports.size(); ++c)
+ SetItemText(r, c + 1, GetDisplayId(c, r));
+}
+
+void CCarrierSlotSelector::SetShowMaterialToggle(BOOL bShow)
+{
+ m_bShowMatToggle = bShow;
+ if (IsWindowVisible()) Invalidate(FALSE);
+}
+
+void CCarrierSlotSelector::SetPortInfo(int portIndex, LPCTSTR portName, LPCTSTR carrierName)
+{
+ if (!SAFE_PORT(portIndex)) return;
+ if (portName) m_ports[portIndex].portName = portName;
+ if (carrierName) m_ports[portIndex].carrierName = carrierName;
+ RebuildTexts();
+ if (IsWindowVisible()) Invalidate(FALSE);
+}
+
+void CCarrierSlotSelector::SetPortAllocated(int portIndex, BOOL allocated, LPCTSTR byName)
+{
+ if (!SAFE_PORT(portIndex)) return;
+ m_ports[portIndex].allocated = !!allocated;
+ if (byName) m_ports[portIndex].allocatedBy = byName; else m_ports[portIndex].allocatedBy.Empty();
+
+ if (allocated)
+ for (int r = 0; r < m_nSlots; ++r) m_ports[portIndex].slots[r].checked = false;
+
+ // 刷新整列
+ for (int r = 0; r < m_nSlots; ++r)
+ {
+ CRect rc; GetSubItemRect(r, portIndex + 1, LVIR_BOUNDS, rc);
+ InvalidateRect(&rc, FALSE);
+ }
+ RebuildTexts();
+}
+
+BOOL CCarrierSlotSelector::IsPortAllocated(int portIndex) const
+{
+ if (!SAFE_PORT(portIndex)) return FALSE;
+ return m_ports[portIndex].allocated;
+}
+
+void CCarrierSlotSelector::SetSlotGlass(int portIndex, int slotIndex, BOOL hasGlass, LPCTSTR coreId, int material)
+{
+ if (!SAFE_PORT(portIndex) || !SAFE_SLOT(slotIndex)) return;
+ auto& cell = m_ports[portIndex].slots[slotIndex];
+ cell.hasGlass = !!hasGlass;
+ cell.coreId = coreId ? coreId : _T("");
+ cell.material = (material == MAT_G2) ? MAT_G2 : MAT_G1;
+ if (!cell.hasGlass) cell.checked = false;
+
+ CRect rc; GetSubItemRect(slotIndex, portIndex + 1, LVIR_BOUNDS, rc);
+ InvalidateRect(&rc, FALSE);
+ RebuildTexts();
+}
+
+void CCarrierSlotSelector::SetSlotChecked(int portIndex, int slotIndex, BOOL checked)
+{
+ if (!SAFE_PORT(portIndex) || !SAFE_SLOT(slotIndex)) return;
+ if (m_ports[portIndex].allocated) return;
+ auto& cell = m_ports[portIndex].slots[slotIndex];
+ if (!cell.hasGlass) return;
+
+ cell.checked = !!checked;
+ NotifySelectionChanged(portIndex, slotIndex, cell.checked);
+
+ CRect rc; GetSubItemRect(slotIndex, portIndex + 1, LVIR_BOUNDS, rc);
+ InvalidateRect(&rc, FALSE);
+ RebuildTexts();
+}
+
+BOOL CCarrierSlotSelector::GetSlotChecked(int portIndex, int slotIndex) const
+{
+ if (!SAFE_PORT(portIndex) || !SAFE_SLOT(slotIndex)) return FALSE;
+ return m_ports[portIndex].slots[slotIndex].checked ? TRUE : FALSE;
+}
+
+int CCarrierSlotSelector::GetSlotMaterialType(int portIndex, int slotIndex) const
+{
+ if (!SAFE_PORT(portIndex) || !SAFE_SLOT(slotIndex)) return MAT_G1;
+ return m_ports[portIndex].slots[slotIndex].material;
+}
+
+void CCarrierSlotSelector::SetSlotMaterialType(int portIndex, int slotIndex, int material, BOOL bNotify)
+{
+ if (!SAFE_PORT(portIndex) || !SAFE_SLOT(slotIndex)) return;
+ if (m_ports[portIndex].allocated) return;
+ auto& cell = m_ports[portIndex].slots[slotIndex];
+ if (!cell.hasGlass) return;
+
+ int mt = (material == MAT_G2) ? MAT_G2 : MAT_G1;
+ if (cell.material != mt)
+ {
+ cell.material = mt;
+ if (bNotify) NotifyMaterialChanged(portIndex, slotIndex, cell.material);
+ CRect rc; GetSubItemRect(slotIndex, portIndex + 1, LVIR_BOUNDS, rc);
+ InvalidateRect(&rc, FALSE);
+ }
+}
+
+CString CCarrierSlotSelector::GetDisplayId(int portIndex, int slotIndex) const
+{
+ CString s(_T("—"));
+ if (!SAFE_PORT(portIndex) || !SAFE_SLOT(slotIndex)) return s;
+ const auto& cell = m_ports[portIndex].slots[slotIndex];
+ if (!cell.hasGlass) return s;
+ s.Format(_T("G%d-%s"), (cell.material == MAT_G2) ? 2 : 1, cell.coreId.GetString());
+ return s;
+}
+
+void CCarrierSlotSelector::CheckAllInPort(int portIndex, BOOL checked, BOOL bNotify)
+{
+ if (!SAFE_PORT(portIndex)) return;
+ if (m_ports[portIndex].allocated) return;
+
+ for (int r = 0; r < m_nSlots; ++r)
+ {
+ auto& cell = m_ports[portIndex].slots[r];
+ if (!cell.hasGlass) continue;
+ if (cell.checked != !!checked)
+ {
+ cell.checked = !!checked;
+ if (bNotify) NotifySelectionChanged(portIndex, r, cell.checked);
+ }
+ CRect rc; GetSubItemRect(r, portIndex + 1, LVIR_BOUNDS, rc);
+ InvalidateRect(&rc, FALSE);
+ }
+ RebuildTexts();
+}
+
+BOOL CCarrierSlotSelector::OnEraseBkgnd(CDC* /*pDC*/)
+{
+ // 关键:不让系统擦白背景;用自绘在 PREPAINT 阶段统一铺底
+ return TRUE;
+}
+
+void CCarrierSlotSelector::OnPaint()
+{
+ CPaintDC dc(this);
+ CRect rc; GetClientRect(&rc);
+
+ CDC memDC;
+ memDC.CreateCompatibleDC(&dc);
+ CBitmap bmp;
+ bmp.CreateCompatibleBitmap(&dc, rc.Width(), rc.Height());
+ HGDIOBJ hOldBmp = memDC.SelectObject(bmp);
+
+ // 统一底色(避免任何擦白)
+ memDC.FillSolidRect(rc, m_colBgNorm);
+
+ // 让控件把客户区内容“画到”内存DC(会触发 NM_CUSTOMDRAW,走你现有自绘逻辑)
+ // PRF_ERASEBKGND 让内部如果想擦背景,也在内存里擦,不会闪屏
+ SendMessage(WM_PRINTCLIENT,
+ reinterpret_cast<WPARAM>(memDC.m_hDC),
+ PRF_CLIENT | PRF_ERASEBKGND | PRF_CHILDREN | PRF_OWNED);
+
+ // 回显到屏幕
+ dc.BitBlt(0, 0, rc.Width(), rc.Height(), &memDC, 0, 0, SRCCOPY);
+
+ memDC.SelectObject(hOldBmp);
+ // bmp, memDC 析构自动释放
+}
+
+void CCarrierSlotSelector::OnSize(UINT nType, int cx, int cy)
+{
+ CListCtrl::OnSize(nType, cx, cy);
+}
+
+CRect CCarrierSlotSelector::GetCheckboxRect(const CRect& cell) const
+{
+ int sz = max(14, min(int(m_rowHeight * 0.70), 20));
+ int leftPad = 8;
+ int top = cell.top + (m_rowHeight - sz) / 2;
+ return CRect(cell.left + leftPad, top, cell.left + leftPad + sz, top + sz);
+}
+
+CRect CCarrierSlotSelector::GetMaterialTagRect(const CRect& cell) const
+{
+ int tagH = max(14, min(int(m_rowHeight * 0.65), m_rowHeight - 8));
+ int tagW = 32;
+ int rightPadForDot = 16;
+ int gap = 6;
+ int top = cell.top + (m_rowHeight - tagH) / 2;
+ int right = cell.right - rightPadForDot - gap;
+ return CRect(right - tagW, top, right, top + tagH);
+}
+
+CRect CCarrierSlotSelector::GetStatusDotRect(const CRect& cell) const
+{
+ int d = max(8, min(int(m_rowHeight * 0.42), 12));
+ int rightPad = 6;
+ int top = cell.top + (m_rowHeight - d) / 2;
+ return CRect(cell.right - rightPad - d, top, cell.right - rightPad, top + d);
+}
+
+void CCarrierSlotSelector::DrawFlatCheckbox(CDC* pDC, const CRect& r, bool checked, bool disabled)
+{
+ CBrush br(disabled ? RGB(245, 245, 245) : RGB(255, 255, 255));
+ CPen pen(PS_SOLID, 1, disabled ? RGB(200, 200, 200) : RGB(130, 130, 135));
+ CBrush* pOldB = pDC->SelectObject(&br);
+ CPen* pOldP = pDC->SelectObject(&pen);
+ pDC->RoundRect(r, CPoint(3, 3));
+ pDC->SelectObject(pOldB);
+ pDC->SelectObject(pOldP);
+ br.DeleteObject(); pen.DeleteObject();
+
+ if (!checked) return;
+
+ COLORREF c = disabled ? RGB(160, 160, 160) : RGB(40, 150, 90);
+ CPen penTick(PS_SOLID, max(2, r.Height() / 8), c);
+ CPen* pOld = pDC->SelectObject(&penTick);
+ POINT p1 = { r.left + r.Width() * 2 / 9, r.top + r.Height() * 5 / 9 };
+ POINT p2 = { r.left + r.Width() * 4 / 9, r.top + r.Height() * 7 / 9 };
+ POINT p3 = { r.left + r.Width() * 7 / 9, r.top + r.Height() * 3 / 9 };
+ pDC->MoveTo(p1); pDC->LineTo(p2); pDC->LineTo(p3);
+ pDC->SelectObject(pOld);
+ penTick.DeleteObject();
+}
+
+void CCarrierSlotSelector::OnLButtonDown(UINT nFlags, CPoint point)
+{
+ LVHITTESTINFO ht = { 0 }; ht.pt = point;
+ int row = SubItemHitTest(&ht);
+ int sub = (row >= 0) ? ht.iSubItem : -1;
+
+ if (row >= 0 && sub >= 1)
+ {
+ int port = sub - 1;
+ if (SAFE_PORT(port) && SAFE_SLOT(row))
+ {
+ auto& pc = m_ports[port];
+ auto& cell = pc.slots[row];
+ CRect rcCell; GetSubItemRect(row, sub, LVIR_BOUNDS, rcCell);
+
+ if (pc.allocated || !cell.hasGlass)
+ {
+ CListCtrl::OnLButtonDown(nFlags, point);
+ return;
+ }
+
+ if (GetCheckboxRect(rcCell).PtInRect(point))
+ {
+ cell.checked = !cell.checked;
+ NotifySelectionChanged(port, row, cell.checked);
+ InvalidateRect(&rcCell, FALSE);
+ RebuildTexts();
+ return;
+ }
+
+ if (m_bShowMatToggle && GetMaterialTagRect(rcCell).PtInRect(point))
+ {
+ cell.material = (cell.material == MAT_G1) ? MAT_G2 : MAT_G1;
+ NotifyMaterialChanged(port, row, cell.material);
+ InvalidateRect(&rcCell, FALSE);
+ return;
+ }
+ }
+ }
+
+ CListCtrl::OnLButtonDown(nFlags, point);
+}
+
+void CCarrierSlotSelector::OnMouseMove(UINT nFlags, CPoint point)
+{
+ CListCtrl::OnMouseMove(nFlags, point);
+}
+
+void CCarrierSlotSelector::OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult)
+{
+ NMLVCUSTOMDRAW* pCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);
+
+ switch (pCD->nmcd.dwDrawStage)
+ {
+ case CDDS_PREPAINT:
+ {
+ // 关键:整控件预先铺底,避免初次 hover/显示时“刷白”
+ CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc);
+ CRect rcClient; GetClientRect(&rcClient);
+ pDC->FillSolidRect(rcClient, m_colBgNorm);
+ *pResult = CDRF_NOTIFYITEMDRAW;
+ return;
+ }
+
+ case CDDS_ITEMPREPAINT:
+ *pResult = CDRF_NOTIFYSUBITEMDRAW;
+ return;
+
+ case (CDDS_SUBITEM | CDDS_ITEMPREPAINT):
+ {
+ CDC* pDC = CDC::FromHandle(pCD->nmcd.hdc);
+ int row = (int)pCD->nmcd.dwItemSpec;
+ int sub = pCD->iSubItem;
+
+ CRect rc; GetSubItemRect(row, sub, LVIR_BOUNDS, rc);
+
+ // 背景
+ COLORREF bk = m_colBgNorm;
+ if (sub >= 1)
+ {
+ int port = sub - 1;
+ if (port % 2 == 0) bk = m_colBgAlt; // 交替列
+ if (SAFE_PORT(port) && m_ports[port].allocated) // 锁定列
+ bk = m_colLockBg;
+ }
+ pDC->FillSolidRect(rc, bk);
+
+ // 网格(上/左加粗;右/下细线)
+ CPen penTopLeft(PS_SOLID, (sub >= 1 && ((sub - 1) % 2 == 0)) ? 2 : 1, RGB(210, 214, 220));
+ CPen* pOldPen = pDC->SelectObject(&penTopLeft);
+ pDC->MoveTo(rc.left, rc.top); pDC->LineTo(rc.right, rc.top);
+ pDC->MoveTo(rc.left, rc.top); pDC->LineTo(rc.left, rc.bottom);
+ pDC->SelectObject(pOldPen); penTopLeft.DeleteObject();
+
+ CPen penThin(PS_SOLID, 1, RGB(220, 224, 230));
+ pOldPen = pDC->SelectObject(&penThin);
+ if (sub == GetHeaderCtrl()->GetItemCount() - 1) { pDC->MoveTo(rc.right - 1, rc.top); pDC->LineTo(rc.right - 1, rc.bottom); }
+ if (row == m_nSlots - 1) { pDC->MoveTo(rc.left, rc.bottom - 1); pDC->LineTo(rc.right, rc.bottom - 1); }
+ pDC->SelectObject(pOldPen); penThin.DeleteObject();
+
+ if (sub == 0)
+ {
+ CString s; s.Format(_T("Slot %d"), row + 1);
+ CFont* pOld = pDC->SelectObject(&m_fntBold);
+ pDC->SetBkMode(TRANSPARENT);
+ pDC->SetTextColor(RGB(60, 60, 64));
+ CRect rText = rc; rText.DeflateRect(8, 0, 8, 0);
+ pDC->DrawText(s, rText, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
+ pDC->SelectObject(pOld);
+ }
+ else if (SAFE_PORT(sub - 1) && SAFE_SLOT(row))
+ {
+ int port = sub - 1;
+ const auto& pc = m_ports[port];
+ const auto& cell = pc.slots[row];
+
+ // 扁平复选框
+ CRect rChk = GetCheckboxRect(rc);
+ DrawFlatCheckbox(pDC, rChk, cell.checked, pc.allocated || !cell.hasGlass);
+
+ // 文本
+ CString s = GetDisplayId(port, row);
+ CRect rText = rc;
+ int leftPad = rChk.right + 6;
+ CRect rDot = GetStatusDotRect(rc);
+ CRect rTag = GetMaterialTagRect(rc);
+ int rightPad = rc.right - min(rTag.left - 6, rDot.left - 6);
+ rText.DeflateRect(leftPad - rc.left, 0, rightPad, 0);
+
+ CFont* pOld = pDC->SelectObject(&m_fntText);
+ pDC->SetBkMode(TRANSPARENT);
+ pDC->SetTextColor(cell.hasGlass ? RGB(40, 40, 40) : RGB(150, 150, 150));
+ pDC->DrawText(s, rText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
+ pDC->SelectObject(pOld);
+
+ // 物料标签(可隐藏)
+ if (m_bShowMatToggle)
+ {
+ CRect rT = rTag;
+ COLORREF crBorder = (cell.material == MAT_G2) ? RGB(180, 150, 220) : RGB(120, 160, 220);
+ COLORREF crFill = (cell.material == MAT_G2) ? RGB(243, 235, 250) : RGB(233, 240, 252);
+ COLORREF crText = (cell.material == MAT_G2) ? RGB(90, 60, 150) : RGB(50, 90, 160);
+ if (pc.allocated || !cell.hasGlass) { crBorder = RGB(210, 210, 210); crFill = RGB(245, 245, 245); crText = RGB(160, 160, 160); }
+ CBrush br(crFill); CPen tagPen(PS_SOLID, 1, crBorder);
+ CPen* pOldP = pDC->SelectObject(&tagPen);
+ CBrush* pOldB = pDC->SelectObject(&br);
+ pDC->RoundRect(rT, CPoint(6, 6));
+ pDC->SelectObject(pOldB); pDC->SelectObject(pOldP);
+ br.DeleteObject(); tagPen.DeleteObject();
+
+ CString t; t.Format(_T("G%d"), (cell.material == MAT_G2) ? 2 : 1);
+ CFont* pOldS = pDC->SelectObject(&m_fntSmall);
+ pDC->SetBkMode(TRANSPARENT); pDC->SetTextColor(crText);
+ pDC->DrawText(t, rT, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
+ pDC->SelectObject(pOldS);
+ }
+
+ // 状态点
+ CRect rD = GetStatusDotRect(rc);
+ COLORREF dot = cell.hasGlass ? (pc.allocated ? RGB(215, 160, 60) : RGB(60, 170, 80)) : RGB(160, 160, 160);
+ CBrush bDot(dot); CBrush* pOldB = pDC->SelectObject(&bDot);
+ pDC->Ellipse(rD);
+ pDC->SelectObject(pOldB); bDot.DeleteObject();
+ }
+
+ *pResult = CDRF_SKIPDEFAULT; // 子项完全自绘
+ return;
+ }
+
+ default:
+ break;
+ }
+
+ *pResult = 0;
+}
+
+BOOL CCarrierSlotSelector::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
+{
+ NMHDR* pNM = reinterpret_cast<NMHDR*>(lParam);
+ if (pNM && pNM->hwndFrom == GetHeaderCtrl()->GetSafeHwnd())
+ {
+ switch (pNM->code)
+ {
+ case HDN_ITEMCLICKA:
+ case HDN_ITEMCLICKW:
+ {
+ HD_NOTIFY* phdn = reinterpret_cast<HD_NOTIFY*>(lParam);
+ OnHeaderClick(phdn->iItem);
+ if (pResult) *pResult = 0;
+ return TRUE; // 我们已处理
+ }
+ default: break;
+ }
+ }
+ return CListCtrl::OnNotify(wParam, lParam, pResult);
+}
+
+void CCarrierSlotSelector::OnHeaderClick(int iItem)
+{
+ // iItem: 0=Slot 列,>=1 为 Port 列
+ if (iItem <= 0) return;
+ int port = iItem - 1;
+ if (!SAFE_PORT(port) || m_ports[port].allocated) return;
+
+ // 计算是否“全选”
+ bool anyCheckable = false;
+ bool allChecked = true;
+ for (int r = 0; r < m_nSlots; ++r)
+ {
+ const auto& cell = m_ports[port].slots[r];
+ if (!cell.hasGlass) continue;
+ anyCheckable = true;
+ if (!cell.checked) { allChecked = false; break; }
+ }
+ if (!anyCheckable) return;
+
+ // 切换:若已全选 -> 取消全选;否则 -> 全选
+ CheckAllInPort(port, allChecked ? FALSE : TRUE, TRUE);
+}
+
+// ==== 通知 ====
+void CCarrierSlotSelector::NotifySelectionChanged(int /*port*/, int /*slot*/, BOOL /*checked*/)
+{
+ if (GetParent())
+ {
+ const int code = 0x2001; // CSSN_SELECTION_CHANGED
+ GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), code), (LPARAM)m_hWnd);
+ }
+}
+
+void CCarrierSlotSelector::NotifyMaterialChanged(int /*port*/, int /*slot*/, int /*material*/)
+{
+ if (GetParent())
+ {
+ const int code = 0x2002; // CSSN_MATERIAL_CHANGED
+ GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), code), (LPARAM)m_hWnd);
+ }
+}
diff --git a/SourceCode/Bond/Servo/CCarrierSlotSelector.h b/SourceCode/Bond/Servo/CCarrierSlotSelector.h
new file mode 100644
index 0000000..9ecbf97
--- /dev/null
+++ b/SourceCode/Bond/Servo/CCarrierSlotSelector.h
@@ -0,0 +1,122 @@
+#include "stdafx.h"
+#pragma once
+
+#include <vector>
+#include <string>
+
+#ifndef _AFX
+#include <afxwin.h>
+#include <afxcmn.h>
+#endif
+
+class CCarrierSlotSelector : public CListCtrl
+{
+public:
+ enum MaterialType { MAT_G1 = 1, MAT_G2 = 2 };
+
+ struct SlotCell
+ {
+ bool hasGlass = false; // 是否有片
+ CString coreId; // 固定核心ID(不含前缀)
+ int material = MAT_G1; // 仅影响展示(G1/G2)
+ bool checked = false; // 是否勾选加工
+ };
+
+ struct PortColumn
+ {
+ CString portName; // "Port 1" ...
+ CString carrierName; // "Carrier A" ...
+ bool allocated = false; // 整列锁定
+ CString allocatedBy; // 占用者
+ std::vector<SlotCell> slots; // size = m_nSlots
+ };
+
+public:
+ CCarrierSlotSelector();
+ virtual ~CCarrierSlotSelector();
+
+ // 初始化 / 尺寸
+ void InitGrid(int nPorts, int nSlots);
+ void SetColumnWidths(int slotColWidth, int portColWidth);
+ void SetRowHeight(int cy); // small image list 控制行高
+
+ // 外观
+ void SetShowMaterialToggle(BOOL bShow);
+ BOOL GetShowMaterialToggle() const { return m_bShowMatToggle; }
+
+ // Port 接口
+ int GetPortCount() const { return (int)m_ports.size(); }
+ int GetSlotCount() const { return m_nSlots; }
+ void SetPortInfo(int portIndex, LPCTSTR portName, LPCTSTR carrierName);
+ void SetPortAllocated(int portIndex, BOOL allocated, LPCTSTR byName = nullptr);
+ BOOL IsPortAllocated(int portIndex) const;
+
+ // Slot 接口
+ void SetSlotGlass(int portIndex, int slotIndex, BOOL hasGlass, LPCTSTR coreId /*可空*/, int material /*1=G1,2=G2*/);
+ void SetSlotChecked(int portIndex, int slotIndex, BOOL checked);
+ BOOL GetSlotChecked(int portIndex, int slotIndex) const;
+
+ int GetSlotMaterialType(int portIndex, int slotIndex) const; // 1/2
+ void SetSlotMaterialType(int portIndex, int slotIndex, int material, BOOL bNotify = TRUE);
+
+ CString GetDisplayId(int portIndex, int slotIndex) const; // "Gx-core" 或 "—"
+ void RebuildTexts(); // 列头计数、单元格临时文本
+
+ // 工具:整列全选/全不选(只影响 hasGlass==true 的格子)
+ void CheckAllInPort(int portIndex, BOOL checked, BOOL bNotify = TRUE);
+
+protected:
+ // 内部数据
+ BOOL m_bFirstShown = FALSE; // 子页面首次显示后强制一次全量重绘
+ int m_nSlots = 0;
+ std::vector<PortColumn> m_ports;
+ BOOL m_bShowMatToggle = TRUE;
+
+ // UI metrics
+ int m_rowHeight = 24;
+ int m_slotColWidth = 100;
+ int m_portColWidth = 180;
+
+ // 行高图像列表
+ CImageList m_ilRowHeight;
+
+ // 颜色
+ COLORREF m_colBgAlt = RGB(240, 242, 245); // 交替列浅灰
+ COLORREF m_colBgNorm = RGB(255, 255, 255);
+ COLORREF m_colLockBg = RGB(255, 244, 214); // 锁定列淡黄
+
+ // 字体
+ CFont m_fntText;
+ CFont m_fntBold;
+ CFont m_fntSmall;
+
+ // 区域计算
+ CRect GetCheckboxRect(const CRect& cell) const;
+ CRect GetMaterialTagRect(const CRect& cell) const;
+ CRect GetStatusDotRect(const CRect& cell) const;
+
+ // 工具
+ void EnsureFonts();
+ void UpdateRowCount();
+ void DrawFlatCheckbox(CDC* pDC, const CRect& r, bool checked, bool disabled); // 扁平复选框
+
+ // 通知父窗口(WM_COMMAND 风格)
+ void NotifySelectionChanged(int port, int slot, BOOL checked);
+ void NotifyMaterialChanged(int port, int slot, int material);
+
+protected:
+ // MFC
+ virtual void PreSubclassWindow() override;
+ afx_msg void OnShowWindow(BOOL bShow, UINT nStatus);
+ afx_msg void OnWindowPosChanged(WINDOWPOS* lpwndpos);
+ afx_msg void OnSize(UINT nType, int cx, int cy);
+ afx_msg BOOL OnEraseBkgnd(CDC* pDC);
+ afx_msg void OnPaint(); // ★ 新增:自带双缓冲绘制
+ afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
+ afx_msg void OnMouseMove(UINT nFlags, CPoint point);
+ afx_msg void OnCustomDraw(NMHDR* pNMHDR, LRESULT* pResult);
+ afx_msg BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult); // 捕获 Header 通知
+ void OnHeaderClick(int iItem);
+
+ DECLARE_MESSAGE_MAP()
+};
diff --git a/SourceCode/Bond/Servo/CCjPage2.cpp b/SourceCode/Bond/Servo/CCjPage2.cpp
index 0f4aac9..b736ba4 100644
--- a/SourceCode/Bond/Servo/CCjPage2.cpp
+++ b/SourceCode/Bond/Servo/CCjPage2.cpp
@@ -48,6 +48,41 @@
CCjPageBase::OnInitDialog();
+ // 鑻ヤ綘鐨勮祫婧愰噷鏈変竴涓崰浣嶇殑 ListCtrl锛堟瘮濡� IDC_LIST1锛夛紝杩欓噷鍋氬瓙绫诲寲锛�
+ m_selector.SubclassDlgItem(IDC_LIST_SELECTOR, this);
+
+ // 鍒濆鍖栵細4 鍒� 脳 8 琛�
+ m_selector.InitGrid(4, 8);
+ m_selector.SetColumnWidths(100, 180);
+ m_selector.SetRowHeight(36);
+
+ // 璁剧疆鍒椾俊鎭�
+ m_selector.SetPortInfo(0, _T("Port 1"), _T("Carrier A"));
+ m_selector.SetPortInfo(1, _T("Port 2"), _T("Carrier B"));
+ m_selector.SetPortInfo(2, _T("Port 3"), _T("Carrier C"));
+ m_selector.SetPortInfo(3, _T("Port 4"), _T("Carrier D"));
+
+ // 璁剧疆閮ㄥ垎 Glass锛堟牳蹇僆D鍥哄畾锛�
+ m_selector.SetSlotGlass(0, 0, TRUE, _T("001"), CCarrierSlotSelector::MAT_G1);
+ m_selector.SetSlotGlass(0, 1, TRUE, _T("002"), CCarrierSlotSelector::MAT_G1);
+ m_selector.SetSlotGlass(0, 3, TRUE, _T("004"), CCarrierSlotSelector::MAT_G1);
+
+ m_selector.SetSlotGlass(1, 0, TRUE, _T("101"), CCarrierSlotSelector::MAT_G2);
+ m_selector.SetSlotGlass(1, 1, TRUE, _T("102"), CCarrierSlotSelector::MAT_G2);
+ m_selector.SetSlotGlass(1, 2, TRUE, _T("103"), CCarrierSlotSelector::MAT_G2);
+ m_selector.SetSlotGlass(1, 5, TRUE, _T("106"), CCarrierSlotSelector::MAT_G2);
+ m_selector.SetSlotGlass(1, 7, TRUE, _T("108"), CCarrierSlotSelector::MAT_G2);
+
+ m_selector.SetSlotGlass(2, 5, TRUE, _T("206"), CCarrierSlotSelector::MAT_G1);
+ m_selector.SetSlotGlass(2, 6, TRUE, _T("207"), CCarrierSlotSelector::MAT_G1);
+ m_selector.SetSlotGlass(2, 7, TRUE, _T("208"), CCarrierSlotSelector::MAT_G1);
+
+ m_selector.SetSlotGlass(3, 0, TRUE, _T("301"), CCarrierSlotSelector::MAT_G1);
+
+ // 閿佸畾 Port 1锛堢ず渚嬶級
+ m_selector.SetPortAllocated(0, TRUE, _T("ProcessJob 1"));
+
+
UpdatePjData();
@@ -87,6 +122,25 @@
GetDlgItemText(IDC_EDIT_PJ_ID, szBuffer, 256);
pProcessJob->setId(std::string(szBuffer));
+ // 鏇存柊閰嶆柟
+ CString strRecipe;
+ CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_RECIPE);
+ int idx = pComboBox->GetCurSel();
+ if (idx >= 0) {
+ pComboBox->GetLBText(idx, strRecipe);
+#ifdef UNICODE
+ CT2A utf8Str(strRecipe, CP_UTF8);
+ std::string recipe(utf8Str);
+#else
+ std::string recipe(strRecipe.GetString());
+#endif
+
+ pProcessJob->setRecipe(SERVO::RecipeMethod::NoTuning, recipe);
+ }
+
+
+
+
ContentChanged(1);
}
diff --git a/SourceCode/Bond/Servo/CCjPage2.h b/SourceCode/Bond/Servo/CCjPage2.h
index e277ac4..5a7338b 100644
--- a/SourceCode/Bond/Servo/CCjPage2.h
+++ b/SourceCode/Bond/Servo/CCjPage2.h
@@ -1,6 +1,7 @@
锘�#pragma once
#include "CCjPageBase.h"
#include "ProcessJob.h"
+#include "CCarrierSlotSelector.h"
// CPjPage1 瀵硅瘽妗�
@@ -21,6 +22,9 @@
private:
void UpdatePjData();
+private:
+ CCarrierSlotSelector m_selector;
+
// 瀵硅瘽妗嗘暟鎹�
#ifdef AFX_DESIGN_TIME
enum { IDD = IDD_CJ_PAGE1 };
diff --git a/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp b/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
index e125c68..6783528 100644
--- a/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
+++ b/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
@@ -357,6 +357,7 @@
void CControlJobManagerDlg::UpProcessJobId(SERVO::CProcessJob* pJob)
{
+ // 鏇存柊listbox
int count = m_listBox.GetCount();
for (int idx = 0; idx < count; idx++) {
if ((m_listBox.GetItemDataPtr(idx) == (void*)pJob)) {
@@ -371,4 +372,22 @@
}
}
}
+
+ // 鏇存柊鏍戞帶浠�
+ // 閬嶅巻鏍硅妭鐐�
+ HTREEITEM hRoot = m_tree.GetRootItem();
+ while (hRoot) {
+ // 閬嶅巻绗簩灞傚瓙鑺傜偣
+ HTREEITEM hChild = m_tree.GetChildItem(hRoot);
+ while (hChild) {
+ DWORD_PTR data = m_tree.GetItemData(hChild);
+ if ((void*)data == pJob) {
+ m_tree.SetItemText(hChild, pJob->id().c_str());
+ return; // 鎵惧埌灏辫繑鍥�
+ }
+ hChild = m_tree.GetNextSiblingItem(hChild);
+ }
+
+ hRoot = m_tree.GetNextSiblingItem(hRoot);
+ }
}
diff --git a/SourceCode/Bond/Servo/Servo.rc b/SourceCode/Bond/Servo/Servo.rc
index fe77aef..4bacf31 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 1b2bcc9..a80c7ab 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj
+++ b/SourceCode/Bond/Servo/Servo.vcxproj
@@ -210,6 +210,7 @@
<ClInclude Include="..\jsoncpp\include\json\writer.h" />
<ClInclude Include="..\jsoncpp\lib_json\json_batchallocator.h" />
<ClInclude Include="CBaseDlg.h" />
+ <ClInclude Include="CCarrierSlotSelector.h" />
<ClInclude Include="CCjPage2.h" />
<ClInclude Include="CCjPage3.h" />
<ClInclude Include="CCjPageBase.h" />
@@ -392,6 +393,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="CBaseDlg.cpp" />
+ <ClCompile Include="CCarrierSlotSelector.cpp" />
<ClCompile Include="CCjPage2.cpp" />
<ClCompile Include="CCjPage3.cpp" />
<ClCompile Include="CCjPageBase.cpp" />
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj.filters b/SourceCode/Bond/Servo/Servo.vcxproj.filters
index 9202388..fb2a15e 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj.filters
+++ b/SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -204,6 +204,7 @@
<ClCompile Include="CCjPage2.cpp" />
<ClCompile Include="CCjPage3.cpp" />
<ClCompile Include="CCjPageBase.cpp" />
+ <ClCompile Include="CCarrierSlotSelector.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="AlarmManager.h" />
@@ -433,6 +434,7 @@
<ClInclude Include="CCjPage2.h" />
<ClInclude Include="CCjPage3.h" />
<ClInclude Include="CCjPageBase.h" />
+ <ClInclude Include="CCarrierSlotSelector.h" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Servo.rc" />
diff --git a/SourceCode/Bond/Servo/resource.h b/SourceCode/Bond/Servo/resource.h
index 0da8d69..5a9214e 100644
--- a/SourceCode/Bond/Servo/resource.h
+++ b/SourceCode/Bond/Servo/resource.h
Binary files differ
--
Gitblit v1.9.3