mrDarker
6 天以前 829fe6c6bc33d53fda9c31fd45a37e1df87befff
SourceCode/Bond/Servo/CControlJobDlg.cpp
@@ -6,15 +6,37 @@
#include "CControlJobDlg.h"
#include "afxdialogex.h"
// ===== 新增:标准库头 =====
#include <array>
#include <string>
#include <unordered_set>
#include <algorithm>
// ===== 新增:CString 的 Hash/Equal(跨 ANSI/Unicode 通吃)=====
struct CStringHash {
    size_t operator()(const CString& s) const noexcept {
#ifdef _UNICODE
        return std::hash<std::wstring>{}(std::wstring(s.GetString()));
#else
        return std::hash<std::string>{}(std::string(s.GetString()));
#endif
    }
};
struct CStringEqual {
    bool operator()(const CString& a, const CString& b) const noexcept {
        return a == b;
    }
};
// CControlJobDlg 对话框
IMPLEMENT_DYNAMIC(CControlJobDlg, CDialogEx)
CControlJobDlg::CControlJobDlg(CWnd* pParent /*=nullptr*/)
   : CDialogEx(IDD_DIALOG_CONTROL_JOB, pParent)
    : CDialogEx(IDD_DIALOG_CONTROL_JOB, pParent)
{
    m_pControlJob = nullptr;
}
CControlJobDlg::~CControlJobDlg()
@@ -23,51 +45,366 @@
void CControlJobDlg::DoDataExchange(CDataExchange* pDX)
{
   CDialogEx::DoDataExchange(pDX);
   DDX_Control(pDX, IDC_LIST1, m_listCtrl);
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_LIST1, m_listCtrl);
}
BEGIN_MESSAGE_MAP(CControlJobDlg, CDialogEx)
    ON_WM_SIZE()
    ON_BN_CLICKED(IDC_BUTTON_COMPLETION_BATH, &CControlJobDlg::OnBnClickedButtonCompletionBath)
    ON_BN_CLICKED(IDC_BUTTON_RELOAD, &CControlJobDlg::OnBnClickedButtonReload)
    ON_WM_TIMER()
END_MESSAGE_MAP()
void CControlJobDlg::SetControlJob(SERVO::CControlJob* pControlJob)
{
    m_pControlJob = pControlJob;
}
// CControlJobDlg 消息处理程序
BOOL CControlJobDlg::OnInitDialog()
{
   CDialogEx::OnInitDialog();
    CDialogEx::OnInitDialog();
    DWORD dwStyle = m_listCtrl.GetExtendedStyle();
    dwStyle |= LVS_EX_FULLROWSELECT;
    dwStyle |= LVS_EX_GRIDLINES;
    m_listCtrl.SetExtendedStyle(dwStyle);
    // label字体
    LOGFONT lf{};
    GetFont()->GetLogFont(&lf);
    lf.lfHeight = -20;
    lf.lfWeight = FW_BOLD;
    _tcscpy_s(lf.lfFaceName, _T("Arial"));
    m_fontNoJob.CreateFontIndirect(&lf);
    GetDlgItem(IDC_LABEL_NO_JOB)->SetFont(&m_fontNoJob);
    // 列表控件
    HIMAGELIST imageList = ImageList_Create(24, 24, ILC_COLOR24, 1, 1);
    ListView_SetImageList(m_listCtrl.GetSafeHwnd(), imageList, LVSIL_SMALL);
    // m_list 已经是对话框上的 CExpandableListCtrl 成员(拖控件改类)
    m_listCtrl.ModifyStyle(0, LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS);
    m_listCtrl.InsertColumn(0, _T("名称"), LVCFMT_LEFT, 260);
    m_listCtrl.InsertColumn(1, _T("状态"), LVCFMT_LEFT, 120);
    m_listCtrl.InsertColumn(2, _T("描述"), LVCFMT_LEFT, 260);
    m_listCtrl.InsertColumn(0, _T("ID"), LVCFMT_LEFT, 180);
    m_listCtrl.InsertColumn(1, _T("类型"), LVCFMT_LEFT, 120);
    m_listCtrl.InsertColumn(2, _T("状态"), LVCFMT_LEFT, 120);
    m_listCtrl.InsertColumn(3, _T("配方"), LVCFMT_LEFT, 120);
    m_listCtrl.InsertColumn(4, _T("Port / Carrier / Slot"), LVCFMT_LEFT, 180);
    m_listCtrl.InsertColumn(5, _T("描述"), LVCFMT_LEFT, 220);
    auto* root1 = m_listCtrl.InsertRoot({ _T("EFEM"), _T("Ready"), _T("Front End Module") });
    m_listCtrl.InsertChild(root1, { _T("Slot #1"), _T("OK"), _T("150mm wafer") });
    m_listCtrl.InsertChild(root1, { _T("Slot #2"), _T("Empty"), _T("") });
    auto* root2 = m_listCtrl.InsertRoot({ _T("Bonder"), _T("Run"), _T("G1+G2 Process") });
    auto* ch21 = m_listCtrl.InsertChild(root2, { _T("Job A"), _T("Proc"), _T("Step 1") });
    m_listCtrl.InsertChild(ch21, { _T("SubStep A1"), _T("Done"), _T("Align") });
    m_listCtrl.InsertChild(ch21, { _T("SubStep A2"), _T("Run"),  _T("Bond") });
    // 控件状态
    Resize();
    OnBnClickedButtonReload();
    SetTimer(1, 2000, nullptr);
    // 初始让顶层展开
    //root1->expanded = true;
    //root2->expanded = true;
    return TRUE;  // return TRUE unless you set the focus to a control
                  // 异常: OCX 属性页应返回 FALSE
}
void CControlJobDlg::OnSize(UINT nType, int cx, int cy)
{
    CDialogEx::OnSize(nType, cx, cy);
    if (GetDlgItem(IDC_LIST1) == nullptr) return;
    Resize();
}
void CControlJobDlg::Resize()
{
    CRect rcClient, rcItem;
    CWnd* pItem;
    GetClientRect(rcClient);
    // 关闭按钮
    int x2 = rcClient.right - 12;
    int y = rcClient.bottom - 12;
    pItem = GetDlgItem(IDCANCEL);
    pItem->GetClientRect(&rcItem);
    pItem->MoveWindow(x2 - rcItem.Width(),
        y - rcItem.Height(),
        rcItem.Width(), rcItem.Height());
    x2 -= rcItem.Width();
    x2 -= 8;
    pItem = GetDlgItem(IDC_BUTTON_RELOAD);
    pItem->GetClientRect(&rcItem);
    pItem->MoveWindow(x2 - rcItem.Width(),
        y - rcItem.Height(),
        rcItem.Width(), rcItem.Height());
    // 结批按钮
    pItem = GetDlgItem(IDC_BUTTON_COMPLETION_BATH);
    pItem->GetClientRect(&rcItem);
    pItem->MoveWindow(12, y - rcItem.Height(),
        rcItem.Width(), rcItem.Height());
    y -= rcItem.Height();
    y -= 12;
    // 线
    pItem = GetDlgItem(IDC_LINE1);
    pItem->MoveWindow(12, y, rcClient.Width() - 24, 2);
    y -= 2;
    // Label
    pItem = GetDlgItem(IDC_LABEL_NO_JOB);
    pItem->GetClientRect(&rcItem);
    pItem->MoveWindow((rcClient.Width() - rcItem.Width()) / 2,
        (y - rcItem.Height()) / 2,
        rcItem.Width(), rcItem.Height());
    // ListCtrl
    pItem = GetDlgItem(IDC_LIST1);
    pItem->MoveWindow(12, 12, rcClient.Width() - 24, y - 12);
}
void CControlJobDlg::ShowGroup1(BOOL bShow)
{
    GetDlgItem(IDC_LABEL_NO_JOB)->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
    GetDlgItem(IDC_LINE1)->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
}
void CControlJobDlg::ShowGroup2(BOOL bShow)
{
    GetDlgItem(IDC_LIST1)->ShowWindow(bShow ? SW_SHOW : SW_HIDE);
}
void CControlJobDlg::LoadData(SERVO::CControlJob* pControlJob)
{
    // —— 工具:按“第一列键”在 parent 下查找子节点
    auto FindChildByKey = [&](CExpandableListCtrl::Node* parent, LPCTSTR key)->CExpandableListCtrl::Node* {
        if (!parent) return nullptr;
        for (auto& up : parent->children) {
            auto* n = up.get();
            if (n && n->cols.size() > 0 && n->cols[0].CompareNoCase(key) == 0)
                return n;
        }
        return nullptr;
    };
    // —— 工具:有则更新、无则创建(6列)
    auto EnsureChild = [&](CExpandableListCtrl::Node* parent, const std::array<CString, 6>& cols)->CExpandableListCtrl::Node* {
        CExpandableListCtrl::Node* n = FindChildByKey(parent, cols[0]);
        if (!n) {
            n = m_listCtrl.InsertChild(parent, { cols[0], cols[1], cols[2], cols[3], cols[4], cols[5] });
        }
        else {
            if ((int)n->cols.size() < 6) n->cols.resize(6);
            for (int i = 0; i < 6; i++) if (n->cols[i] != cols[i]) n->cols[i] = cols[i];
        }
        return n;
    };
    // —— 工具:删除 parent 下“本次未出现”的子节点(基于第一列键)
    auto RemoveStaleChildren = [&](CExpandableListCtrl::Node* parent, const std::unordered_set<CString, CStringHash, CStringEqual>& keep) {
        if (!parent) return;
        auto& vec = parent->children;
        vec.erase(std::remove_if(vec.begin(), vec.end(), [&](const std::unique_ptr<CExpandableListCtrl::Node>& up) {
            auto* n = up.get();
            if (!n || n->cols.empty()) return true; // 防御:无效行直接删
            return keep.find(n->cols[0]) == keep.end();
            }), vec.end());
    };
    // ———————————— 1) 无数据:清空并复位缓存 ————————————
    if (!pControlJob) {
        m_listCtrl.ClearTree();
        m_rootNode = nullptr;
        m_lastCjPtr = nullptr;
        m_lastCjId.Empty();
        m_listCtrl.RebuildVisible();
        return;
    }
    const CString curId = pControlJob->id().c_str();
    const bool cjChanged = (pControlJob != m_lastCjPtr) || (curId != m_lastCjId);
    // ———————————— 2) CJ 变了:整棵重建(保留展开标记不必管,重建即可) ————————————
    if (cjChanged || !m_rootNode) {
        m_listCtrl.ClearTree();
        m_rootNode = m_listCtrl.InsertRoot({
            pControlJob->id().c_str(), _T("ControlJob"),
            pControlJob->getStateText().c_str(), _T(""), _T(""),
            pControlJob->failReason().c_str()
            });
        auto pjs = pControlJob->getPjs();
        for (auto pj : pjs) {
            auto* pjNode = m_listCtrl.InsertChild(m_rootNode, {
                pj->id().c_str(), _T("ProcessJob"),
                pj->getStateText().c_str(), pj->recipeSpec().c_str(), _T(""),
                pj->failReason().c_str()
                });
            auto cs = pj->carriers();
            for (auto c : cs) {
                for (auto g : c.contexts) {
                    auto* pGlass = static_cast<SERVO::CGlass*>(g);
                    if (pGlass) {
                        int port = 0, slot = 0;
                        pGlass->getOrginPort(port, slot);
                        CString carrier; carrier.Format(_T("%s / Port%d / Slot%d"),
                            CString(c.carrierId.c_str()), port + 1, slot + 1);
                        m_listCtrl.InsertChild(pjNode, {
                            pGlass->getID().c_str(), _T("Glass"),
                            pGlass->getStateText().c_str(), _T(""),
                            carrier, _T("")
                            });
                    }
                    else {
                        m_listCtrl.InsertChild(pjNode, {
                            _T("Null@") + CString(c.carrierId.c_str()), _T("Glass"),
                            _T(""), _T(""), CString(c.carrierId.c_str()), _T("")
                            });
                    }
                }
            }
            pjNode->expanded = true;
        }
        m_rootNode->expanded = true;
        m_lastCjPtr = pControlJob;
        m_lastCjId = curId;
        m_listCtrl.RebuildVisible();
        return;
    }
    // ———————————— 3) CJ 未变:增量更新 ————————————
    // 3.1 更新 CJ 行文本(状态可能变化)
    if ((int)m_rootNode->cols.size() < 6) m_rootNode->cols.resize(6);
    m_rootNode->cols[0] = pControlJob->id().c_str();
    m_rootNode->cols[1] = _T("ControlJob");
    m_rootNode->cols[2] = pControlJob->getStateText().c_str();
    // cols[3] 保留为空
    // cols[4] 保留为空
    m_rootNode->cols[5] = pControlJob->failReason().c_str();
    // 3.2 同步 PJ 层
    std::unordered_set<CString, CStringHash, CStringEqual> pjKeysWanted;
    auto pjs = pControlJob->getPjs();
    for (auto pj : pjs) {
        CString pjId = pj->id().c_str();
        pjKeysWanted.insert(pjId);
        auto* pjNode = FindChildByKey(m_rootNode, pjId);
        if (!pjNode) {
            pjNode = m_listCtrl.InsertChild(m_rootNode, {
                pjId, _T("ProcessJob"),
                pj->getStateText().c_str(), pj->recipeSpec().c_str(), _T(""),
                pj->failReason().c_str()
                });
            pjNode->expanded = true; // 初次出现默认展开
        }
        else {
            if ((int)pjNode->cols.size() < 6) pjNode->cols.resize(6);
            pjNode->cols[1] = _T("ProcessJob");
            pjNode->cols[2] = pj->getStateText().c_str();
            pjNode->cols[3] = pj->recipeSpec().c_str();
            pjNode->cols[5] = pj->failReason().c_str();
        }
        // 3.3 同步 Glass 层(第一列键:GlassID;空对象用 "Null@CarrierId" 防冲突)
        std::unordered_set<CString, CStringHash, CStringEqual> gKeysWanted;
        auto cs = pj->carriers();
        for (auto c : cs) {
            for (auto g : c.contexts) {
                auto* pGlass = static_cast<SERVO::CGlass*>(g);
                if (pGlass) {
                    int port = 0, slot = 0;
                    pGlass->getOrginPort(port, slot);
                    CString carrier; carrier.Format(_T("%s / Port%d / Slot%d"),
                        CString(c.carrierId.c_str()), port + 1, slot + 1);
                    CString gid = pGlass->getID().c_str();
                    gKeysWanted.insert(gid);
                    auto* rowG = FindChildByKey(pjNode, gid);
                    if (!rowG) {
                        m_listCtrl.InsertChild(pjNode, {
                            gid, _T("Glass"),
                            pGlass->getStateText().c_str(), _T(""),
                            carrier, _T("")
                            });
                    }
                    else {
                        if ((int)rowG->cols.size() < 6) rowG->cols.resize(6);
                        rowG->cols[1] = _T("Glass");
                        rowG->cols[2] = pGlass->getStateText().c_str();
                        rowG->cols[3] = _T("");
                        rowG->cols[4] = carrier;
                        rowG->cols[5] = _T("");
                    }
                }
                else {
                    CString key = _T("Null@") + CString(c.carrierId.c_str());
                    gKeysWanted.insert(key);
                    auto* rowG = FindChildByKey(pjNode, key);
                    if (!rowG) {
                        m_listCtrl.InsertChild(pjNode, {
                            key, _T("Glass"), _T(""), _T(""),
                            CString(c.carrierId.c_str()), _T("")
                            });
                    }
                    else {
                        if ((int)rowG->cols.size() < 6) rowG->cols.resize(6);
                        rowG->cols[1] = _T("Glass");
                        rowG->cols[2] = _T("");
                        rowG->cols[3] = _T("");
                        rowG->cols[4] = CString(c.carrierId.c_str());
                        rowG->cols[5] = _T("");
                    }
                }
            }
        }
        // 删除本次不存在的 Glass 子节点
        RemoveStaleChildren(pjNode, gKeysWanted);
    }
    // 删除本次不存在的 PJ 子节点
    RemoveStaleChildren(m_rootNode, pjKeysWanted);
    // 3.4 重建可见行(不改变 expanded 标志,避免闪烁/折叠状态丢失)
    m_listCtrl.RebuildVisible();
}
   return TRUE;  // return TRUE unless you set the focus to a control
              // 异常: OCX 属性页应返回 FALSE
void CControlJobDlg::OnBnClickedButtonCompletionBath()
{
    if (theApp.m_model.getMaster().forceCompleteControlJob("测试手动结批")) {
        AfxMessageBox("结批完成");
    }
    OnBnClickedButtonReload();
}
void CControlJobDlg::OnBnClickedButtonReload()
{
    auto* cj = m_pControlJob;
    if (cj == nullptr) {
        cj = theApp.m_model.getMaster().getControlJob();
    }
    ShowGroup1(cj == nullptr);
    ShowGroup2(cj != nullptr);
    GetDlgItem(IDC_BUTTON_COMPLETION_BATH)->EnableWindow(cj != nullptr);
    LoadData(cj);
}
void CControlJobDlg::OnTimer(UINT_PTR nIDEvent)
{
    if (1 == nIDEvent) {
        OnBnClickedButtonReload();
    }
    CDialogEx::OnTimer(nIDEvent);
}