LAPTOP-SNT8I5JK\Boounion
2025-09-20 1095cb22a8fe1c9ed840fc2a3852ba518589b077
1.刷新,优化,增加工具条按钮;
已添加2个文件
已修改9个文件
299 ■■■■ 文件已修改
SourceCode/Bond/Servo/CControlJobDlg.cpp 264 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJobDlg.h 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CGlass.cpp 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CMaster.cpp 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/Servo.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/ServoDlg.cpp 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/TopToolbar.cpp 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/TopToolbar.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/Res/cassette_gray_32.ico 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/x64/Debug/Res/cassette_high_32.ico 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CControlJobDlg.cpp
@@ -6,6 +6,28 @@
#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 对话框
@@ -31,6 +53,8 @@
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()
@@ -69,17 +93,9 @@
    // 控件状态
    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
@@ -101,10 +117,19 @@
    // 关闭按钮
    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());
@@ -149,34 +174,207 @@
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* root2 = m_listCtrl.InsertChild(root1, {pj->id().c_str(),  _T("ProcessJob"),
                pj->getStateText().c_str(), pj->recipeSpec().c_str(), _T(""), pj->failReason().c_str()});
            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) {
                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);
                        m_listCtrl.InsertChild(pjNode, {
                            pGlass->getID().c_str(), _T("Glass"),
                            pGlass->getStateText().c_str(), _T(""),
                            carrier, _T("")
                            });
                }
                else {
                    m_listCtrl.InsertChild(root2, { "Null", _T("Glass"), _T(""), _T(""), c.carrierId.c_str(), _T("") });
                        m_listCtrl.InsertChild(pjNode, {
                            _T("Null@") + CString(c.carrierId.c_str()), _T("Glass"),
                            _T(""), _T(""), CString(c.carrierId.c_str()), _T("")
                            });
                }
            }
        }
        root2->expanded = true;
            pjNode->expanded = true;
    }
    root1->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();
}
@@ -186,6 +384,11 @@
        AfxMessageBox("结批完成");
    }
    OnBnClickedButtonReload();
}
void CControlJobDlg::OnBnClickedButtonReload()
{
    auto* cj = m_pControlJob;
    if (cj == nullptr) {
        cj = theApp.m_model.getMaster().getControlJob();
@@ -196,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);
}
SourceCode/Bond/Servo/CControlJobDlg.h
@@ -26,6 +26,11 @@
    SERVO::CControlJob* m_pControlJob;
    CFont m_fontNoJob;
    // 记录上一次的 CJ 身份(指针/ID)
    SERVO::CControlJob* m_lastCjPtr = nullptr;
    CString             m_lastCjId;
    CExpandableListCtrl::Node* m_rootNode = nullptr;
protected:
    CExpandableListCtrl m_listCtrl;
@@ -43,4 +48,6 @@
    virtual BOOL OnInitDialog();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnBnClickedButtonCompletionBath();
    afx_msg void OnBnClickedButtonReload();
    afx_msg void OnTimer(UINT_PTR nIDEvent);
};
SourceCode/Bond/Servo/CGlass.cpp
@@ -306,7 +306,7 @@
            return "Queued";
            break;
        case SERVO::GlsState::Completed:
            return "Queued";
            return "Completed";
            break;
        case SERVO::GlsState::Aborted:
            return "Aborted";
@@ -322,10 +322,8 @@
    }
    bool CGlass::queue() {
        LOGI("CGlass::queue 01 %s", m_strID.c_str());
        if (m_state != GlsState::NoState) return false;
        markQueued();
        LOGI("CGlass::queue 02 %s", m_strID.c_str());
        return true;
    }
@@ -350,11 +348,9 @@
    }
    bool CGlass::complete() {
        LOGI("CGlass::complete 01 %s", m_strID.c_str());
        if (m_state != GlsState::InProcess && m_state != GlsState::Paused) return false;
        m_state = GlsState::Completed;
        markEnd();
        LOGI("CGlass::complete 02 %s", m_strID.c_str());
        return true;
    }
SourceCode/Bond/Servo/CMaster.cpp
@@ -1480,6 +1480,8 @@
                                if (m_listener.onCjEnd != nullptr) {
                                    m_listener.onCjEnd(this, pJob);
                                }
                                completeControlJob("工艺正常完成");
                            }
                        }
                    }
SourceCode/Bond/Servo/Servo.rc
Binary files differ
SourceCode/Bond/Servo/ServoDlg.cpp
@@ -1039,20 +1039,17 @@
        }
    }
    else if (id == IDC_BUTTON_JOBS) {
        static int i = 0; i++;
        if (i % 2 == 0) {
            CControlJobManagerDlg dlg;
            dlg.DoModal();
        }
        else {
            CControlJobDlg dlg;
            dlg.DoModal();
        }
    }
    else if (id == IDC_BUTTON_PORT_CONFIG) {
        CPortConfigurationDlg dlg;
        dlg.DoModal();
    }
    else if (id == IDC_BUTTON_CASSETTE) {
        CControlJobManagerDlg dlg;
        dlg.DoModal();
    }
    else if (id == IDC_BUTTON_ROBOT) {
        theApp.m_model.getMaster().clearError();
        SERVO::CEFEM* pEFEM = (SERVO::CEFEM*)theApp.m_model.getMaster().getEquipment(EQ_ID_EFEM);
SourceCode/Bond/Servo/TopToolbar.cpp
@@ -37,6 +37,7 @@
    DDX_Control(pDX, IDC_BUTTON_ALARM, m_btnAlarm);
    DDX_Control(pDX, IDC_BUTTON_SETTINGS, m_btnSettings);
    DDX_Control(pDX, IDC_BUTTON_PORT_CONFIG, m_btnPortConfig);
    DDX_Control(pDX, IDC_BUTTON_CASSETTE, m_btnCassette);
    DDX_Control(pDX, IDC_BUTTON_ROBOT, m_btnRobot);
    DDX_Control(pDX, IDC_BUTTON_OPERATOR, m_btnOperator);
}
@@ -66,6 +67,7 @@
    InitBtn(m_btnSettings, "Settings_High_32.ico", "Settings_Gray_32.ico");
    InitBtn(m_btnRobot, "Robot_High_32.ico", "Robot_Gray_32.ico");
    InitBtn(m_btnPortConfig, "PortConfig_High_32.ico", "PortConfig_Gray_32.ico");
    InitBtn(m_btnCassette, "Cassette_High_32.ico", "Cassette_Gray_32.ico");
    InitBtn(m_btnOperator, "Operator_High_32.ico", "Operator_Gray_32.ico");
    HMENU hMenu = LoadMenu(AfxGetInstanceHandle(), MAKEINTRESOURCEA(IDR_MENU_OPEATOR));
    m_btnOperator.SetMenu(hMenu);
@@ -168,6 +170,11 @@
    x += BTN_WIDTH;
    x += 2;
    pItem = GetDlgItem(IDC_BUTTON_CASSETTE);
    pItem->MoveWindow(x, y, BTN_WIDTH, nBthHeight);
    x += BTN_WIDTH;
    x += 2;
    pItem = GetDlgItem(IDC_BUTTON_ROBOT);
    pItem->MoveWindow(x, y, BTN_WIDTH, nBthHeight);
    x += BTN_WIDTH;
@@ -225,6 +232,7 @@
    case IDC_BUTTON_ALARM:
    case IDC_BUTTON_SETTINGS:
    case IDC_BUTTON_PORT_CONFIG:
    case IDC_BUTTON_CASSETTE:
    case IDC_BUTTON_ROBOT:
        GetParent()->SendMessage(ID_MSG_TOOLBAR_BTN_CLICKED, 0, LOWORD(wParam));
        break;
SourceCode/Bond/Servo/TopToolbar.h
@@ -38,6 +38,7 @@
    CBlButton m_btnAlarm;
    CBlButton m_btnSettings;
    CBlButton m_btnPortConfig;
    CBlButton m_btnCassette;
    CBlButton m_btnRobot;
    CBlButton m_btnOperator;
SourceCode/Bond/Servo/resource.h
Binary files differ
SourceCode/Bond/x64/Debug/Res/cassette_gray_32.ico
SourceCode/Bond/x64/Debug/Res/cassette_high_32.ico