已添加4个文件
已修改9个文件
2217 ■■■■■ 文件已修改
SourceCode/Bond/Servo/CCarrierSlotGrid.cpp 902 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCarrierSlotGrid.h 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCarrierSlotSelector.cpp 673 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCarrierSlotSelector.h 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCjPage2.cpp 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCjPage2.h 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJobManagerDlg.cpp 196 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJobManagerDlg.h 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CLoadPort.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.vcxproj.filters 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CCarrierSlotGrid.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,902 @@
#include "stdafx.h"
#include "CCarrierSlotGrid.h"
#include <gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;
#define SAFE_PORT(p) ((p) >= 0 && (p) < (int)m_ports.size())
#define SAFE_SLOT(s) ((s) >= 0 && (s) < m_nSlots)
static bool       s_gdiplusInited = false;
static ULONG_PTR  s_gdiplusToken = 0;
static void EnsureGdiplus()
{
    if (!s_gdiplusInited)
    {
        GdiplusStartupInput in;
        if (GdiplusStartup(&s_gdiplusToken, &in, nullptr) == Ok)
            s_gdiplusInited = true;
    }
}
BEGIN_MESSAGE_MAP(CCarrierSlotGrid, CWnd)
    ON_WM_ERASEBKGND()
    ON_WM_PAINT()
    ON_WM_CREATE()
    ON_WM_SIZE()
    ON_WM_HSCROLL()
    ON_WM_VSCROLL()          // â˜… æ–°å¢ž
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONUP()
    ON_WM_MOUSEWHEEL()
    ON_WM_MOUSEMOVE()
    ON_WM_SETCURSOR()
    ON_WM_SHOWWINDOW()
    ON_WM_WINDOWPOSCHANGED()
END_MESSAGE_MAP()
CCarrierSlotGrid::CCarrierSlotGrid() {}
CCarrierSlotGrid::~CCarrierSlotGrid()
{
    if ((HFONT)m_fntText)  m_fntText.DeleteObject();
    if ((HFONT)m_fntBold)  m_fntBold.DeleteObject();
    if ((HFONT)m_fntSmall) m_fntSmall.DeleteObject();
}
void CCarrierSlotGrid::PreSubclassWindow()
{
    CWnd::PreSubclassWindow();
    if (GetParent() && GetParent()->GetFont()) SetFont(GetParent()->GetFont());
    EnsureFonts();
    EnsureGdiplus();
    // ç¡®ä¿æ ·å¼åŒ…含滚动条
    ModifyStyle(0, WS_HSCROLL | WS_VSCROLL, 0);
    SetWindowPos(nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
}
int CCarrierSlotGrid::OnCreate(LPCREATESTRUCT cs)
{
    if (CWnd::OnCreate(cs) == -1) return -1;
    if (GetParent() && GetParent()->GetFont()) SetFont(GetParent()->GetFont());
    EnsureFonts();
    EnsureGdiplus();
    ModifyStyle(0, WS_HSCROLL | WS_VSCROLL, 0);
    return 0;
}
void CCarrierSlotGrid::EnsureFonts()
{
    if ((HFONT)m_fntText) return;
    LOGFONT lf{}; bool ok = false;
    if (GetParent() && GetParent()->GetFont()) { GetParent()->GetFont()->GetLogFont(&lf); ok = true; }
    else if (GetFont()) { GetFont()->GetLogFont(&lf); ok = true; }
    else {
        NONCLIENTMETRICS ncm = { 0 }; ncm.cbSize = sizeof(NONCLIENTMETRICS);
        if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0)) { lf = ncm.lfMessageFont; ok = true; }
    }
    if (!ok) { CFont* pDef = CFont::FromHandle((HFONT)GetStockObject(DEFAULT_GUI_FONT)); pDef->GetLogFont(&lf); }
    m_fntText.CreateFontIndirect(&lf);
    LOGFONT lfb = lf; lfb.lfWeight = FW_SEMIBOLD;  m_fntBold.CreateFontIndirect(&lfb);
    LOGFONT lfs = lf; lfs.lfHeight = (LONG)(lf.lfHeight * 0.9); if (lfs.lfHeight == 0) lfs.lfHeight = lf.lfHeight - 1; m_fntSmall.CreateFontIndirect(&lfs);
}
void CCarrierSlotGrid::InitGrid(int nPorts, int nSlots)
{
    ASSERT(nPorts >= 1 && nSlots >= 1);
    m_ports.assign(nPorts, PortColumn{});
    m_portColCXs.assign(nPorts, 180);
    m_nSlots = nSlots;
    for (auto& pc : m_ports) pc.slots.assign(m_nSlots, SlotCell{});
    UpdateScrollRange();
    Invalidate(FALSE);
}
void CCarrierSlotGrid::SetColumnWidths(int slotColWidth, int portColWidth)
{
    if (slotColWidth > 0) m_slotColCX = slotColWidth;
    if (portColWidth > 0)
        for (size_t i = 0; i < m_portColCXs.size(); ++i) m_portColCXs[i] = portColWidth;
    UpdateScrollRange();
    Invalidate(FALSE);
}
void CCarrierSlotGrid::SetRowHeight(int cy) { m_rowHeight = max(18, min(cy, 64)); UpdateScrollRange(); Invalidate(FALSE); }
void CCarrierSlotGrid::SetHeaderHeight(int cy) { m_headerCY = max(20, min(cy, 48)); UpdateScrollRange(); Invalidate(FALSE); }
void CCarrierSlotGrid::SetShowMaterialToggle(BOOL bShow) { m_bShowMatToggle = bShow; Invalidate(FALSE); }
void CCarrierSlotGrid::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;
    Invalidate(FALSE);
}
void CCarrierSlotGrid::SetPortAllocated(int portIndex, BOOL allocated, LPCTSTR byName)
{
    if (!SAFE_PORT(portIndex)) return;
    auto& pc = m_ports[portIndex];
    pc.allocated = !!allocated;
    pc.allocatedBy = byName ? byName : _T("");
    if (pc.allocated)
        for (auto& cell : pc.slots) cell.checked = false;
    Invalidate(FALSE);
}
BOOL CCarrierSlotGrid::IsPortAllocated(int portIndex) const
{
    if (!SAFE_PORT(portIndex)) return FALSE; return m_ports[portIndex].allocated;
}
void CCarrierSlotGrid::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;
    Invalidate(FALSE);
}
void CCarrierSlotGrid::SetSlotChecked(int portIndex, int slotIndex, BOOL checked)
{
    if (!SAFE_PORT(portIndex) || !SAFE_SLOT(slotIndex)) return;
    auto& pc = m_ports[portIndex];
    if (pc.allocated) return;
    auto& cell = pc.slots[slotIndex];
    if (!cell.hasGlass) return;
    cell.checked = !!checked;
    NotifySelectionChanged(portIndex, slotIndex, cell.checked);
    Invalidate(FALSE);
}
BOOL CCarrierSlotGrid::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 CCarrierSlotGrid::GetSlotMaterialType(int portIndex, int slotIndex) const
{
    if (!SAFE_PORT(portIndex) || !SAFE_SLOT(slotIndex)) return MAT_G1;
    return m_ports[portIndex].slots[slotIndex].material;
}
void CCarrierSlotGrid::SetSlotMaterialType(int portIndex, int slotIndex, int material, BOOL bNotify)
{
    if (!SAFE_PORT(portIndex) || !SAFE_SLOT(slotIndex)) return;
    auto& pc = m_ports[portIndex];
    if (pc.allocated) return;
    auto& cell = pc.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);
        Invalidate(FALSE);
    }
}
CString CCarrierSlotGrid::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 CCarrierSlotGrid::CheckAllInPort(int portIndex, BOOL checked, BOOL bNotify)
{
    if (!SAFE_PORT(portIndex)) return;
    auto& pc = m_ports[portIndex];
    if (pc.allocated) return;
    for (int r = 0; r < m_nSlots; ++r) {
        auto& cell = pc.slots[r];
        if (!cell.hasGlass) continue;
        if (cell.checked != !!checked) {
            cell.checked = !!checked;
            if (bNotify) NotifySelectionChanged(portIndex, r, cell.checked);
        }
    }
    Invalidate(FALSE);
}
void CCarrierSlotGrid::RebuildTexts() { Invalidate(FALSE); }
CSize CCarrierSlotGrid::CalcBestClientSize(int nSlotsOverride) const
{
    const int slots = (nSlotsOverride > 0) ? nSlotsOverride : m_nSlots;
    int w = m_slotColCX;
    for (int cx : m_portColCXs) w += cx;
    int h = m_headerCY + slots * m_rowHeight;
    return CSize(w, h);
}
CSize CCarrierSlotGrid::CalcBestWindowSize(BOOL includeNonClient, int nSlotsOverride) const
{
    CSize cli = CalcBestClientSize(nSlotsOverride);
    if (!includeNonClient) return cli;
    RECT rc = { 0, 0, cli.cx, cli.cy };
    // ç›®æ ‡æ˜¯â€œåˆšå¥½ä¸å‡ºçŽ°æ»šåŠ¨æ¡â€çš„çª—å£å¤–æ¡†å¤§å°ï¼š
    // ç”¨å½“前样式去掉 WS_HSCROLL/WS_VSCROLL å†åš AdjustWindowRectEx
    DWORD style = GetStyle();
    DWORD exStyle = GetExStyle();
    style &= ~(WS_HSCROLL | WS_VSCROLL);
    ::AdjustWindowRectEx(&rc, style, FALSE, exStyle);
    return CSize(rc.right - rc.left, rc.bottom - rc.top);
}
// ---------- å‡ ä½• ----------
CRect CCarrierSlotGrid::GetClientRectNoSB() const
{
    CRect rc; GetClientRect(&rc); return rc;
}
CRect CCarrierSlotGrid::GetHeaderRect() const
{
    CRect rc = GetClientRectNoSB(); rc.bottom = rc.top + m_headerCY; return rc;
}
int CCarrierSlotGrid::GetTotalContentWidth() const
{
    int w = m_slotColCX;
    for (int cx : m_portColCXs) w += cx;
    return w;
}
CRect CCarrierSlotGrid::GetHeaderItemRect(int iItem) const
{
    CRect rcHeader = GetHeaderRect();
    int x = rcHeader.left - m_scrollX;
    if (iItem == 0)
        return CRect(x, rcHeader.top, x + m_slotColCX, rcHeader.bottom);
    x += m_slotColCX;
    for (int c = 1; c < iItem; ++c) x += m_portColCXs[c - 1];
    int w = m_portColCXs[iItem - 1];
    return CRect(x, rcHeader.top, x + w, rcHeader.bottom);
}
BOOL CCarrierSlotGrid::GetCellRect(int row, int sub, CRect& rc) const
{
    CRect cli = GetClientRectNoSB();
    int y0 = cli.top + m_headerCY - m_scrollY;
    int top = y0 + row * m_rowHeight;
    int bottom = top + m_rowHeight;
    if (bottom <= cli.top + m_headerCY || top >= cli.bottom) return FALSE;
    int x = cli.left - m_scrollX;
    if (sub == 0) { rc = CRect(x, top, x + m_slotColCX, bottom); return TRUE; }
    x += m_slotColCX;
    for (int c = 1; c < sub; ++c) x += m_portColCXs[c - 1];
    int w = m_portColCXs[sub - 1];
    rc = CRect(x, top, x + w, bottom);
    return TRUE;
}
CRect CCarrierSlotGrid::GetHeaderCheckboxRect(int iItem) const
{
    CRect rItem = GetHeaderItemRect(iItem);
    const int box = 16;
    const int padR = 6;
    int vpad = max(0, (rItem.Height() - box) / 2);
    return CRect(rItem.right - padR - box, rItem.top + vpad, rItem.right - padR, rItem.bottom - vpad);
}
CRect CCarrierSlotGrid::GetCheckboxRect(const CRect& cell) const
{
    int sz = max(14, min(int(m_rowHeight * 0.70), 20));
    int leftPad = 8;
    int top = cell.top + (cell.Height() - sz) / 2;
    return CRect(cell.left + leftPad, top, cell.left + leftPad + sz, top + sz);
}
CRect CCarrierSlotGrid::GetMaterialTagRect(const CRect& cell) const
{
    int h = max(14, min(int(m_rowHeight * 0.65), m_rowHeight - 8));
    int w = 32;
    int gap = 6;
    int rightPadForDot = 16;
    int top = cell.top + (cell.Height() - h) / 2;
    int right = cell.right - rightPadForDot - gap;
    return CRect(right - w, top, right, top + h);
}
CRect CCarrierSlotGrid::GetStatusDotRect(const CRect& cell) const
{
    int d = max(8, min(int(m_rowHeight * 0.42), 12));
    int rightPad = 6;
    int top = cell.top + (cell.Height() - d) / 2;
    return CRect(cell.right - rightPad - d, top, cell.right - rightPad, top + d);
}
void CCarrierSlotGrid::UpdateScrollRange()
{
    CRect rc; GetClientRect(&rc);
    // åž‚ç›´
    const int contentH = m_headerCY + m_nSlots * m_rowHeight;
    const int pageY = max(1, rc.Height());
    const int maxPosY = max(0, contentH - pageY);
    m_scrollY = max(0, min(m_scrollY, maxPosY));
    SCROLLINFO siY = { sizeof(SCROLLINFO) };
    siY.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
    siY.nMin = 0; siY.nMax = contentH - 1; siY.nPage = pageY; siY.nPos = m_scrollY;
    SetScrollInfo(SB_VERT, &siY, TRUE);
    // Ë®Æ½
    const int contentW = GetTotalContentWidth();
    const int pageX = max(1, rc.Width());
    const int maxPosX = max(0, contentW - pageX);
    m_scrollX = max(0, min(m_scrollX, maxPosX));
    SCROLLINFO siX = { sizeof(SCROLLINFO) };
    siX.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
    siX.nMin = 0; siX.nMax = contentW - 1; siX.nPage = pageX; siX.nPos = m_scrollX;
    SetScrollInfo(SB_HORZ, &siX, TRUE);
}
// ---------- è¡¨å¤´åˆ†éš”线命中 ----------
int CCarrierSlotGrid::HitHeaderEdge(CPoint pt) const
{
    if (!m_bAllowResize) return -1;  // â† æ–°å¢ž
    if (!GetHeaderRect().PtInRect(pt)) return -1;
    const int tol = 4;
    int x = GetHeaderRect().left - m_scrollX + m_slotColCX;
    if (abs(pt.x - x) <= tol) return 0;
    int cum = GetHeaderRect().left - m_scrollX + m_slotColCX;
    for (int i = 0; i <= GetPortCount() - 2; ++i) {
        cum += m_portColCXs[i];
        if (abs(pt.x - cum) <= tol) return i + 1;
    }
    return -1;
}
// ---------- ç»˜åˆ¶ ----------
BOOL CCarrierSlotGrid::OnEraseBkgnd(CDC* /*pDC*/) { return TRUE; }
void CCarrierSlotGrid::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);
    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);
}
void CCarrierSlotGrid::PaintTo(CDC* pDC)
{
    EnsureGdiplus();
    CRect cli = GetClientRectNoSB();
    pDC->FillSolidRect(cli, m_colBg);
    // Header
    CRect rh = GetHeaderRect();
    pDC->FillSolidRect(rh, ::GetSysColor(COLOR_BTNFACE));
    CPen penSep(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW));
    CPen* pOldPen = pDC->SelectObject(&penSep);
    pDC->MoveTo(rh.left, rh.bottom - 1); pDC->LineTo(rh.right, rh.bottom - 1);
    pDC->SelectObject(pOldPen);
    for (int i = 0; i <= GetPortCount(); ++i)
    {
        CRect rItem = GetHeaderItemRect(i);
        // ä¿®æ”¹ä¸ºï¼š
        if (i < GetPortCount()) { // â˜… æœ€åŽä¸€åˆ—不画分隔线
            CPen pen(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW));
            pOldPen = pDC->SelectObject(&pen);
            pDC->MoveTo(rItem.right - 1, rItem.top);
            pDC->LineTo(rItem.right - 1, rItem.bottom);
            pDC->SelectObject(pOldPen);
        }
        CString text;
        if (i == 0) {
            text = _T("Slot");
            CRect rt = rItem; rt.DeflateRect(6, 0, 6, 0);
            pDC->SetBkMode(TRANSPARENT);
            pDC->SelectObject(&m_fntBold);
            pDC->SetTextColor(::GetSysColor(COLOR_BTNTEXT));
            pDC->DrawText(text, rt, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
        }
        else {
            const auto& pc = m_ports[i - 1];
            int selected = 0; bool any = false, all = true;
            for (const auto& cell : pc.slots) {
                if (cell.hasGlass) { any = true; if (cell.checked) ++selected; else all = false; }
            }
            if (!any) all = false;
            CString leftTitle = pc.carrierName.IsEmpty()
                ? pc.portName
                : (pc.portName + _T(" (") + pc.carrierName + _T(")"));
            // å‹¾é€‰æ¡†é å³
            CRect rcCb = GetHeaderCheckboxRect(i);
            DrawFlatCheckbox(pDC, rcCb, all, pc.allocated);
            // è®¡æ•°è´´è¿‘勾选框左侧
            CString cnt; cnt.Format(_T("%d/%d"), selected, m_nSlots);
            SIZE szCnt{ 0,0 };
            { CFont* o = pDC->SelectObject(&m_fntBold);
            GetTextExtentPoint32(pDC->GetSafeHdc(), cnt, cnt.GetLength(), &szCnt);
            pDC->SelectObject(o); }
            const int gap = 6;
            CRect rcCnt(rcCb.left - gap - szCnt.cx, rItem.top, rcCb.left - gap, rItem.bottom);
            // å·¦ä¾§æ ‡é¢˜
            CRect rt = rItem; rt.DeflateRect(6, 0, (rItem.right - rcCnt.left) + 6, 0);
            pDC->SetBkMode(TRANSPARENT);
            pDC->SelectObject(&m_fntBold);
            pDC->SetTextColor(::GetSysColor(COLOR_BTNTEXT));
            pDC->DrawText(leftTitle, rt, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
            // è®¡æ•°
            pDC->SelectObject(&m_fntBold);
            pDC->SetTextColor(::GetSysColor(COLOR_BTNTEXT));
            pDC->DrawText(cnt, rcCnt, DT_RIGHT | DT_VCENTER | DT_SINGLELINE);
        }
    }
    // Cells
    for (int r = 0; r < m_nSlots; ++r)
    {
        for (int s = 0; s <= GetPortCount(); ++s)
        {
            CRect rc;
            if (!GetCellRect(r, s, rc)) continue;
            COLORREF bk = m_colBg;
            if (s >= 1) {
                int port = s - 1;
                if (port % 2 == 0) bk = m_colAlt;
                if (SAFE_PORT(port) && m_ports[port].allocated) bk = m_colLock;
            }
            pDC->FillSolidRect(rc, bk);
            CPen penMajor(PS_SOLID, (s >= 1 && ((s - 1) % 2 == 0)) ? 2 : 1, m_gridMajor);
            CPen* pOld = pDC->SelectObject(&penMajor);
            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(pOld);
            CPen penMinor(PS_SOLID, 1, m_gridMinor);
            pOld = pDC->SelectObject(&penMinor);
            if (s == GetPortCount()) { pDC->MoveTo(rc.right - 1, rc.top); pDC->LineTo(rc.right - 1, rc.bottom); }
            if (r == m_nSlots - 1) { pDC->MoveTo(rc.left, rc.bottom - 1); pDC->LineTo(rc.right, rc.bottom - 1); }
            pDC->SelectObject(pOld);
            if (s == 0) {
                CString sl; sl.Format(_T("Slot %d"), r + 1);
                CRect rt = rc; rt.DeflateRect(8, 0, 8, 0);
                pDC->SelectObject(&m_fntBold);
                pDC->SetBkMode(TRANSPARENT);
                pDC->SetTextColor(RGB(60, 60, 64));
                pDC->DrawText(sl, rt, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
            }
            else {
                int port = s - 1;
                const auto& pc = m_ports[port];
                const auto& cell = pc.slots[r];
                CRect rChk = GetCheckboxRect(rc);
                DrawFlatCheckbox(pDC, rChk, cell.checked, pc.allocated || !cell.hasGlass);
                CString t = GetDisplayId(port, r);
                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);
                pDC->SelectObject(&m_fntText);
                pDC->SetBkMode(TRANSPARENT);
                pDC->SetTextColor(cell.hasGlass ? m_text : m_textDim);
                pDC->DrawText(t, rText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
                if (m_bShowMatToggle)
                {
                    CRect rT = GetMaterialTagRect(rc);
                    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);
                    CString tx; tx.Format(_T("G%d"), (cell.material == MAT_G2) ? 2 : 1);
                    pDC->SelectObject(&m_fntSmall);
                    pDC->SetBkMode(TRANSPARENT); pDC->SetTextColor(crText);
                    pDC->DrawText(tx, rT, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
                }
                // çŠ¶æ€ç‚¹ï¼ˆGDI+ æŠ—锯齿)
                {
                    Graphics g(pDC->GetSafeHdc());
                    g.SetSmoothingMode(SmoothingModeAntiAlias);
                    COLORREF c = cell.hasGlass ? (pc.allocated ? RGB(215, 160, 60) : RGB(60, 170, 80)) : RGB(160, 160, 160);
                    SolidBrush brush(Color(255, GetRValue(c), GetGValue(c), GetBValue(c)));
                    Pen outline(Color(255, 120, 120, 120), 1.f);
                    g.FillEllipse(&brush, rDot.left, rDot.top, rDot.Width(), rDot.Height());
                    g.DrawEllipse(&outline, (REAL)rDot.left, (REAL)rDot.top, (REAL)rDot.Width(), (REAL)rDot.Height());
                }
            }
        }
    }
// ===== åœ¨æ¯ä¸ªå·²åˆ†é…(allocated)的列中央绘制半透明 LOCK æ°´å°ï¼ˆç”¨ HDC+LOGFONT æž„造字体)=====
    {
        Gdiplus::Graphics g(pDC->GetSafeHdc());
        g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
        CRect cli = GetClientRectNoSB();
        CRect rh = GetHeaderRect();
        // å–当前 UI å­—体(优先粗体)
        LOGFONT lf{};
        if ((HFONT)m_fntBold)      m_fntBold.GetLogFont(&lf);
        else if ((HFONT)m_fntText) m_fntText.GetLogFont(&lf);
        for (int i = 0; i < GetPortCount(); ++i)
        {
            if (!m_ports[i].allocated) continue;
            // åˆ—矩形(除去表头,考虑水平滚动)
            CRect rCol = GetHeaderItemRect(i + 1);
            rCol.top = rh.bottom;
            rCol.bottom = cli.bottom;
            if (rCol.right <= cli.left || rCol.left >= cli.right || rCol.Height() <= 0) continue;
            // è‡ªé€‚应一个合适的像素高度
            int availW = rCol.Width() - 12;
            int availH = rCol.Height() - 12;
            int emPx = max(16, min(min(availW / 3, availH / 5), 72));
            if (emPx < 16) emPx = 16;
            // å­—号减半(并给个更低的兜底,避免太小)
            emPx = max(12, emPx / 2);
            // ç”¨ LOGFONTW + HDC æž„造 GDI+ å­—体
            LOGFONTW lfw{};
#ifdef UNICODE
            lfw = *reinterpret_cast<LOGFONTW*>(&lf);
#else
            lfw.lfHeight = lf.lfHeight;
            lfw.lfWidth = lf.lfWidth;
            lfw.lfEscapement = lf.lfEscapement;
            lfw.lfOrientation = lf.lfOrientation;
            lfw.lfWeight = lf.lfWeight;
            lfw.lfItalic = lf.lfItalic;
            lfw.lfUnderline = lf.lfUnderline;
            lfw.lfStrikeOut = lf.lfStrikeOut;
            lfw.lfCharSet = lf.lfCharSet;
            lfw.lfOutPrecision = lf.lfOutPrecision;
            lfw.lfClipPrecision = lf.lfClipPrecision;
            lfw.lfQuality = lf.lfQuality;
            lfw.lfPitchAndFamily = lf.lfPitchAndFamily;
            MultiByteToWideChar(CP_ACP, 0, lf.lfFaceName, -1, lfw.lfFaceName, LF_FACESIZE);
#endif
            lfw.lfHeight = -emPx;           // è´Ÿå€¼=按像素高度
            lfw.lfWeight = FW_BOLD;
            Gdiplus::Font gdifont(pDC->GetSafeHdc(), &lfw);    // â˜… åŠ ä¸Š Gdiplus::
            Gdiplus::StringFormat fmt;
            fmt.SetAlignment(Gdiplus::StringAlignmentCenter);
            fmt.SetLineAlignment(Gdiplus::StringAlignmentCenter);
            Gdiplus::Color col(140, 120, 100, 60);             // åŠé€æ˜Ž
            Gdiplus::SolidBrush brush(col);
            Gdiplus::RectF box((Gdiplus::REAL)rCol.left, (Gdiplus::REAL)rCol.top,
                (Gdiplus::REAL)rCol.Width(), (Gdiplus::REAL)rCol.Height());
            if (gdifont.GetLastStatus() == Gdiplus::Ok) {
                g.DrawString(L"LOCK", -1, &gdifont, box, &fmt, &brush);
            }
            else {
                Gdiplus::Font fallback(L"Arial", (Gdiplus::REAL)emPx, Gdiplus::FontStyleBold, Gdiplus::UnitPixel);
                g.DrawString(L"LOCK", -1, &fallback, box, &fmt, &brush);
            }
        }
    }
    // === å®¢æˆ·åŒºå†… 1px ç°è‰²è¾¹æ¡†ï¼ˆä¸åŒ…滚动条,但不会缺角/抢绘制)===
    {
        CRect cli; GetClientRect(&cli);
        // ç”¨ FrameRect æ›´ç¨³ï¼ˆé¿å…å³ä¸‹è§’丢线)
        CBrush br; br.CreateSolidBrush(::GetSysColor(COLOR_3DSHADOW));
        CRect r = cli;
        // æ³¨æ„ï¼šå®¢æˆ·åŒºåæ ‡æ˜¯ [0..width, 0..height];FrameRect ä¼šåœ¨å†…ä¾§ç”» 1px
        pDC->FrameRect(&r, &br);
        br.DeleteObject();
    }
}
void CCarrierSlotGrid::OnPaint()
{
    CPaintDC dc(this);
    CRect rc; GetClientRect(&rc);
    CDC mem; mem.CreateCompatibleDC(&dc);
    CBitmap bmp; bmp.CreateCompatibleBitmap(&dc, rc.Width(), rc.Height());
    HGDIOBJ ob = mem.SelectObject(bmp);
    PaintTo(&mem);
    dc.BitBlt(0, 0, rc.Width(), rc.Height(), &mem, 0, 0, SRCCOPY);
    mem.SelectObject(ob);
}
void CCarrierSlotGrid::OnSize(UINT nType, int cx, int cy)
{
    CWnd::OnSize(nType, cx, cy);
    UpdateScrollRange();
    Invalidate(FALSE);
    RedrawWindow(nullptr, nullptr, RDW_INVALIDATE | RDW_FRAME | RDW_UPDATENOW);
}
void CCarrierSlotGrid::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pBar)
{
    UNREFERENCED_PARAMETER(pBar);
    SCROLLINFO si = { sizeof(SCROLLINFO) };
    si.fMask = SIF_ALL;
    GetScrollInfo(SB_HORZ, &si);
    int pos = m_scrollX;
    const int maxPos = max(0, (int)si.nMax - (int)si.nPage + 1);
    switch (nSBCode)
    {
    case SB_LINELEFT:  pos -= 30; break;
    case SB_LINERIGHT: pos += 30; break;
    case SB_PAGELEFT:  pos -= (int)si.nPage; break;
    case SB_PAGERIGHT: pos += (int)si.nPage; break;
    case SB_THUMBTRACK:
    case SB_THUMBPOSITION:
        pos = (int)si.nTrackPos;   // â˜… 32 ä½æ‹–动位置
        break;
    default:
        return;
    }
    pos = max(0, min(pos, maxPos));
    if (pos != m_scrollX) {
        m_scrollX = pos;
        SetScrollPos(SB_HORZ, m_scrollX);
        Invalidate(FALSE);
    }
}
void CCarrierSlotGrid::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pBar)
{
    UNREFERENCED_PARAMETER(pBar);
    SCROLLINFO si = { sizeof(SCROLLINFO) };
    si.fMask = SIF_ALL;
    GetScrollInfo(SB_VERT, &si);
    int pos = m_scrollY;
    const int maxPos = max(0, (int)si.nMax - (int)si.nPage + 1);
    switch (nSBCode)
    {
    case SB_LINEUP:    pos -= m_rowHeight; break;
    case SB_LINEDOWN:  pos += m_rowHeight; break;
    case SB_PAGEUP:    pos -= (int)si.nPage; break;
    case SB_PAGEDOWN:  pos += (int)si.nPage; break;
    case SB_THUMBTRACK:
    case SB_THUMBPOSITION:
        pos = (int)si.nTrackPos;   // â˜… 32 ä½æ‹–动位置
        break;
    default:
        return;
    }
    pos = max(0, min(pos, maxPos));
    if (pos != m_scrollY) {
        m_scrollY = pos;
        SetScrollPos(SB_VERT, m_scrollY);
        Invalidate(FALSE);
    }
}
BOOL CCarrierSlotGrid::OnMouseWheel(UINT, short zDelta, CPoint)
{
    int delta = (zDelta > 0 ? -1 : +1) * (m_rowHeight * 3);
    m_scrollY = max(0, m_scrollY + delta);
    UpdateScrollRange();
    Invalidate(FALSE);
    return TRUE;
}
void CCarrierSlotGrid::OnShowWindow(BOOL bShow, UINT nStatus)
{
    CWnd::OnShowWindow(bShow, nStatus);
    if (bShow) { UpdateScrollRange(); Invalidate(FALSE); }
}
void CCarrierSlotGrid::OnWindowPosChanged(WINDOWPOS* wp)
{
    CWnd::OnWindowPosChanged(wp);
    if (wp && (wp->flags & SWP_SHOWWINDOW)) { UpdateScrollRange(); Invalidate(FALSE); }
}
void CCarrierSlotGrid::OnLButtonDown(UINT nFlags, CPoint pt)
{
    // æ˜¯å¦æ‹–动列宽
    int edge = m_bAllowResize ? HitHeaderEdge(pt) : -1;  // â† ä¿®æ”¹
    if (edge >= 0)
    {
        m_bResizing = true;
        m_resizeEdge = edge;
        m_resizeStartX = pt.x;
        m_slotColCXStart = m_slotColCX;
        m_portColCXsStart = m_portColCXs;
        SetCapture();
        return;
    }
    // Header ç‚¹å‡»ï¼ˆä»…复选框区域)
    if (GetHeaderRect().PtInRect(pt))
    {
        for (int i = 1; i <= GetPortCount(); ++i)
        {
            CRect rItem = GetHeaderItemRect(i);
            if (!rItem.PtInRect(pt)) continue;
            CRect rcCb = GetHeaderCheckboxRect(i);
            if (!rcCb.PtInRect(pt)) return;
            int port = i - 1;
            if (!SAFE_PORT(port) || m_ports[port].allocated) return;
            bool any = false, all = true;
            for (auto& cell : m_ports[port].slots) {
                if (!cell.hasGlass) continue;
                any = true; if (!cell.checked) { all = false; break; }
            }
            if (!any) return;
            CheckAllInPort(port, all ? FALSE : TRUE, TRUE);
            return;
        }
        return;
    }
    // Cell ç‚¹å‡»
    CRect cli = GetClientRectNoSB();
    if (pt.y < cli.top + m_headerCY) return;
    int yIn = pt.y - (cli.top + m_headerCY) + m_scrollY;
    int row = yIn / m_rowHeight;
    if (!SAFE_SLOT(row)) return;
    int x = pt.x + m_scrollX - cli.left;
    int sub = 0;
    if (x < m_slotColCX) sub = 0;
    else {
        x -= m_slotColCX;
        sub = 1;
        for (size_t i = 0; i < m_portColCXs.size(); ++i) {
            if (x < m_portColCXs[i]) { sub = (int)i + 1; break; }
            x -= m_portColCXs[i];
            sub = (int)i + 2;
        }
        if (sub < 1 || sub > GetPortCount()) return;
    }
    if (sub == 0) return;
    int port = sub - 1;
    if (!SAFE_PORT(port)) return;
    auto& pc = m_ports[port];
    auto& cell = pc.slots[row];
    CRect rc; if (!GetCellRect(row, sub, rc)) return;
    if (pc.allocated || !cell.hasGlass) return;
    if (GetCheckboxRect(rc).PtInRect(pt))
    {
        cell.checked = !cell.checked;
        NotifySelectionChanged(port, row, cell.checked);
        Invalidate(FALSE);
        return;
    }
    if (m_bShowMatToggle && GetMaterialTagRect(rc).PtInRect(pt))
    {
        cell.material = (cell.material == MAT_G1) ? MAT_G2 : MAT_G1;
        NotifyMaterialChanged(port, row, cell.material);
        Invalidate(FALSE);
        return;
    }
    CWnd::OnLButtonDown(nFlags, pt);
}
void CCarrierSlotGrid::OnLButtonUp(UINT nFlags, CPoint pt)
{
    if (m_bResizing)
    {
        ReleaseCapture();
        m_bResizing = false;
        m_resizeEdge = -1;
        UpdateScrollRange();
        Invalidate(FALSE);
    }
    CWnd::OnLButtonUp(nFlags, pt);
}
void CCarrierSlotGrid::OnMouseMove(UINT nFlags, CPoint pt)
{
    if (m_bResizing)
    {
        int dx = pt.x - m_resizeStartX;
        if (m_resizeEdge == 0)
        {
            int nw = max(m_slotColMin, m_slotColCXStart + dx);
            if (nw != m_slotColCX) { m_slotColCX = nw; UpdateScrollRange(); Invalidate(FALSE); }
        }
        else
        {
            int idx = m_resizeEdge - 1; // è°ƒæ•´ Port idx çš„宽度
            int nw = max(m_portColMin, m_portColCXsStart[idx] + dx);
            if (nw != m_portColCXs[idx]) { m_portColCXs[idx] = nw; UpdateScrollRange(); Invalidate(FALSE); }
        }
        return;
    }
    int edge = HitHeaderEdge(pt);
    if (edge != m_hitEdgeHover)
    {
        m_hitEdgeHover = edge;
        if (m_hitEdgeHover >= 0) ::SetCursor(::LoadCursor(nullptr, IDC_SIZEWE));
        else ::SetCursor(::LoadCursor(nullptr, IDC_ARROW));
    }
}
BOOL CCarrierSlotGrid::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
    CPoint pt; ::GetCursorPos(&pt); ScreenToClient(&pt);
    if (m_bAllowResize && (m_bResizing || HitHeaderEdge(pt) >= 0))
    {
        ::SetCursor(::LoadCursor(nullptr, IDC_SIZEWE));
        return TRUE;
    }
    return CWnd::OnSetCursor(pWnd, nHitTest, message);
}
// ---------- Í¨Öª ----------
void CCarrierSlotGrid::NotifySelectionChanged(int /*port*/, int /*slot*/, BOOL /*checked*/)
{
    if (GetParent())
    {
        const int code = 0x2001;
        GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), code), (LPARAM)m_hWnd);
    }
}
void CCarrierSlotGrid::NotifyMaterialChanged(int /*port*/, int /*slot*/, int /*material*/)
{
    if (GetParent())
    {
        const int code = 0x2002;
        GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), code), (LPARAM)m_hWnd);
    }
}
SourceCode/Bond/Servo/CCarrierSlotGrid.h
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,160 @@
#include "stdafx.h"
#pragma once
#include <vector>
#ifndef _AFX
#include <afxwin.h>
#endif
class CCarrierSlotGrid : public CWnd
{
public:
    enum MaterialType { MAT_G1 = 1, MAT_G2 = 2 };
    struct SlotCell {
        bool    hasGlass = false;
        CString coreId;
        int     material = MAT_G1;
        bool    checked = false;
    };
    struct PortColumn {
        CString               portName;
        CString               carrierName;
        bool                  allocated = false;
        CString               allocatedBy;
        std::vector<SlotCell> slots;
    };
public:
    CCarrierSlotGrid();
    virtual ~CCarrierSlotGrid();
    BOOL SubclassDlgItem(UINT nID, CWnd* pParent) {
        BOOL ok = CWnd::SubclassDlgItem(nID, pParent);
        if (ok) {
            if (pParent && pParent->GetFont()) SetFont(pParent->GetFont());
            EnsureFonts();
            Invalidate(FALSE);
        }
        return ok;
    }
    virtual void PreSubclassWindow() override;
    // åˆå§‹åŒ–
    void InitGrid(int nPorts, int nSlots);
    void SetColumnWidths(int slotColWidth, int portColWidth);
    void SetRowHeight(int cy);
    void SetHeaderHeight(int cy);
    void SetShowMaterialToggle(BOOL bShow);
    // è¯»/写
    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;
    void SetSlotGlass(int portIndex, int slotIndex, BOOL hasGlass, LPCTSTR coreId, int material);
    void SetSlotChecked(int portIndex, int slotIndex, BOOL checked);
    BOOL GetSlotChecked(int portIndex, int slotIndex) const;
    int  GetSlotMaterialType(int portIndex, int slotIndex) const;
    void SetSlotMaterialType(int portIndex, int slotIndex, int material, BOOL bNotify = TRUE);
    CString GetDisplayId(int portIndex, int slotIndex) const;
    void    CheckAllInPort(int portIndex, BOOL checked, BOOL bNotify = TRUE);
    void RebuildTexts();
    void EnableColumnResize(BOOL enable) { m_bAllowResize = !!enable; Invalidate(FALSE); }
    // è®¡ç®—最佳大小:
// - CalcBestClientSize:内容区域(不含滚动条/非客户区)刚好容纳表头+所有行、全部列
// - CalcBestWindowSize:在当前窗口样式下,将“内容大小”转换为窗口外框大小(会考虑 WS_BORDER/CLIENTEDGE ç­‰ï¼‰
//   é»˜è®¤æŒ‰â€œéšè—æ»šåŠ¨æ¡â€çš„ç›®æ ‡æ¥ç®—ï¼ˆå³ä¸æŠŠ WS_HSCROLL/WS_VSCROLL è®¡å…¥è°ƒæ•´ï¼‰
    CSize CalcBestClientSize(int nSlotsOverride = -1) const;
    CSize CalcBestWindowSize(BOOL includeNonClient = TRUE, int nSlotsOverride = -1) const;
protected:
    // æ•°æ®
    int                     m_nSlots = 0;
    std::vector<PortColumn> m_ports;
    BOOL                    m_bShowMatToggle = TRUE;
    // å°ºå¯¸/滚动
    int m_rowHeight = 26;
    int m_headerCY = 28;
    int m_slotColCX = 100;
    std::vector<int> m_portColCXs;
    int m_scrollY = 0;
    int m_scrollX = 0;
    int m_slotColMin = 60;
    int m_portColMin = 80;
    // é¢œè‰²
    COLORREF m_colBg = RGB(255, 255, 255);
    COLORREF m_colAlt = RGB(240, 242, 245);
    COLORREF m_colLock = RGB(255, 244, 214);
    COLORREF m_gridMajor = RGB(210, 214, 220);
    COLORREF m_gridMinor = RGB(220, 224, 230);
    COLORREF m_text = RGB(40, 40, 40);
    COLORREF m_textDim = RGB(150, 150, 150);
    // å­—体
    CFont m_fntText;
    CFont m_fntBold;
    CFont m_fntSmall;
    // æ‹–动列宽
    bool m_bResizing = false;
    int  m_resizeEdge = -1;   // 0=Slot|Port1;1..N-1=Port i|i+1
    int  m_resizeStartX = 0;
    int  m_slotColCXStart = 0;
    std::vector<int> m_portColCXsStart;
    int  m_hitEdgeHover = -1;
    bool m_bAllowResize = true; // â† æ–°å¢žï¼šæ˜¯å¦å…è®¸æ‹–动列宽
    // å·¥å…·
    void EnsureFonts();
    void UpdateScrollRange();
    int  GetTotalContentWidth() const;
    void NotifySelectionChanged(int port, int slot, BOOL checked);
    void NotifyMaterialChanged(int port, int slot, int material);
    // å‡ ä½•
    CRect GetClientRectNoSB() const;
    BOOL  GetCellRect(int row, int sub, CRect& rc) const; // sub: 0=Slot, 1..N=Port
    CRect GetHeaderRect() const;
    CRect GetHeaderItemRect(int iItem) const;
    CRect GetHeaderCheckboxRect(int iItem) const;
    CRect GetCheckboxRect(const CRect& cell) const;
    CRect GetMaterialTagRect(const CRect& cell) const;
    CRect GetStatusDotRect(const CRect& cell) const;
    BOOL IsColumnResizeEnabled() const { return m_bAllowResize ? TRUE : FALSE; }
    int   HitHeaderEdge(CPoint pt) const;
    // ç»˜åˆ¶
    void DrawFlatCheckbox(CDC* pDC, const CRect& r, bool checked, bool disabled);
    void PaintTo(CDC* pDC);
protected:
    // æ¶ˆæ¯
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
    afx_msg void OnPaint();
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
    afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar); // â˜… çºµå‘滚动条
    afx_msg void OnLButtonDown(UINT nFlags, CPoint pt);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint pt);
    afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
    afx_msg void OnMouseMove(UINT nFlags, CPoint pt);
    afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
    afx_msg void OnShowWindow(BOOL bShow, UINT nStatus);
    afx_msg void OnWindowPosChanged(WINDOWPOS* wp);
    DECLARE_MESSAGE_MAP()
};
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
@@ -32,6 +32,10 @@
    ON_WM_DESTROY()
    ON_EN_CHANGE(IDC_EDIT_PJ_ID, &CCjPage2::OnEnChangeEditPjId)
    ON_CBN_SELCHANGE(IDC_COMBO_RECIPE, &CCjPage2::OnCbnSelchangeComboRecipe)
    ON_BN_CLICKED(IDC_RADIO1, &CCjPage2::OnBnClickedRadio1)
    ON_BN_CLICKED(IDC_RADIO2, &CCjPage2::OnBnClickedRadio2)
    ON_BN_CLICKED(IDC_RADIO3, &CCjPage2::OnBnClickedRadio3)
    ON_BN_CLICKED(IDC_RADIO4, &CCjPage2::OnBnClickedRadio4)
END_MESSAGE_MAP()
@@ -43,15 +47,32 @@
    UpdatePjData();
}
void CCjPage2::SetPjWarps(std::vector<PJWarp>& pjs)
{
    m_pjWarps = pjs;
}
BOOL CCjPage2::OnInitDialog()
{
    CCjPageBase::OnInitDialog();
    m_grid.SubclassDlgItem(IDC_GRID1, this);
     m_grid.InitGrid(4, 8);
    m_grid.SetColumnWidths(100, 220);
    m_grid.SetRowHeight(28);
    m_grid.SetHeaderHeight(32);
    m_grid.EnableColumnResize(FALSE); // ç¦æ­¢æ‹–动列宽
    m_grid.SetPortInfo(0, _T("Port 1"), _T(""));
    m_grid.SetPortInfo(1, _T("Port 2"), _T(""));
    m_grid.SetPortInfo(2, _T("Port 3"), _T(""));
    m_grid.SetPortInfo(3, _T("Port 4"), _T(""));
    UpdatePjData();
        ;
    return TRUE;  // return TRUE unless you set the focus to a control
                  // å¼‚常: OCX å±žæ€§é¡µåº”返回 FALSE
}
@@ -66,26 +87,67 @@
void CCjPage2::Resize()
{
    CCjPageBase::Resize();
    /*
    CWnd* pItem;
    CRect rcClient, rcItem;
    GetClientRect(&rcClient);
    pItem = GetDlgItem(IDC_LABEL_TITLE);
    pItem = GetDlgItem(IDC_GRID1);
    pItem->GetWindowRect(&rcItem);
    pItem->MoveWindow(12, 8, rcClient.Width() - 24, rcItem.Height());
    */
    ScreenToClient(rcItem);
    // è®©æŽ§ä»¶çª—口尺寸自动匹配当前列宽/行数(不出现滚动条)
    if (::IsWindow(m_grid.m_hWnd)) {
        CSize best = m_grid.CalcBestWindowSize(TRUE); // è®¡ç®—到含非客户区的最终窗口大小
        pItem->MoveWindow(rcItem.left, rcItem.top, best.cx, best.cy);
        pItem->Invalidate();
    }
}
void CCjPage2::OnApply() 
{
    //SERVO::CProcessJob*
    if (m_pContext == nullptr) return;
    SERVO::CProcessJob* pProcessJob = (SERVO::CProcessJob*)m_pContext;
    PJWarp* pPjWarp = (PJWarp*)m_pContext;
    SERVO::CProcessJob* pProcessJob = (SERVO::CProcessJob*)pPjWarp->pj;
    // æ›´æ–°åç§°
    BOOL bOkName = TRUE;
    char szBuffer[256];
    GetDlgItemText(IDC_EDIT_PJ_ID, szBuffer, 256);
    for (auto item : m_pjWarps) {
        if (item.pj != m_pContext) {
            SERVO::CProcessJob* temp = (SERVO::CProcessJob*)item.pj;
            if (temp->id().compare(std::string(szBuffer)) == 0) {
                bOkName = FALSE;
                break;
            }
        }
    }
    if (!bOkName) {
        AfxMessageBox("不能使用和其它Process Job相同的ID");
        return;
    }
    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);
}
@@ -102,11 +164,23 @@
    }
    if (m_pContext) {
        SERVO::CProcessJob* pProcessJob = (SERVO::CProcessJob*)m_pContext;
        PJWarp* pPjWarp = (PJWarp*)m_pContext;
        SERVO::CProcessJob* pProcessJob = (SERVO::CProcessJob*)pPjWarp->pj;
        SetDlgItemText(IDC_EDIT_PJ_ID, pProcessJob->id().c_str());
        int idx = pComboBox->FindStringExact(-1, pProcessJob->recipeSpec().c_str());
        if (idx != CB_ERR) pComboBox->SetCurSel(idx);
    }
    // ç¤ºä¾‹ï¼šè®¾ç½®Port信息、锁列、填充Glass
    /*
    m_grid.SetPortInfo(0, _T("Port 1"), _T(""));
    m_grid.SetPortInfo(1, _T("Port 2"), _T(""));
    m_grid.SetPortInfo(2, _T("Port 3"), _T("Carrier C"));
    m_grid.SetPortInfo(3, _T("Port 4"), _T("Carrier D"));
    m_grid.SetPortAllocated(2, TRUE, _T("ProcessJob 1"));
    m_grid.SetSlotGlass(0, 0, TRUE, _T("A00123"), CCarrierSlotGrid::MAT_G1);
    m_grid.SetSlotGlass(0, 1, TRUE, _T("A00124"), CCarrierSlotGrid::MAT_G1);
    */
    m_bContentChangedLock = FALSE;
}
@@ -120,3 +194,35 @@
{
    ContentChanged(0);
}
void CCjPage2::OnBnClickedRadio1()
{
    m_grid.SetPortAllocated(0, FALSE, _T(""));
    m_grid.SetPortAllocated(1, TRUE, _T(""));
    m_grid.SetPortAllocated(2, TRUE, _T(""));
    m_grid.SetPortAllocated(3, TRUE, _T(""));
}
void CCjPage2::OnBnClickedRadio2()
{
    m_grid.SetPortAllocated(0, TRUE, _T(""));
    m_grid.SetPortAllocated(1, FALSE, _T(""));
    m_grid.SetPortAllocated(2, TRUE, _T(""));
    m_grid.SetPortAllocated(3, TRUE, _T(""));
}
void CCjPage2::OnBnClickedRadio3()
{
    m_grid.SetPortAllocated(0, TRUE, _T(""));
    m_grid.SetPortAllocated(1, TRUE, _T(""));
    m_grid.SetPortAllocated(2, FALSE, _T(""));
    m_grid.SetPortAllocated(3, TRUE, _T(""));
}
void CCjPage2::OnBnClickedRadio4()
{
    m_grid.SetPortAllocated(0, TRUE, _T(""));
    m_grid.SetPortAllocated(1, TRUE, _T(""));
    m_grid.SetPortAllocated(2, TRUE, _T(""));
    m_grid.SetPortAllocated(3, FALSE, _T(""));
}
SourceCode/Bond/Servo/CCjPage2.h
@@ -1,7 +1,15 @@
#pragma once
#include "CCjPageBase.h"
#include "ProcessJob.h"
#include "CCarrierSlotGrid.h"
struct PJWarp {
    void* pj;
    int port;
    bool checkSlot[8];
};
// CPjPage1 å¯¹è¯æ¡†
@@ -13,6 +21,9 @@
    CCjPage2(CWnd* pParent = nullptr);   // æ ‡å‡†æž„造函数
    virtual ~CCjPage2();
public:
    void SetPjWarps(std::vector<PJWarp>& pjs);
protected:
    void Resize();
    virtual void OnApply();
@@ -20,6 +31,10 @@
private:
    void UpdatePjData();
private:
    CCarrierSlotGrid m_grid;
    std::vector<PJWarp> m_pjWarps;
// å¯¹è¯æ¡†æ•°æ®
#ifdef AFX_DESIGN_TIME
@@ -35,4 +50,8 @@
    afx_msg void OnDestroy();
    afx_msg void OnEnChangeEditPjId();
    afx_msg void OnCbnSelchangeComboRecipe();
    afx_msg void OnBnClickedRadio1();
    afx_msg void OnBnClickedRadio2();
    afx_msg void OnBnClickedRadio3();
    afx_msg void OnBnClickedRadio4();
};
SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
@@ -20,10 +20,10 @@
CControlJobManagerDlg::~CControlJobManagerDlg()
{
    for (auto pj : m_processJobs) {
        delete pj;
    for (auto item : m_pjWarps) {
        delete (SERVO::CProcessJob*)item.pj;
    }
    m_processJobs.clear();
    m_pjWarps.clear();
    if (m_pControlJob != nullptr) {
        delete m_pControlJob;
@@ -34,7 +34,6 @@
void CControlJobManagerDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_LIST1, m_listBox);
    DDX_Control(pDX, IDC_TREE1, m_tree);    
}
@@ -42,10 +41,6 @@
BEGIN_MESSAGE_MAP(CControlJobManagerDlg, CDialogEx)
    ON_WM_SIZE()
    ON_WM_GETMINMAXINFO()
    ON_BN_CLICKED(IDC_BUTTON_CREATE_CJ, &CControlJobManagerDlg::OnBnClickedCreateCJ)
    ON_BN_CLICKED(IDC_BUTTON_CREATE_PJ, &CControlJobManagerDlg::OnBnClickedButtonCreatePj)
    ON_CLBN_CHKCHANGE(IDC_LIST1, &CControlJobManagerDlg::OnListChkChange) // å¤é€‰çŠ¶æ€å˜æ›´é€šçŸ¥
    ON_LBN_SELCHANGE(IDC_LIST1, &CControlJobManagerDlg::OnLbnSelchangeList1)
    ON_NOTIFY(TVN_SELCHANGED, IDC_TREE1, &CControlJobManagerDlg::OnTvnSelchangedTree1)
    ON_WM_DESTROY()
    ON_BN_CLICKED(IDC_BUTTON_APPLY, &CControlJobManagerDlg::OnBnClickedButtonApply)
@@ -65,7 +60,7 @@
        }
        else if (1 == code) {
            if (contextType == 1) {
                UpProcessJobId((SERVO::CProcessJob*)pContext);
                UpProcessJobId((PJWarp*)pContext);
            }
        }
    };
@@ -93,12 +88,12 @@
    m_pages.push_back(pPage3);
    // ListBox
    m_listBox.ModifyStyle(0, LBS_OWNERDRAWFIXED | LBS_HASSTRINGS);
    m_listBox.SetCheckStyle(BS_AUTOCHECKBOX);
    m_listBox.SetItemHeight(0, max(22, ::GetSystemMetrics(SM_CYMENUCHECK) + 4));
    // tree
    m_tree.ModifyStyle(0, TVS_CHECKBOXES);
    InitData();
    UpdateControlJob();
    UpdateCtrlState();
    Resize();
@@ -131,50 +126,25 @@
    // å…ˆç§»åŠ¨æŒ‰é’®
    pItem = GetDlgItem(IDC_BUTTON_CREATE_PJ);
    pItem->GetWindowRect(&rcItem);
    pItem->MoveWindow(x, y2 - rcItem.Height(), rcItem.Width(), rcItem.Height());
    x += rcItem.Width();
    x += 8;
    pItem = GetDlgItem(IDC_BUTTON_CREATE_CJ);
    pItem->GetWindowRect(&rcItem);
    pItem->MoveWindow(x, y2 - rcItem.Height(), rcItem.Width(), rcItem.Height());
    x += rcItem.Width();
    x += 8;
    pItem = GetDlgItem(IDC_BUTTON_BATH_COMPLETION);
    pItem->GetWindowRect(&rcItem);
    pItem->MoveWindow(x, y2 - rcItem.Height(), rcItem.Width(), rcItem.Height());
    x += rcItem.Width();
    x += 8;
    pItem = GetDlgItem(IDC_BUTTON_BATH_DELETE);
    pItem->GetWindowRect(&rcItem);
    pItem->MoveWindow(x, y2 - rcItem.Height(), rcItem.Width(), rcItem.Height());
    x += rcItem.Width();
    x += 8;
    pItem = GetDlgItem(IDC_BUTTON_APPLY);
    pItem->GetWindowRect(&rcItem);
    pItem->MoveWindow(x2 - rcItem.Width(), y2 - rcItem.Height(), rcItem.Width(), rcItem.Height());
    x2 -= rcItem.Width();
    x2 -= 8;
    pItem = GetDlgItem(IDC_BUTTON_BATH_COMPLETION);
    pItem->GetWindowRect(&rcItem);
    pItem->MoveWindow(x2 - rcItem.Width(), y2 - rcItem.Height(), rcItem.Width(), rcItem.Height());
    x += rcItem.Width();
    x += 8;
    y2 -= rcItem.Height();
    y2 -= 8;
    // åˆ—表控件
    x = 12;
    y = 12;
    pItem = GetDlgItem(IDC_LIST1);
    pItem->MoveWindow(x, y, LEFTWIDTH, 180);
    x += LEFTWIDTH;
    x += 5;
    y += 180;
    y += 5;
    // æ ‘控件
    x = 12;
    y = 12;
    pItem = GetDlgItem(IDC_TREE1);
    pItem->MoveWindow(x, y, LEFTWIDTH, y2 - y);
    x += LEFTWIDTH;
@@ -182,6 +152,7 @@
    // å­é¡µé¢
    x2 = rcClient.right - 12;
    for (auto page : m_pages) {
        page->MoveWindow(x, 12, x2 - x, y2 - 12);
    }
@@ -204,45 +175,7 @@
void CControlJobManagerDlg::UpdateCtrlState()
{
    auto& master = theApp.m_model.getMaster();
    auto cj = master.getControlJob();
    GetDlgItem(IDC_BUTTON_CREATE_PJ)->EnableWindow(TRUE);
    GetDlgItem(IDC_BUTTON_CREATE_CJ)->EnableWindow(
        m_pControlJob == nullptr
    );
    GetDlgItem(IDC_BUTTON_BATH_COMPLETION)->EnableWindow(false);
    GetDlgItem(IDC_BUTTON_BATH_DELETE)->EnableWindow(true);
}
void CControlJobManagerDlg::OnBnClickedButtonCreatePj()
{
    static int index = 0;
    char szBuffer[256];
    sprintf_s(szBuffer, 256, "PJ%03d", ++index);
    SERVO::CProcessJob* pj = new SERVO::CProcessJob(std::string(szBuffer));
    m_processJobs.push_back(pj);
    UpdateProcessJobs();
    UpdateCtrlState();
}
void CControlJobManagerDlg::OnBnClickedCreateCJ()
{
    if (m_pControlJob != nullptr) {
        return;
    }
    m_pControlJob = new SERVO::CControlJob("CJ" + CToolUnits::NowStrSec());
    UpdateControlJob();
    UpdateCtrlState();
}
void CControlJobManagerDlg::UpdateProcessJobs()
{
    m_listBox.ResetContent();
    for (auto item : m_processJobs) {
        int index = m_listBox.InsertString(m_listBox.GetCount(), item->id().c_str());
        m_listBox.SetItemDataPtr(index, item);
    }
}
void CControlJobManagerDlg::UpdateControlJob()
@@ -252,31 +185,13 @@
    HTREEITEM hRoot = m_tree.InsertItem(m_pControlJob->id().c_str(), 0, 0);
    m_tree.SetItemData(hRoot, (DWORD_PTR)m_pControlJob);
    auto& pjs = m_pControlJob->getPjs();
    for (auto pj : pjs) {
        HTREEITEM hItem = m_tree.InsertItem(pj->id().c_str(), 0, 0, hRoot);
        m_tree.SetItemData(hItem, (DWORD_PTR)pj);
    m_tree.SetItemState(hRoot, 0, TVIS_STATEIMAGEMASK);
    for (auto& item : m_pjWarps) {
        HTREEITEM hItem = m_tree.InsertItem(((SERVO::CProcessJob*)item.pj)->id().c_str(), 0, 0, hRoot);
        m_tree.SetItemData(hItem, (DWORD_PTR)&item);
        m_tree.SetItemState(hItem, INDEXTOSTATEIMAGEMASK(false ? 2 : 1), TVIS_STATEIMAGEMASK);
    }
    m_tree.Expand(hRoot, TVE_EXPAND);
}
void CControlJobManagerDlg::OnListChkChange()
{
    int idx = m_listBox.GetCurSel();
    if (idx != LB_ERR) {
        SERVO::CProcessJob* pj = (SERVO::CProcessJob*)m_listBox.GetItemDataPtr(idx);
        BOOL checked = m_listBox.GetCheck(idx); // è‹¥ä¸‰æ€: 0/1/2
        if (checked) {
            if (AddPorcessJob(pj)) {
                UpdateControlJob();
            }
        }
        else {
            if (RemovePorcessJob(pj)) {
                UpdateControlJob();
            }
        }
    }
}
bool CControlJobManagerDlg::AddPorcessJob(SERVO::CProcessJob* pj)
@@ -289,21 +204,6 @@
{
    if (m_pControlJob == nullptr) return false;
    return m_pControlJob->removePjPointer(pj->id());
}
void CControlJobManagerDlg::OnLbnSelchangeList1()
{
    int idx = m_listBox.GetCurSel();
    if (idx != LB_ERR) {
        SERVO::CProcessJob* pj = (SERVO::CProcessJob*)m_listBox.GetItemDataPtr(idx);
        ASSERT(m_pages.size() == 3);
        m_pages[0]->ShowWindow(SW_HIDE);
        m_pages[1]->ShowWindow(SW_SHOW);
        m_pages[2]->ShowWindow(SW_HIDE);
        m_pages[1]->SetContext(pj, 1);
    }
}
void CControlJobManagerDlg::OnTvnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult)
@@ -321,12 +221,13 @@
            m_pages[2]->ShowWindow(SW_SHOW);
        }
        else if (m_tree.GetParentItem(hParent) == nullptr) {
            SERVO::CProcessJob* pj = (SERVO::CProcessJob*)m_tree.GetItemData(hSel);
            PJWarp* pjWarp = (PJWarp*)m_tree.GetItemData(hSel);
            m_pages[0]->ShowWindow(SW_HIDE);
            m_pages[1]->ShowWindow(SW_SHOW);
            m_pages[2]->ShowWindow(SW_HIDE);
            m_pages[1]->SetContext(pjWarp, 1);
            m_pages[1]->SetContext(pj, 1);
            ((CCjPage2*)m_pages[1])->SetPjWarps(m_pjWarps);
        }
        else {
            // æœ‰ç¥–å…ˆ â†’ ç¬¬ä¸‰å±‚及以下 â†’ Glass
@@ -346,6 +247,21 @@
    }
}
void CControlJobManagerDlg::InitData()
{
    ASSERT(m_pControlJob == nullptr);
    m_pControlJob = new SERVO::CControlJob("CJ" + CToolUnits::NowStrSec());
    char szBuffer[256];
    for (int i = 0; i < 4; i++) {
        sprintf_s(szBuffer, 256, "PJ%03d", i + 1);
        SERVO::CProcessJob* pj = new SERVO::CProcessJob(std::string(szBuffer));
        PJWarp pjWarp;
        pjWarp.pj = pj;
        pjWarp.port = -1;
        m_pjWarps.push_back(pjWarp);
    }
}
void CControlJobManagerDlg::OnBnClickedButtonApply()
{
@@ -355,20 +271,24 @@
    GetDlgItem(IDC_BUTTON_APPLY)->EnableWindow(FALSE);
}
void CControlJobManagerDlg::UpProcessJobId(SERVO::CProcessJob* pJob)
void CControlJobManagerDlg::UpProcessJobId(PJWarp* pjWarp)
{
    int count = m_listBox.GetCount();
    for (int idx = 0; idx < count; idx++) {
        if ((m_listBox.GetItemDataPtr(idx) == (void*)pJob)) {
            BOOL check = m_listBox.GetCheck(idx);
            BOOL wasCurSel = (m_listBox.GetCurSel() == idx); // å•选模式
            m_listBox.DeleteString(idx);
            int newIdx = m_listBox.InsertString(idx, pJob->id().c_str());
            m_listBox.SetItemDataPtr(newIdx, pJob);
            m_listBox.SetCheck(newIdx, check);
            if (wasCurSel) {
                m_listBox.SetCurSel(newIdx);
    // æ›´æ–°æ ‘控件
    // éåŽ†æ ¹èŠ‚ç‚¹
    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 == pjWarp) {
                SERVO::CProcessJob* pj = (SERVO::CProcessJob*)pjWarp->pj;
                m_tree.SetItemText(hChild, pj->id().c_str());
                return; // æ‰¾åˆ°å°±è¿”回
            }
            hChild = m_tree.GetNextSiblingItem(hChild);
        }
        hRoot = m_tree.GetNextSiblingItem(hRoot);
    }
}
SourceCode/Bond/Servo/CControlJobManagerDlg.h
@@ -18,17 +18,16 @@
private:
    void Resize();
    void UpdateCtrlState();
    void UpdateProcessJobs();
    void UpdateControlJob();
    bool AddPorcessJob(SERVO::CProcessJob* pj);
    bool RemovePorcessJob(SERVO::CProcessJob* pj);
    void UpProcessJobId(SERVO::CProcessJob* pJob);
    void UpProcessJobId(PJWarp* pjWarp);
    void InitData();
private:
    std::vector<CCjPageBase*> m_pages;
    SERVO::CControlJob* m_pControlJob;
    std::vector<SERVO::CProcessJob*> m_processJobs;
    CCheckListBox m_listBox;
    std::vector<PJWarp> m_pjWarps;
    CApredTreeCtrl2 m_tree;
@@ -45,10 +44,6 @@
    virtual BOOL OnInitDialog();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI);
    afx_msg void OnBnClickedCreateCJ();
    afx_msg void OnBnClickedButtonCreatePj();
    afx_msg void OnListChkChange();
    afx_msg void OnLbnSelchangeList1();
    afx_msg void OnTvnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult);
    afx_msg void OnDestroy();
    afx_msg void OnBnClickedButtonApply();
SourceCode/Bond/Servo/CLoadPort.cpp
@@ -353,7 +353,7 @@
        // ä»Žé…ç½®è¯»å‡ºçš„enable,初始化时写给efem
        static int i_enable[4] = { 0 };
        if ((++i_enable[m_nIndex]) == 10 + m_nIndex) {
        if ((++i_enable[m_nIndex]) == 20 + m_nIndex) {
            eablePort(m_bEnable, [&](int code) -> int {
                LOGI("<LoadPort-%d>eablePort:code=%d", m_nIndex, code);
                return 0;
SourceCode/Bond/Servo/Servo.rc
Binary files differ
SourceCode/Bond/Servo/Servo.vcxproj
@@ -210,6 +210,8 @@
    <ClInclude Include="..\jsoncpp\include\json\writer.h" />
    <ClInclude Include="..\jsoncpp\lib_json\json_batchallocator.h" />
    <ClInclude Include="CBaseDlg.h" />
    <ClInclude Include="CCarrierSlotGrid.h" />
    <ClInclude Include="CCarrierSlotSelector.h" />
    <ClInclude Include="CCjPage2.h" />
    <ClInclude Include="CCjPage3.h" />
    <ClInclude Include="CCjPageBase.h" />
@@ -392,6 +394,8 @@
      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
    </ClCompile>
    <ClCompile Include="CBaseDlg.cpp" />
    <ClCompile Include="CCarrierSlotGrid.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,8 @@
    <ClCompile Include="CCjPage2.cpp" />
    <ClCompile Include="CCjPage3.cpp" />
    <ClCompile Include="CCjPageBase.cpp" />
    <ClCompile Include="CCarrierSlotSelector.cpp" />
    <ClCompile Include="CCarrierSlotGrid.cpp" />
  </ItemGroup>
  <ItemGroup>
    <ClInclude Include="AlarmManager.h" />
@@ -433,6 +435,8 @@
    <ClInclude Include="CCjPage2.h" />
    <ClInclude Include="CCjPage3.h" />
    <ClInclude Include="CCjPageBase.h" />
    <ClInclude Include="CCarrierSlotSelector.h" />
    <ClInclude Include="CCarrierSlotGrid.h" />
  </ItemGroup>
  <ItemGroup>
    <ResourceCompile Include="Servo.rc" />
SourceCode/Bond/Servo/resource.h
Binary files differ