LAPTOP-SNT8I5JK\Boounion
2025-06-06 bb13ecc602edb0247f65a1362135e4ef70a5a79f
Merge branch 'master' into clh

# Conflicts:
# SourceCode/Bond/Servo/resource.h
已修改9个文件
243 ■■■■■ 文件已修改
SourceCode/Bond/Servo/BlButton.cpp 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRobotCmdTestDlg.cpp 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRobotTask.cpp 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRobotTask.h 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRobotTaskDlg.cpp 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/CRobotTaskDlg.h 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/JobSlotGrid.cpp 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/JobSlotGrid.h 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/resource.h 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/Servo/BlButton.cpp
@@ -219,8 +219,7 @@
        hFont = (HFONT)pFont->GetSafeHandle();
    }
    ::SelectObject(hDC, hFont);
    HFONT hOldFont = (HFONT)::SelectObject(hDC, hFont);
    ::SetBkMode(hDC, TRANSPARENT);
    ::SetTextColor(hDC, m_crText[state]);
@@ -237,6 +236,7 @@
        }
        DrawTextA(hDC, szText, (int)strlen(szText), &rcText, DT_VCENTER | DT_CENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
    }
    ::SelectObject(hDC, hOldFont);
    // 是否有小圆点
@@ -283,8 +283,8 @@
        ::Polygon(hDC, pt, 3);
        ::SelectObject(hDC, hOldBrush);
        ::SelectObject(hDC, hOldPen);
        ::DeleteObject(hBrush);
        ::DeleteObject(hPen);
        ::DeleteObject(hbrDrop);     // 正确释放小三角使用的画刷
        ::DeleteObject(hPenDrop);    // 正确释放小三角的笔
    }
}
SourceCode/Bond/Servo/CRobotCmdTestDlg.cpp
SourceCode/Bond/Servo/CRobotTask.cpp
@@ -174,6 +174,18 @@
        return m_robotCmdParam.putSlotNo;
    }
    CString CRobotTask::getStateString()
    {
        switch (m_state) {
        case ROBOT_TASK_STATE::Ready:     return _T("Ready");
        case ROBOT_TASK_STATE::Running:   return _T("Running");
        case ROBOT_TASK_STATE::Error:     return _T("Error");
        case ROBOT_TASK_STATE::Abort:     return _T("Abort");
        case ROBOT_TASK_STATE::Completed: return _T("Completed");
        default:                          return _T("Unknown");
        }
    }
    void CRobotTask::fetchOut()
    {
        m_timeFetchOut = CToolUnits::getUnixTimestamp();;
SourceCode/Bond/Servo/CRobotTask.h
@@ -30,6 +30,7 @@
        int getSrcSlot();
        int getTarPosition();
        int getTarSlot();
        CString getStateString();
        // 从源地拔片
        void fetchOut();
SourceCode/Bond/Servo/CRobotTaskDlg.cpp
@@ -32,6 +32,7 @@
    ON_WM_DESTROY()
    ON_WM_SIZE()
    ON_WM_ACTIVATE()
    ON_BN_CLICKED(IDC_BUTTON_ABORT_TASK, &CRobotTaskDlg::OnBnClickedAbortTask)
END_MESSAGE_MAP()
@@ -47,15 +48,47 @@
    GetDlgItem(IDC_LABEL_NO_TASK)->ShowWindow(m_pRobotTask == nullptr ? SW_SHOW : SW_HIDE);
    GetDlgItem(IDC_LABEL_GET_PUT)->ShowWindow(m_pRobotTask != nullptr ? SW_SHOW : SW_HIDE);
    if (m_btnAbortTask.m_hWnd) {
        m_btnAbortTask.ShowWindow(m_pRobotTask ? SW_SHOW : SW_HIDE);
    }
    if (m_pRobotTask != nullptr) {
        SERVO::CEquipment* pEq1, * pEq2;
        pEq1 = theApp.m_model.getMaster().getEquipment(m_pRobotTask->getSrcPosition());
        pEq2 = theApp.m_model.getMaster().getEquipment(m_pRobotTask->getTarPosition());
        using namespace SERVO;
        CString strText;
        strText.Format(_T("%s --> %s"), pEq1->getName().c_str(), pEq2->getName().c_str());
        SetDlgItemText(IDC_LABEL_GET_PUT, strText);
        CEquipment* pSrcEq = theApp.m_model.getMaster().getEquipment(pRobotTask->getSrcPosition());
        CEquipment* pDstEq = theApp.m_model.getMaster().getEquipment(pRobotTask->getTarPosition());
        ROBOT_CMD_PARAM& param = pRobotTask->getRobotCmdParam();
        auto format_time = [](time_t t) -> CString {
            if (t == 0) {
                return _T("-");
            }
            CTime time(t);
            return time.Format(_T("%Y-%m-%d %H:%M:%S"));
        };
        CString strDetail;
        strDetail.Format(
            _T("任务 ID: %s\r\n源位置: %s (P%d)\r\n目标位置: %s (P%d)\r\n")
            _T("源槽位: Slot %d\r\n目标槽位: Slot %d\r\n手臂编号: Arm %d\r\n任务状态: %s\r\n")
            _T("创建时间: %s\r\n取片时间: %s\r\n放片时间: %s\r\n结束时间: %s"),
            pRobotTask->getId().c_str(),
            pSrcEq ? pSrcEq->getName().c_str() : _T("未知"),
            param.getPosition,
            pDstEq ? pDstEq->getName().c_str() : _T("未知"),
            param.putPosition,
            param.getSlotNo,
            param.putSlotNo,
            param.armNo,
            pRobotTask->getStateString(),
            format_time(pRobotTask->getCreateTime()),
            format_time(pRobotTask->getFetchoutTime()),
            format_time(pRobotTask->getStoredTime()),
            format_time(pRobotTask->getFinishTime())
        );
        SetDlgItemText(IDC_LABEL_GET_PUT, strDetail);
    }
}
@@ -64,6 +97,20 @@
    CDialogEx::OnInitDialog();
    // TODO:  在此添加额外的初始化
    // 创建“停止任务”按钮
    m_btnAbortTask.Create(_T("停止任务"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, CRect(0, 0, 100, 30), this, IDC_BUTTON_ABORT_TASK);
    // 动态按钮创建后设置字体
    if (m_fontButton.GetSafeHandle() == nullptr) {
        m_fontButton.CreatePointFont(110, _T("微软雅黑")); // 或 "Segoe UI"
    }
    m_btnAbortTask.SetFont(&m_fontButton);
    // 设置 LABEL 控件的字体
    if (m_fontDetail.GetSafeHandle() == nullptr) {
        m_fontDetail.CreatePointFont(100, _T("微软雅黑"));
    }
    GetDlgItem(IDC_LABEL_GET_PUT)->SetFont(&m_fontDetail);
    return TRUE;  // return TRUE unless you set the focus to a control
                  // 异常: OCX 属性页应返回 FALSE
@@ -86,6 +133,13 @@
    CDialogEx::OnDestroy();
    // TODO: 在此处添加消息处理程序代码
    if (m_fontButton.GetSafeHandle()) {
        m_fontButton.DeleteObject();
    }
    if (m_fontDetail.GetSafeHandle()) {
        m_fontDetail.DeleteObject();
    }
}
void CRobotTaskDlg::OnSize(UINT nType, int cx, int cy)
@@ -116,9 +170,32 @@
        (rcClient.Height() - rcItem.Height()) / 2, rcItem.Width(), rcItem.Height());
    pItem = GetDlgItem(IDC_LABEL_GET_PUT);
    pItem->GetClientRect(&rcItem);
    pItem->MoveWindow(12,
        12, rcItem.Width(), rcItem.Height());
    if (pItem && pItem->m_hWnd) {
        const int nLabelX = 12;
        const int nLabelY = 12;
        const int nLabelWidth = rcClient.Width() - 24;
        const int nLabelHeight = rcClient.Height() - 24;
        pItem->MoveWindow(nLabelX, nLabelY, nLabelWidth, nLabelHeight);
}
    // 设置“停止任务”按钮位置(右下角)
    if (m_btnAbortTask.m_hWnd != nullptr) {
        const int nBtnWidth = 100;
        const int nBtnHeight = 28;
        const int nMargin = 12;
        const int nPosX = rcClient.right - nBtnWidth - nMargin;
        const int nPosY = rcClient.bottom - nBtnHeight - nMargin;
        m_btnAbortTask.MoveWindow(nPosX, nPosY, nBtnWidth, nBtnHeight);
    }
}
void CRobotTaskDlg::OnBnClickedAbortTask()
{
    if (m_pRobotTask) {
        m_pRobotTask->abort();
        AfxMessageBox(_T("任务已停止。"));
    }
}
SourceCode/Bond/Servo/CRobotTaskDlg.h
@@ -21,6 +21,9 @@
private:
    SERVO::CRobotTask* m_pRobotTask;
    CButton m_btnAbortTask;
    CFont m_fontButton;
    CFont m_fontDetail;
// 对话框数据
@@ -38,4 +41,5 @@
    afx_msg void OnDestroy();
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized);
    afx_msg void OnBnClickedAbortTask();
};
SourceCode/Bond/Servo/JobSlotGrid.cpp
@@ -10,6 +10,10 @@
BEGIN_MESSAGE_MAP(CJobSlotGrid, CWnd)
    ON_WM_PAINT()
    ON_WM_ERASEBKGND()
    ON_WM_MOUSEMOVE()
    ON_WM_MOUSELEAVE()
    ON_WM_LBUTTONDOWN()
    ON_WM_LBUTTONUP()
END_MESSAGE_MAP()
CJobSlotGrid::CJobSlotGrid() {
@@ -48,6 +52,7 @@
    m_nRows = nRows;
    m_nCols = nCols;
    m_vSlotStatus.assign(nRows, std::vector<bool>(nCols, false));
    m_vSlotClickable.assign(nRows, std::vector<bool>(nCols, false));
    // 初始化文本数组
    m_vSlotText.assign(nRows, std::vector<CString>(nCols));
@@ -130,8 +135,88 @@
    Invalidate();
}
void CJobSlotGrid::SetSlotClickable(int nRow, int nCol, bool bClickable)
{
    if (nRow >= 0 && nRow < m_nRows && nCol >= 0 && nCol < m_nCols) {
        m_vSlotClickable[nRow][nCol] = bClickable;
    }
}
bool CJobSlotGrid::IsSlotClickable(int nRow, int nCol) const
{
    if (nRow >= 0 && nRow < m_nRows && nCol >= 0 && nCol < m_nCols) {
        return m_vSlotClickable[nRow][nCol];
    }
    return false;
}
void CJobSlotGrid::SetSlotClickCallback(SlotClickCallback fnCallback)
{
    m_fnSlotClickCallback = fnCallback;
}
BOOL CJobSlotGrid::OnEraseBkgnd(CDC* pDC) {
    return TRUE;
}
void CJobSlotGrid::OnMouseMove(UINT nFlags, CPoint point)
{
    TRACKMOUSEEVENT tme = { sizeof(TRACKMOUSEEVENT), TME_LEAVE, m_hWnd };
    ::TrackMouseEvent(&tme);
    CRect rect;
    GetClientRect(&rect);
    int nCellWidth = rect.Width() / m_nCols;
    int nCellHeight = rect.Height() / m_nRows;
    int nCol = point.x / nCellWidth;
    int nRow = point.y / nCellHeight;
    if (nRow != m_ptHover.y || nCol != m_ptHover.x) {
        m_ptHover = CPoint(nCol, nRow);
        Invalidate();
    }
    CWnd::OnMouseMove(nFlags, point);
}
void CJobSlotGrid::OnMouseLeave()
{
    m_ptHover = CPoint(-1, -1);
    Invalidate();
    CWnd::OnMouseLeave();
}
void CJobSlotGrid::OnLButtonDown(UINT nFlags, CPoint point)
{
    m_bLButtonDown = true;
    Invalidate();
    CWnd::OnLButtonDown(nFlags, point);
}
void CJobSlotGrid::OnLButtonUp(UINT nFlags, CPoint point)
{
    m_bLButtonDown = false;
    Invalidate();
    // 保持原有逻辑不变
    CRect rect;
    GetClientRect(&rect);
    int nCellWidth = rect.Width() / m_nCols;
    int nCellHeight = rect.Height() / m_nRows;
    int nCol = point.x / nCellWidth;
    int nRow = point.y / nCellHeight;
    if (IsSlotClickable(nRow, nCol)) {
        if (m_fnSlotClickCallback) {
            m_fnSlotClickCallback(nRow, nCol);
        }
    }
    CWnd::OnLButtonUp(nFlags, point);
}
void CJobSlotGrid::OnPaint() {
@@ -152,14 +237,30 @@
        for (int j = 0; j < m_nCols; ++j) {
            CRect cellRect(j * nCellWidth, i * nCellHeight, (j + 1) * nCellWidth, (i + 1) * nCellHeight);
            // 背景
            CBrush* pBrush = m_vSlotStatus[i][j] ? &m_brushHasJob : &m_brushNoJob;
            pDC->FillRect(&cellRect, pBrush);
            // 判断状态:悬停 / 按下
            bool bIsHover = (m_ptHover.x == j && m_ptHover.y == i);
            bool bIsClicking = bIsHover && m_bLButtonDown;
            // 选择颜色
            COLORREF fillColor;
            if (bIsClicking) {
                fillColor = RGB(0, 120, 215);   // 鼠标按下色
            }
            else if (bIsHover) {
                fillColor = RGB(200, 230, 255); // 悬停高亮
            }
            else {
                fillColor = m_vSlotStatus[i][j] ? m_colorHasJob : m_colorNoJob;
            }
            // 画背景
            CBrush brush(fillColor);
            pDC->FillRect(&cellRect, &brush);
            // 边框
            pDC->DrawEdge(&cellRect, EDGE_SUNKEN, BF_RECT);
            // 文字(居中)
            // 文本
            pDC->SetBkMode(TRANSPARENT);
            pDC->SetTextColor(RGB(0, 0, 0));
            pDC->DrawText(m_vSlotText[i][j], &cellRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
SourceCode/Bond/Servo/JobSlotGrid.h
@@ -1,6 +1,9 @@
#pragma once
#include <afxwin.h>
#include <vector>
#include <functional>
using SlotClickCallback = std::function<void(int nRow, int nCol)>;
class CJobSlotGrid : public CWnd
{
@@ -16,15 +19,24 @@
    void SetSlotText(int nRow, int nCol, const CString& strText);
    void SetTextFont(const CString& strFontName, int nPointSize);
    void ClearAll();
    void SetSlotClickable(int nRow, int nCol, bool bClickable);
    bool IsSlotClickable(int nRow, int nCol) const;
    void SetSlotClickCallback(SlotClickCallback fnCallback);
protected:
    afx_msg void OnPaint();
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    afx_msg void OnMouseLeave();
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
    DECLARE_MESSAGE_MAP()
private:
    int m_nRows;
    int m_nCols;
    bool m_bLButtonDown = false;             // 鼠标是否按下
    CPoint m_ptHover{ -1, -1 };   // 当前悬停的 cell 索引(row, col)
    CFont m_fontText;
    COLORREF m_colorHasJob;
    COLORREF m_colorNoJob;
@@ -32,6 +44,8 @@
    CBrush m_brushNoJob;
    std::vector<std::vector<bool>> m_vSlotStatus;
    std::vector<std::vector<CString>> m_vSlotText;
    std::vector<std::vector<bool>> m_vSlotClickable;
    SlotClickCallback m_fnSlotClickCallback;
    void DrawGrid(CDC* pDC);
};
SourceCode/Bond/Servo/resource.h
Binary files differ