mrDarker
6 天以前 829fe6c6bc33d53fda9c31fd45a37e1df87befff
SourceCode/Bond/Servo/CControlJobDlg.cpp
@@ -6,13 +6,35 @@
#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;
}
@@ -23,14 +45,16 @@
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()
@@ -42,7 +66,7 @@
// CControlJobDlg 消息处理程序
BOOL CControlJobDlg::OnInitDialog()
{
   CDialogEx::OnInitDialog();
    CDialogEx::OnInitDialog();
    // label字体
@@ -69,20 +93,12 @@
    // 控件状态
    Resize();
    OnBnClickedButtonReload();
    SetTimer(1, 2000, nullptr);
    // 如果m_pControlJob为空,取master的
    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);
   return TRUE;  // return TRUE unless you set the focus to a control
              // 异常: OCX 属性页应返回 FALSE
    return TRUE;  // return TRUE unless you set the focus to a control
                  // 异常: OCX 属性页应返回 FALSE
}
void CControlJobDlg::OnSize(UINT nType, int cx, int cy)
@@ -101,15 +117,30 @@
    // 关闭按钮
    int x2 = rcClient.right - 12;
    int y = rcClient.bottom - 12;
    pItem = GetDlgItem(IDCANCEL);
    pItem->GetClientRect(&rcItem);
    pItem->MoveWindow(rcClient.right - 12 - rcItem.Width(),
    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);
@@ -143,43 +174,221 @@
void CControlJobDlg::LoadData(SERVO::CControlJob* pControlJob)
{
    m_listCtrl.ClearTree();
    if (pControlJob == nullptr) return;
    // —— 工具:按“第一列键”在 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;
    };
    auto* root1 = m_listCtrl.InsertRoot({ pControlJob->id().c_str(), _T("ControlJob"),
            pControlJob->getStateText().c_str(), _T(""), _T(""), pControlJob->failReason().c_str()});
    // —— 工具:有则更新、无则创建(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) {
        auto* root2 = m_listCtrl.InsertChild(root1, {pj->id().c_str(),  _T("ProcessJob"),
                pj->getStateText().c_str(), pj->recipeSpec().c_str(), _T(""), pj->failReason().c_str()});
        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) {
                SERVO::CGlass* pGlass = (SERVO::CGlass*)g;
                if (pGlass != nullptr) {
                    int port, slot;
                auto* pGlass = static_cast<SERVO::CGlass*>(g);
                if (pGlass) {
                    int port = 0, slot = 0;
                    pGlass->getOrginPort(port, slot);
                    std::string carrier = c.carrierId + " / Port" + std::to_string(port + 1) + " / Slot" + std::to_string(slot + 1);
                    m_listCtrl.InsertChild(root2, { pGlass->getID().c_str(), _T("Glass"),
                        pGlass->getStateText().c_str(), _T(""), carrier.c_str(), _T("") });
                    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 {
                    m_listCtrl.InsertChild(root2, { "Null", _T("Glass"), _T(""), _T(""), c.carrierId.c_str(), _T("") });
                    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("");
                    }
                }
            }
        }
        root2->expanded = true;
        // 删除本次不存在的 Glass 子节点
        RemoveStaleChildren(pjNode, gKeysWanted);
    }
    root1->expanded = true;
    // 删除本次不存在的 PJ 子节点
    RemoveStaleChildren(m_rootNode, pjKeysWanted);
    // 3.4 重建可见行(不改变 expanded 标志,避免闪烁/折叠状态丢失)
    m_listCtrl.RebuildVisible();
}
void CControlJobDlg::OnBnClickedButtonCompletionBath()
{
    if (theApp.m_model.getMaster().completeControlJob("测试手动结批")) {
    if (theApp.m_model.getMaster().forceCompleteControlJob("测试手动结批")) {
        AfxMessageBox("结批完成");
    }
    OnBnClickedButtonReload();
}
void CControlJobDlg::OnBnClickedButtonReload()
{
    auto* cj = m_pControlJob;
    if (cj == nullptr) {
        cj = theApp.m_model.getMaster().getControlJob();
@@ -190,3 +399,12 @@
    GetDlgItem(IDC_BUTTON_COMPLETION_BATH)->EnableWindow(cj != nullptr);
    LoadData(cj);
}
void CControlJobDlg::OnTimer(UINT_PTR nIDEvent)
{
    if (1 == nIDEvent) {
        OnBnClickedButtonReload();
    }
    CDialogEx::OnTimer(nIDEvent);
}