LAPTOP-SNT8I5JK\Boounion
2025-09-17 4ef35bf238fc6f7217e4b6de4aee37192ec503ec
1.自绘CListCtrl用于ProcessJob的Carrier选择
已添加2个文件
已修改7个文件
876 ■■■■■ 文件已修改
SourceCode/Bond/Servo/CCarrierSlotSelector.cpp 673 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCarrierSlotSelector.h 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCjPage2.cpp 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCjPage2.h 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJobManagerDlg.cpp 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
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);
    }
}
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()
};
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(核心ID固定)
    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);
}
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 };
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);
    }
}
SourceCode/Bond/Servo/Servo.rc
Binary files differ
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" />
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" />
SourceCode/Bond/Servo/resource.h
Binary files differ