From fd381da2f50420d8c861ad9cf213d7b1586f0188 Mon Sep 17 00:00:00 2001
From: LAPTOP-SNT8I5JK\Boounion <Chenluhua@qq.com>
Date: 星期四, 18 九月 2025 18:06:18 +0800
Subject: [PATCH] 1.Port物料选择控件完善; 2.继续处理CControlJobManagerDlg的功能;

---
 SourceCode/Bond/Servo/CCjPage2.cpp              |  238 ++++++++++++++--
 SourceCode/Bond/Servo/CCarrierSlotGrid.cpp      |  293 ++++++++++++++++----
 SourceCode/Bond/Servo/CCarrierSlotGrid.h        |   39 ++
 SourceCode/Bond/Servo/CCjPageBase.h             |    2 
 SourceCode/Bond/Servo/CCjPage2.h                |    9 
 SourceCode/Bond/Servo/Servo.rc                  |    0 
 SourceCode/Bond/Servo/CCjPage3.h                |    2 
 SourceCode/Bond/Servo/CControlJobManagerDlg.h   |   10 
 SourceCode/Bond/Servo/CControlJobManagerDlg.cpp |  209 +++++++++++++-
 SourceCode/Bond/Servo/CLoadPort.cpp             |    3 
 SourceCode/Bond/Servo/ServoDlg.cpp              |   18 
 SourceCode/Bond/Servo/CCjPage3.cpp              |    4 
 12 files changed, 695 insertions(+), 132 deletions(-)

diff --git a/SourceCode/Bond/Servo/CCarrierSlotGrid.cpp b/SourceCode/Bond/Servo/CCarrierSlotGrid.cpp
index f26ed7f..c44a1b5 100644
--- a/SourceCode/Bond/Servo/CCarrierSlotGrid.cpp
+++ b/SourceCode/Bond/Servo/CCarrierSlotGrid.cpp
@@ -1,4 +1,4 @@
-#include "stdafx.h"
+锘�#include "stdafx.h"
 #include "CCarrierSlotGrid.h"
 #include <gdiplus.h>
 #pragma comment(lib, "gdiplus.lib")
@@ -26,7 +26,7 @@
     ON_WM_CREATE()
     ON_WM_SIZE()
     ON_WM_HSCROLL()
-    ON_WM_VSCROLL()          // ★ 新增
+    ON_WM_VSCROLL()          // 鈽� 鏂板
     ON_WM_LBUTTONDOWN()
     ON_WM_LBUTTONUP()
     ON_WM_MOUSEWHEEL()
@@ -50,8 +50,7 @@
     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);
 }
 
@@ -61,7 +60,7 @@
     if (GetParent() && GetParent()->GetFont()) SetFont(GetParent()->GetFont());
     EnsureFonts();
     EnsureGdiplus();
-    ModifyStyle(0, WS_HSCROLL | WS_VSCROLL, 0);
+
     return 0;
 }
 
@@ -142,7 +141,7 @@
     Invalidate(FALSE);
 }
 
-void CCarrierSlotGrid::SetSlotChecked(int portIndex, int slotIndex, BOOL checked)
+void CCarrierSlotGrid::SetSlotChecked(int portIndex, int slotIndex, BOOL checked, BOOL bNotify/* = FALSE*/)
 {
     if (!SAFE_PORT(portIndex) || !SAFE_SLOT(slotIndex)) return;
     auto& pc = m_ports[portIndex];
@@ -150,7 +149,7 @@
     auto& cell = pc.slots[slotIndex];
     if (!cell.hasGlass) return;
     cell.checked = !!checked;
-    NotifySelectionChanged(portIndex, slotIndex, cell.checked);
+    if(bNotify) NotifySelectionChanged(portIndex, slotIndex, cell.checked);
     Invalidate(FALSE);
 }
 
@@ -183,7 +182,7 @@
 
 CString CCarrierSlotGrid::GetDisplayId(int portIndex, int slotIndex) const
 {
-    CString s(_T("—"));
+    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;
@@ -218,24 +217,172 @@
     return CSize(w, h);
 }
 
-CSize CCarrierSlotGrid::CalcBestWindowSize(BOOL includeNonClient, int nSlotsOverride) const
+CSize CCarrierSlotGrid::CalcBestWindowSize(BOOL includeNonClient,
+    int nSlotsOverride,
+    int extraPadX,
+    int extraPadY) const
 {
-    CSize cli = CalcBestClientSize(nSlotsOverride);
-    if (!includeNonClient) return cli;
+    // 1) 鍩虹瀹㈡埛鍖哄昂瀵革紙鍚垜浠湪瀹㈡埛鍖虹敾鐨� 1px 杈规锛氬乏鍙�+2/涓婁笅+2锛�
+    const CSize content = CalcBestClientSize(nSlotsOverride);
 
-    RECT rc = { 0, 0, cli.cx, cli.cy };
+    // 2) 鍙� DPI銆佹粴鍔ㄦ潯灏哄锛堝敖閲忕敤 ForDpi锛屽洖閫�鍒版櫘閫氾級
+    UINT dpi = 96;
+#if (_WIN32_WINNT >= 0x0603)
+    if (m_hWnd) {
+        HMODULE hUser32 = ::GetModuleHandleW(L"user32.dll");
+        if (hUser32) {
+            typedef UINT(WINAPI* PFN_GETDPIFORWINDOW)(HWND);
+            auto pGetDpiForWindow = (PFN_GETDPIFORWINDOW)::GetProcAddress(hUser32, "GetDpiForWindow");
+            if (pGetDpiForWindow) dpi = pGetDpiForWindow(m_hWnd);
+        }
+    }
+#endif
+    int cxVScroll = ::GetSystemMetrics(SM_CXVSCROLL);
+    int cyHScroll = ::GetSystemMetrics(SM_CYHSCROLL);
+#if (_WIN32_WINNT >= 0x0A00) // Win10: 鍙敤 GetSystemMetricsForDpi
+    HMODULE hUser32_2 = ::GetModuleHandleW(L"user32.dll");
+    if (hUser32_2) {
+        typedef int (WINAPI* PFN_GSMFD)(int, UINT);
+        auto pGsmForDpi = (PFN_GSMFD)::GetProcAddress(hUser32_2, "GetSystemMetricsForDpi");
+        if (pGsmForDpi) {
+            cxVScroll = pGsmForDpi(SM_CXVSCROLL, dpi);
+            cyHScroll = pGsmForDpi(SM_CYHSCROLL, dpi);
+        }
+    }
+#endif
 
-    // 目标是“刚好不出现滚动条”的窗口外框大小:
-    // 用当前样式去掉 WS_HSCROLL/WS_VSCROLL 再做 AdjustWindowRectEx
-    DWORD style = GetStyle();
+    // 3) DPI 鑷�傚簲瀹夊叏浣欓噺锛堥伩鍏嶅彇鏁磋宸�/涓婚宸紓锛�
+    const int autoPad = max(1, MulDiv(2, (int)dpi, 96)); // 绾︾瓑浜� 2px@96DPI
+    const int padX = (extraPadX >= 0) ? extraPadX : autoPad;
+    const int padY = (extraPadY >= 0) ? extraPadY : autoPad;
+
+    // 4) 杩唬锛氳�冭檻婊氬姩鏉$浉浜掑奖鍝嶏紝鐩村埌绋冲畾涓嶉渶瑕佹粴鍔ㄦ潯
+    int needCx = content.cx + padX;
+    int needCy = content.cy + padY;
+
+    while (true) {
+        bool needV = (GetTotalContentWidth() > needCx);                      // 瀹戒笉澶熲啋浼氬嚭鐜版í鍚戞粴鍔ㄦ潯锛燂紙娉ㄦ剰锛氭í鏉″崰楂樺害锛�
+        bool needH = (m_headerCY + (nSlotsOverride > 0 ? nSlotsOverride : m_nSlots) * m_rowHeight + 2 /*瀹㈡埛鍖鸿竟妗�*/ > needCy); // 楂樹笉澶熲啋浼氬嚭鐜扮旱鏉★紵锛堢旱鏉″崰瀹藉害锛�
+
+        // 娉ㄦ剰锛氬嚭鐜扳�滅旱鍚戞潯鈥濅細鍑忓皯鍙敤瀹藉害锛涘嚭鐜扳�滄í鍚戞潯鈥濅細鍑忓皯鍙敤楂樺害
+        // 鎴戜滑鐩爣鏄鈥滃嵆浣挎墸鎺夎繖浜涘崰浣嶁�濆悗涔熶粛鐒� >= 鍐呭灏哄
+        int adjCx = content.cx + padX + (needH ? cxVScroll : 0);
+        int adjCy = content.cy + padY + (needV ? cyHScroll : 0);
+
+        if (adjCx <= needCx && adjCy <= needCy) break; // 绋冲畾锛氬綋鍓� needCx/needCy 瓒冲
+        needCx = max(needCx, adjCx);
+        needCy = max(needCy, adjCy);
+    }
+
+    if (!includeNonClient) return CSize(needCx, needCy);
+
+    // 5) 鎶娾�滅悊鎯冲鎴峰尯灏哄鈥濇崲绠楁垚绐楀彛澶栨灏哄锛堝幓鎺� WS_H/VSCROLL 鍋氭崲绠楋級
+    RECT rc = { 0, 0, needCx, needCy };
+    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);
 }
 
-// ---------- 几何 ----------
+void CCarrierSlotGrid::DisableSystemScrollbars()
+{
+    // 鍘绘帀鏍峰紡
+    ModifyStyle(WS_HSCROLL | WS_VSCROLL, 0, 0);
+    // 闅愯棌锛堝吋瀹规�э級
+    ShowScrollBar(SB_HORZ, FALSE);
+    ShowScrollBar(SB_VERT, FALSE);
+    // 娓呯┖婊氬姩淇℃伅
+    SCROLLINFO si{ sizeof(SCROLLINFO) }; si.fMask = SIF_ALL; si.nMin = 0; si.nMax = 0; si.nPage = 0; si.nPos = 0;
+    SetScrollInfo(SB_HORZ, &si, TRUE);
+    SetScrollInfo(SB_VERT, &si, TRUE);
+    // 璁╅潪瀹㈡埛鍖洪噸绠�
+    SetWindowPos(nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
+    Invalidate(FALSE);
+}
+
+// 娉ㄦ剰锛氳繖閲岀敤鈥滄棤婊氬姩鏉℃牱寮忊�濇潵鎹㈢畻绐楀彛澶栨灏哄锛岀‘淇濆鎴峰尯=鍐呭灏哄 + 鎴戜滑瀹㈡埛鍖鸿竟妗�
+void CCarrierSlotGrid::ResizeWindowToFitAll(BOOL includeNonClient, int nSlotsOverride)
+{
+    // 璁$畻鍐呭鎵�闇�瀹㈡埛鍖猴紙CalcBestClientSize 鍐呭凡鍖呭惈鎴戜滑瀹㈡埛鍖�1px杈规鐨� +2锛�
+    CSize need = CalcBestClientSize(nSlotsOverride);
+
+    if (!includeNonClient) {
+        SetWindowPos(nullptr, 0, 0, need.cx, need.cy,
+            SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+        return;
+    }
+
+    RECT rc = { 0, 0, need.cx, need.cy };
+    DWORD style = GetStyle() & ~(WS_HSCROLL | WS_VSCROLL); // 鈫� 鐢ㄢ�滄棤婊氬姩鏉♀�濈殑鏍峰紡鏉ユ崲绠�
+    DWORD exStyle = GetExStyle();
+    ::AdjustWindowRectEx(&rc, style, FALSE, exStyle);
+
+    int w = rc.right - rc.left;
+    int h = rc.bottom - rc.top;
+
+    SetWindowPos(nullptr, 0, 0, w, h,
+        SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+}
+
+void CCarrierSlotGrid::SetNoScrollbarsMode(BOOL enable)
+{
+    m_noScrollbars = !!enable;
+
+    if (m_noScrollbars) {
+        // 1) 鍋忕Щ娓呴浂
+        m_scrollX = 0;
+        m_scrollY = 0;
+
+        // 2) 鍘绘帀鏍峰紡骞堕殣钘忔潯
+        ModifyStyle(WS_HSCROLL | WS_VSCROLL, 0, 0);
+        ShowScrollBar(SB_BOTH, FALSE);
+
+        // 3) 娓呯┖婊氬姩淇℃伅锛堝嵆渚胯祫婧愰噷鍘熸湰甯︿簡鏍峰紡锛屼篃涓嶅啀褰卞搷锛�
+        SCROLLINFO si{ sizeof(SCROLLINFO) }; si.fMask = SIF_ALL;
+        SetScrollInfo(SB_HORZ, &si, TRUE);
+        SetScrollInfo(SB_VERT, &si, TRUE);
+
+        // 4) 閫氱煡绯荤粺闈炲鎴峰尯鍒锋柊锛岀‘淇濇潯琚交搴曠Щ闄�
+        SetWindowPos(nullptr, 0, 0, 0, 0,
+            SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
+
+        Invalidate(FALSE);
+    }
+    else {
+        // 閫�鍑烘棤婊氬姩鏉℃ā寮忥細浠呮仮澶嶆牱寮忥紝瀹為檯鑼冨洿浼氬湪 UpdateScrollRange 涓噸鏂拌缃�
+        ModifyStyle(0, WS_HSCROLL | WS_VSCROLL, 0);
+        SetWindowPos(nullptr, 0, 0, 0, 0,
+            SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
+        UpdateScrollRange();
+        Invalidate(FALSE);
+    }
+}
+
+void CCarrierSlotGrid::FitWindowToContentNoScroll(BOOL includeNonClient, int nSlotsOverride)
+{
+    // 纭繚宸插浜庘�滄棤婊氬姩鏉℃ā寮忊�濓紝闃叉绯荤粺鍦� AdjustWindowRectEx 鏃堕鐣欐粴鍔ㄦ潯闈炲鎴峰尯
+    SetNoScrollbarsMode(TRUE);
+
+    // 浣犺嚜宸辩殑 CalcBestClientSize 宸插寘鍚鎴峰尯 1px 杈规(+2)鐨勪慨姝�
+    CSize needCli = CalcBestClientSize(nSlotsOverride);
+
+    if (!includeNonClient) {
+        SetWindowPos(nullptr, 0, 0, needCli.cx, needCli.cy,
+            SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+        return;
+    }
+
+    RECT rc{ 0, 0, needCli.cx, needCli.cy };
+    // 娉ㄦ剰锛氭鏃剁獥鍙f牱寮忓凡缁忔病鏈� WS_H/VSCROLL 浜嗏�斺�旂敤鐪熷疄鏍峰紡鎹㈢畻鍗冲彲
+    ::AdjustWindowRectEx(&rc, GetStyle(), FALSE, GetExStyle());
+    const int w = rc.right - rc.left;
+    const int h = rc.bottom - rc.top;
+
+    SetWindowPos(nullptr, 0, 0, w, h,
+        SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED);
+}
+
+// ---------- 鍑犱綍 ----------
 CRect CCarrierSlotGrid::GetClientRectNoSB() const
 {
     CRect rc; GetClientRect(&rc); return rc;
@@ -313,9 +460,16 @@
 
 void CCarrierSlotGrid::UpdateScrollRange()
 {
+    if (m_noScrollbars) {
+        // 纭繚鍋忕Щ涓�鐩翠负 0锛屼笉璁句换浣曟粴鍔ㄤ俊鎭�
+        m_scrollX = 0;
+        m_scrollY = 0;
+        return;
+    }
+
     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);
@@ -325,7 +479,7 @@
     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);
@@ -336,10 +490,10 @@
     SetScrollInfo(SB_HORZ, &siX, TRUE);
 }
 
-// ---------- 表头分隔线命中 ----------
+// ---------- 琛ㄥご鍒嗛殧绾垮懡涓� ----------
 int CCarrierSlotGrid::HitHeaderEdge(CPoint pt) const
 {
-    if (!m_bAllowResize) return -1;  // ← 新增
+    if (!m_bAllowResize) return -1;  // 鈫� 鏂板
     if (!GetHeaderRect().PtInRect(pt)) return -1;
     const int tol = 4;
     int x = GetHeaderRect().left - m_scrollX + m_slotColCX;
@@ -353,7 +507,7 @@
     return -1;
 }
 
-// ---------- 绘制 ----------
+// ---------- 缁樺埗 ----------
 BOOL CCarrierSlotGrid::OnEraseBkgnd(CDC* /*pDC*/) { return TRUE; }
 
 void CCarrierSlotGrid::DrawFlatCheckbox(CDC* pDC, const CRect& r, bool checked, bool disabled)
@@ -395,8 +549,8 @@
     {
         CRect rItem = GetHeaderItemRect(i);
 
-        // 修改为:
-        if (i < GetPortCount()) { // ★ 最后一列不画分隔线
+        // 淇敼涓猴細
+        if (i < GetPortCount()) { // 鈽� 鏈�鍚庝竴鍒椾笉鐢诲垎闅旂嚎
             CPen pen(PS_SOLID, 1, ::GetSysColor(COLOR_3DSHADOW));
             pOldPen = pDC->SelectObject(&pen);
             pDC->MoveTo(rItem.right - 1, rItem.top);
@@ -425,11 +579,11 @@
                 ? 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);
@@ -438,14 +592,14 @@
             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);
@@ -527,7 +681,7 @@
                     pDC->DrawText(tx, rT, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
                 }
 
-                // 状态点(GDI+ 抗锯齿)
+                // 鐘舵�佺偣锛圙DI+ 鎶楅敮榻匡級
                 {
                     Graphics g(pDC->GetSafeHdc());
                     g.SetSmoothingMode(SmoothingModeAntiAlias);
@@ -543,7 +697,7 @@
 
 
 
-// ===== 在每个已分配(allocated)的列中央绘制半透明 LOCK 水印(用 HDC+LOGFONT 构造字体)=====
+// ===== 鍦ㄦ瘡涓凡鍒嗛厤(allocated)鐨勫垪涓ぎ缁樺埗鍗婇�忔槑 LOCK 姘村嵃锛堢敤 HDC+LOGFONT 鏋勯�犲瓧浣擄級=====
     {
         Gdiplus::Graphics g(pDC->GetSafeHdc());
         g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
@@ -551,7 +705,7 @@
         CRect cli = GetClientRectNoSB();
         CRect rh = GetHeaderRect();
 
-        // 取当前 UI 字体(优先粗体)
+        // 鍙栧綋鍓� UI 瀛椾綋锛堜紭鍏堢矖浣擄級
         LOGFONT lf{};
         if ((HFONT)m_fntBold)      m_fntBold.GetLogFont(&lf);
         else if ((HFONT)m_fntText) m_fntText.GetLogFont(&lf);
@@ -560,21 +714,21 @@
         {
             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 + HDC 鏋勯�� GDI+ 瀛椾綋
             LOGFONTW lfw{};
 #ifdef UNICODE
             lfw = *reinterpret_cast<LOGFONTW*>(&lf);
@@ -594,14 +748,14 @@
             lfw.lfPitchAndFamily = lf.lfPitchAndFamily;
             MultiByteToWideChar(CP_ACP, 0, lf.lfFaceName, -1, lfw.lfFaceName, LF_FACESIZE);
 #endif
-            lfw.lfHeight = -emPx;           // 负值=按像素高度
+            lfw.lfHeight = -emPx;           // 璐熷��=鎸夊儚绱犻珮搴�
             lfw.lfWeight = FW_BOLD;
 
-            Gdiplus::Font gdifont(pDC->GetSafeHdc(), &lfw);    // ★ 加上 Gdiplus::
+            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::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());
@@ -616,14 +770,14 @@
         }
     }
 
-    // === 客户区内 1px 灰色边框(不包滚动条,但不会缺角/抢绘制)===
+    // === 瀹㈡埛鍖哄唴 1px 鐏拌壊杈规锛堜笉鍖呮粴鍔ㄦ潯锛屼絾涓嶄細缂鸿/鎶㈢粯鍒讹級===
     {
         CRect cli; GetClientRect(&cli);
 
-        // 用 FrameRect 更稳(避免右下角丢线)
+        // 鐢� FrameRect 鏇寸ǔ锛堥伩鍏嶅彸涓嬭涓㈢嚎锛�
         CBrush br; br.CreateSolidBrush(::GetSysColor(COLOR_3DSHADOW));
         CRect r = cli;
-        // 注意:客户区坐标是 [0..width, 0..height];FrameRect 会在内侧画 1px
+        // 娉ㄦ剰锛氬鎴峰尯鍧愭爣鏄� [0..width, 0..height]锛汧rameRect 浼氬湪鍐呬晶鐢� 1px
         pDC->FrameRect(&r, &br);
         br.DeleteObject();
     }
@@ -654,6 +808,8 @@
 
 void CCarrierSlotGrid::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pBar)
 {
+    if (m_noScrollbars) return; // 鈫� 鏂板
+
     UNREFERENCED_PARAMETER(pBar);
 
     SCROLLINFO si = { sizeof(SCROLLINFO) };
@@ -671,7 +827,7 @@
     case SB_PAGERIGHT: pos += (int)si.nPage; break;
     case SB_THUMBTRACK:
     case SB_THUMBPOSITION:
-        pos = (int)si.nTrackPos;   // ★ 32 位拖动位置
+        pos = (int)si.nTrackPos;   // 鈽� 32 浣嶆嫋鍔ㄤ綅缃�
         break;
     default:
         return;
@@ -687,6 +843,8 @@
 
 void CCarrierSlotGrid::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pBar)
 {
+    if (m_noScrollbars) return; // 鈫� 鏂板
+
     UNREFERENCED_PARAMETER(pBar);
 
     SCROLLINFO si = { sizeof(SCROLLINFO) };
@@ -704,7 +862,7 @@
     case SB_PAGEDOWN:  pos += (int)si.nPage; break;
     case SB_THUMBTRACK:
     case SB_THUMBPOSITION:
-        pos = (int)si.nTrackPos;   // ★ 32 位拖动位置
+        pos = (int)si.nTrackPos;   // 鈽� 32 浣嶆嫋鍔ㄤ綅缃�
         break;
     default:
         return;
@@ -720,6 +878,8 @@
 
 BOOL CCarrierSlotGrid::OnMouseWheel(UINT, short zDelta, CPoint)
 {
+    if (m_noScrollbars) return FALSE; // 鈫� 鏂板锛氬交搴曚笉婊�
+
     int delta = (zDelta > 0 ? -1 : +1) * (m_rowHeight * 3);
     m_scrollY = max(0, m_scrollY + delta);
     UpdateScrollRange();
@@ -741,8 +901,8 @@
 
 void CCarrierSlotGrid::OnLButtonDown(UINT nFlags, CPoint pt)
 {
-    // 是否拖动列宽
-    int edge = m_bAllowResize ? HitHeaderEdge(pt) : -1;  // ← 修改
+    // 鏄惁鎷栧姩鍒楀
+    int edge = m_bAllowResize ? HitHeaderEdge(pt) : -1;  // 鈫� 淇敼
     if (edge >= 0)
     {
         m_bResizing = true;
@@ -754,7 +914,7 @@
         return;
     }
 
-    // Header 点击(仅复选框区域)
+    // Header 鐐瑰嚮锛堜粎澶嶉�夋鍖哄煙锛�
     if (GetHeaderRect().PtInRect(pt))
     {
         for (int i = 1; i <= GetPortCount(); ++i)
@@ -780,7 +940,7 @@
         return;
     }
 
-    // Cell 点击
+    // Cell 鐐瑰嚮
     CRect cli = GetClientRectNoSB();
     if (pt.y < cli.top + m_headerCY) return;
     int yIn = pt.y - (cli.top + m_headerCY) + m_scrollY;
@@ -856,7 +1016,7 @@
         }
         else
         {
-            int idx = m_resizeEdge - 1; // 调整 Port idx 的宽度
+            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); }
         }
@@ -883,20 +1043,41 @@
     return CWnd::OnSetCursor(pWnd, nHitTest, message);
 }
 
-// ---------- 通知 ----------
-void CCarrierSlotGrid::NotifySelectionChanged(int /*port*/, int /*slot*/, BOOL /*checked*/)
+void CCarrierSlotGrid::NotifySelectionChanged(int port, int slot, BOOL checked)
 {
-    if (GetParent())
-    {
+    // 鍏煎鏃х殑 WM_COMMAND锛堝彲鐣欙紝涔熷彲娉ㄩ噴鎺夛級
+    if (GetParent()) {
         const int code = 0x2001;
         GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), code), (LPARAM)m_hWnd);
     }
+    // 鏂扮殑 WM_NOTIFY锛屽甫涓婄储寮曚笌鐘舵��
+    if (GetParent()) {
+        CSG_SEL_CHANGE nm{};
+        nm.hdr.hwndFrom = m_hWnd;
+        nm.hdr.idFrom = (UINT)GetDlgCtrlID();
+        nm.hdr.code = CSGN_SEL_CHANGED;
+        nm.port = port;
+        nm.slot = slot;
+        nm.checked = checked;
+        GetParent()->SendMessage(WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
+    }
 }
-void CCarrierSlotGrid::NotifyMaterialChanged(int /*port*/, int /*slot*/, int /*material*/)
+
+void CCarrierSlotGrid::NotifyMaterialChanged(int port, int slot, int material)
 {
-    if (GetParent())
-    {
+    if (GetParent()) {
         const int code = 0x2002;
         GetParent()->SendMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), code), (LPARAM)m_hWnd);
     }
+    if (GetParent()) {
+        CSG_MAT_CHANGE nm{};
+        nm.hdr.hwndFrom = m_hWnd;
+        nm.hdr.idFrom = (UINT)GetDlgCtrlID();
+        nm.hdr.code = CSGN_MAT_CHANGED;
+        nm.port = port;
+        nm.slot = slot;
+        nm.material = material;
+        GetParent()->SendMessage(WM_NOTIFY, nm.hdr.idFrom, (LPARAM)&nm);
+    }
 }
+
diff --git a/SourceCode/Bond/Servo/CCarrierSlotGrid.h b/SourceCode/Bond/Servo/CCarrierSlotGrid.h
index 73b1765..b3e2c2e 100644
--- a/SourceCode/Bond/Servo/CCarrierSlotGrid.h
+++ b/SourceCode/Bond/Servo/CCarrierSlotGrid.h
@@ -7,6 +7,25 @@
 #include <afxwin.h>
 #endif
 
+
+// 放到 CCarrierSlotGrid 类定义前或内部 public: 区都可
+enum { CSGN_SEL_CHANGED = 1, CSGN_MAT_CHANGED = 2 };
+
+struct CSG_SEL_CHANGE {
+    NMHDR hdr;   // hdr.code = CSGN_SEL_CHANGED
+    int   port;  // 0..GetPortCount()-1
+    int   slot;  // 0..GetSlotCount()-1
+    BOOL  checked;
+};
+
+struct CSG_MAT_CHANGE {
+    NMHDR hdr;   // hdr.code = CSGN_MAT_CHANGED
+    int   port;
+    int   slot;
+    int   material; // 1=G1, 2=G2
+};
+
+
 class CCarrierSlotGrid : public CWnd
 {
 public:
@@ -57,7 +76,7 @@
     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);
+    void SetSlotChecked(int portIndex, int slotIndex, BOOL checked, BOOL bNotify = FALSE);
     BOOL GetSlotChecked(int portIndex, int slotIndex) const;
 
     int  GetSlotMaterialType(int portIndex, int slotIndex) const;
@@ -73,9 +92,24 @@
 // - CalcBestClientSize:内容区域(不含滚动条/非客户区)刚好容纳表头+所有行、全部列
 // - CalcBestWindowSize:在当前窗口样式下,将“内容大小”转换为窗口外框大小(会考虑 WS_BORDER/CLIENTEDGE 等)
 //   默认按“隐藏滚动条”的目标来算(即不把 WS_HSCROLL/WS_VSCROLL 计入调整)
+// 计算最佳大小(支持可选安全边距,默认按 DPI 约等于 2px)
     CSize CalcBestClientSize(int nSlotsOverride = -1) const;
-    CSize CalcBestWindowSize(BOOL includeNonClient = TRUE, int nSlotsOverride = -1) const;
+    CSize CalcBestWindowSize(BOOL includeNonClient = TRUE,
+        int nSlotsOverride = -1,
+        int extraPadX = -1,  // -1 表示按 DPI 自动
+        int extraPadY = -1) const;
 
+    // 永久禁用系统滚动条(去掉样式并刷新非客户区)
+    void DisableSystemScrollbars();
+
+    // 把窗口尺寸调到正好容纳所有内容(不出现滚动条)
+    void ResizeWindowToFitAll(BOOL includeNonClient = TRUE, int nSlotsOverride = -1);
+
+    // 进入/退出无滚动条模式(去样式、清滚动、忽略滚动消息)
+    void SetNoScrollbarsMode(BOOL enable);
+
+    // 在“无滚动条模式”下,把窗口尺寸调到刚好容纳所有内容(不出现滚动条)
+    void FitWindowToContentNoScroll(BOOL includeNonClient = TRUE, int nSlotsOverride = -1);
 
 protected:
     // 数据
@@ -115,6 +149,7 @@
     std::vector<int> m_portColCXsStart;
     int  m_hitEdgeHover = -1;
     bool m_bAllowResize = true; // ← 新增:是否允许拖动列宽
+    bool m_noScrollbars = false;   // ← 新增:无滚动条模式
 
     // 工具
     void EnsureFonts();
diff --git a/SourceCode/Bond/Servo/CCjPage2.cpp b/SourceCode/Bond/Servo/CCjPage2.cpp
index e827a19..5e79f6a 100644
--- a/SourceCode/Bond/Servo/CCjPage2.cpp
+++ b/SourceCode/Bond/Servo/CCjPage2.cpp
@@ -15,7 +15,7 @@
 CCjPage2::CCjPage2(CWnd* pParent /*=nullptr*/)
 	: CCjPageBase(IDD_CJ_PAGE2, pParent)
 {
-
+    m_nSelRadioId = 0;
 }
 
 CCjPage2::~CCjPage2()
@@ -36,6 +36,8 @@
     ON_BN_CLICKED(IDC_RADIO2, &CCjPage2::OnBnClickedRadio2)
     ON_BN_CLICKED(IDC_RADIO3, &CCjPage2::OnBnClickedRadio3)
     ON_BN_CLICKED(IDC_RADIO4, &CCjPage2::OnBnClickedRadio4)
+    ON_NOTIFY(CSGN_SEL_CHANGED, IDC_GRID1, &CCjPage2::OnGridSelChanged)
+    ON_NOTIFY(CSGN_MAT_CHANGED, IDC_GRID1, &CCjPage2::OnGridMatChanged)
 END_MESSAGE_MAP()
 
 
@@ -60,15 +62,29 @@
     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.SetRowHeight(32);
+    m_grid.SetHeaderHeight(36);
     m_grid.EnableColumnResize(FALSE); // 绂佹鎷栧姩鍒楀
+    m_grid.SetShowMaterialToggle(TRUE);
+    m_grid.DisableSystemScrollbars();
+    m_grid.ResizeWindowToFitAll(TRUE); // TRUE=鍖呭惈闈炲鎴峰尯锛堣竟妗嗐�佹爣棰樻爮锛�
+    m_grid.SetNoScrollbarsMode(TRUE);           // 褰诲簳绂佺敤婊氬姩鏉�
+    m_grid.FitWindowToContentNoScroll(TRUE);    // 绐楀彛灏哄鍒氬ソ瀹圭撼鍏ㄩ儴鍐呭锛堜笉鍑虹幇婊氬姩鏉★級
 
     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(""));
 
+
+    // 娴嬭瘯鏁版嵁
+    char szBuffer[256];
+    for (int port = 0; port < 4; port++) {
+        for (int slot = 0; slot < 8; slot++) {
+            sprintf_s(szBuffer, 256, "Gls%04d%04d", port + 1, slot + 1);
+            m_grid.SetSlotGlass(port, slot, TRUE, szBuffer, CCarrierSlotGrid::MAT_G1);
+        }
+    }
 
     UpdatePjData();
 
@@ -98,16 +114,16 @@
 
     // 璁╂帶浠剁獥鍙e昂瀵歌嚜鍔ㄥ尮閰嶅綋鍓嶅垪瀹�/琛屾暟锛堜笉鍑虹幇婊氬姩鏉★級
     if (::IsWindow(m_grid.m_hWnd)) {
-        CSize best = m_grid.CalcBestWindowSize(TRUE); // 璁$畻鍒板惈闈炲鎴峰尯鐨勬渶缁堢獥鍙eぇ灏�
+        CSize best = m_grid.CalcBestWindowSize(TRUE, -1, 2, 2);
         pItem->MoveWindow(rcItem.left, rcItem.top, best.cx, best.cy);
         pItem->Invalidate();
     }
 }
 
-void CCjPage2::OnApply() 
+int CCjPage2::OnApply() 
 {
     //SERVO::CProcessJob*
-    if (m_pContext == nullptr) return;
+    if (m_pContext == nullptr) return -1;
     PJWarp* pPjWarp = (PJWarp*)m_pContext;
     SERVO::CProcessJob* pProcessJob = (SERVO::CProcessJob*)pPjWarp->pj;
 
@@ -116,7 +132,7 @@
     char szBuffer[256];
     GetDlgItemText(IDC_EDIT_PJ_ID, szBuffer, 256);
     for (auto item : m_pjWarps) {
-        if (item.pj != m_pContext) {
+        if (item.pj != pProcessJob) {
             SERVO::CProcessJob* temp = (SERVO::CProcessJob*)item.pj;
             if (temp->id().compare(std::string(szBuffer)) == 0) {
                 bOkName = FALSE;
@@ -126,7 +142,7 @@
     }
     if (!bOkName) {
         AfxMessageBox("涓嶈兘浣跨敤鍜屽叾瀹働rocess Job鐩稿悓鐨処D");
-        return;
+        return -1;
     }
 
 
@@ -149,11 +165,31 @@
     }
 
 
+    // 鏇存柊Port
+    int port = -1;
+    static int ids[] = { IDC_RADIO1, IDC_RADIO2, IDC_RADIO3, IDC_RADIO4 };
+    for (int i = 0; i < 4; i++) {
+        int state = ((CButton*)GetDlgItem(ids[i]))->GetCheck();
+        if (state == BST_CHECKED) port = i;
+    }
+    pPjWarp->port = port;
+
+    if (pPjWarp->port != -1) {
+        for (int i = 0; i < 8; i++) {
+            pPjWarp->checkSlot[i] = m_grid.GetSlotChecked(pPjWarp->port, i);
+            pPjWarp->material[i] = m_grid.GetSlotMaterialType(pPjWarp->port, i);
+        }
+    }
+
+
     ContentChanged(1);
+    return 0;
 }
 
 void CCjPage2::UpdatePjData()
 {
+    if (m_pContext == nullptr) return;
+
     m_bContentChangedLock = TRUE;
 
     CComboBox* pComboBox = (CComboBox*)GetDlgItem(IDC_COMBO_RECIPE);
@@ -163,24 +199,51 @@
         pComboBox->AddString(CString(recipe.c_str()));
     }
 
-    if (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);
+
+    // ComboBox
+    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);
+
+
+    // 4涓猚heckbox
+    static int ids[] = { IDC_RADIO1, IDC_RADIO2, IDC_RADIO3, IDC_RADIO4};
+    static char* pszUsed[] = { "Port1(宸插崰鐢�)", "Port2(宸插崰鐢�)", "Port3(宸插崰鐢�)", "Port4(宸插崰鐢�)" };
+    static char* pszUnUsed[] = { "Port1(鍙敤)", "Port2(鍙敤)", "Port3(鍙敤)", "Port4(鍙敤)" };
+
+    int portIndex = -1;
+    bool enable[] = {true, true, true, true};
+    bool checked[] = { false, false, false, false };
+    for (auto item : m_pjWarps) {
+        if (0 <= item.port && item.port <= 4 && item.pj != ((PJWarp*)m_pContext)->pj) {
+            enable[item.port] = false;
+        }
+    }
+    if (0 <= ((PJWarp*)m_pContext)->port && ((PJWarp*)m_pContext)->port <= 3) {
+        checked[((PJWarp*)m_pContext)->port] = true;
+        portIndex = ((PJWarp*)m_pContext)->port;
+        m_nSelRadioId = ids[((PJWarp*)m_pContext)->port];
     }
 
-    // 绀轰緥锛氳缃甈ort淇℃伅銆侀攣鍒椼�佸~鍏匞lass
-    /*
-    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);
-    */
+    for (int i = 0; i < 4; i++) {
+        CButton* pButton = (CButton*)GetDlgItem(ids[i]);
+        pButton->SetCheck(checked[i] ? BST_CHECKED : BST_UNCHECKED);
+        pButton->SetWindowText(enable[i] ? pszUnUsed[i] : pszUsed[i]);
+        pButton->EnableWindow(enable[i]);
+
+        m_grid.SetPortAllocated(i, !checked[i], _T(""));
+    }
+
+
+    // 璁剧疆鍕鹃�夋暟鎹�
+    if (portIndex != -1) {
+        for (int i = 0; i < 8; i++) {
+            m_grid.SetSlotChecked(portIndex, i, ((PJWarp*)m_pContext)->checkSlot[i]);
+            m_grid.SetSlotMaterialType(portIndex, i, ((PJWarp*)m_pContext)->material[i]);
+        }
+    }
 
     m_bContentChangedLock = FALSE;
 }
@@ -197,32 +260,127 @@
 
 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(""));
+    BOOL lock[] = {TRUE, TRUE, TRUE, TRUE};
+    if (IDC_RADIO1 == m_nSelRadioId) {
+        CheckRadioButton(IDC_RADIO1, IDC_RADIO4, 0);
+        m_nSelRadioId = 0;
+    }
+    else {
+        CheckRadioButton(IDC_RADIO1, IDC_RADIO4, IDC_RADIO1);
+        m_nSelRadioId = IDC_RADIO1;
+        lock[0] = FALSE;
+    }
+
+    for (int i = 0; i < 4; i++) {
+        m_grid.SetPortAllocated(i, lock[i], _T(""));
+    }
+
+    ContentChanged(0);
 }
 
 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(""));
+    BOOL lock[] = { TRUE, TRUE, TRUE, TRUE };
+    if (IDC_RADIO2 == m_nSelRadioId) {
+        CheckRadioButton(IDC_RADIO1, IDC_RADIO4, 0);
+        m_nSelRadioId = 0;
+    }
+    else {
+        CheckRadioButton(IDC_RADIO1, IDC_RADIO4, IDC_RADIO2);
+        m_nSelRadioId = IDC_RADIO2;
+        lock[1] = FALSE;
+    }
+
+    for (int i = 0; i < 4; i++) {
+        m_grid.SetPortAllocated(i, lock[i], _T(""));
+    }
+
+    ContentChanged(0);
 }
 
 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(""));
+    BOOL lock[] = { TRUE, TRUE, TRUE, TRUE };
+    if (IDC_RADIO3 == m_nSelRadioId) {
+        CheckRadioButton(IDC_RADIO1, IDC_RADIO4, 0);
+        m_nSelRadioId = 0;
+    }
+    else {
+        CheckRadioButton(IDC_RADIO1, IDC_RADIO4, IDC_RADIO3);
+        m_nSelRadioId = IDC_RADIO3;
+        lock[2] = FALSE;
+    }
+
+    for (int i = 0; i < 4; i++) {
+        m_grid.SetPortAllocated(i, lock[i], _T(""));
+    }
+
+    ContentChanged(0);
 }
 
 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(""));
+    BOOL lock[] = { TRUE, TRUE, TRUE, TRUE };
+    if (IDC_RADIO4 == m_nSelRadioId) {
+        CheckRadioButton(IDC_RADIO1, IDC_RADIO4, 0);
+        m_nSelRadioId = 0;
+    }
+    else {
+        CheckRadioButton(IDC_RADIO1, IDC_RADIO4, IDC_RADIO4);
+        m_nSelRadioId = IDC_RADIO4;
+        lock[3] = FALSE;
+    }
+
+    for (int i = 0; i < 4; i++) {
+        m_grid.SetPortAllocated(i, lock[i], _T(""));
+    }
+
+    ContentChanged(0);
+}
+
+void CCjPage2::OnGridSelChanged(NMHDR* pNMHDR, LRESULT* pResult)
+{
+    auto* nm = reinterpret_cast<CSG_SEL_CHANGE*>(pNMHDR);
+    const int port = nm->port;
+    const int slot = nm->slot;
+    const BOOL chk = nm->checked;
+
+    // 杩欓噷鍐欎綘鐨勪笟鍔¢�昏緫
+    // 渚嬪锛氭洿鏂扮姸鎬佹爮 / 鍚屾鍏跺畠鎺т欢 / 缁熻鏁伴噺
+    ContentChanged(0);
+
+    /*
+    if (m_pContext != nullptr) {
+        PJWarp* pjWarp = (PJWarp*)m_pContext;
+        for (int i = 0; i < 8; i++) {
+            pjWarp->checkSlot[i] = m_grid.GetSlotChecked(port, i);
+            pjWarp->material[i] = m_grid.GetSlotMaterialType(port, i);
+        }
+    }
+    */
+
+    *pResult = 0;
+}
+
+void CCjPage2::OnGridMatChanged(NMHDR* pNMHDR, LRESULT* pResult)
+{
+    auto* nm = reinterpret_cast<CSG_MAT_CHANGE*>(pNMHDR);
+    const int port = nm->port;
+    const int slot = nm->slot;
+    const int mat = nm->material; // 1/2
+
+    // 渚嬪锛氬嵆鍒诲埛鏂板彸渚ч瑙�/璁板綍鏃ュ織绛�
+    ContentChanged(0);
+
+    /*
+    if (m_pContext != nullptr) {
+        PJWarp* pjWarp = (PJWarp*)m_pContext;
+        for (int i = 0; i < 8; i++) {
+            pjWarp->checkSlot[i] = m_grid.GetSlotChecked(port, i);
+            pjWarp->material[i] = m_grid.GetSlotMaterialType(port, i);
+        }
+    }
+    */
+
+    *pResult = 0;
 }
diff --git a/SourceCode/Bond/Servo/CCjPage2.h b/SourceCode/Bond/Servo/CCjPage2.h
index bbbdb59..284aeb4 100644
--- a/SourceCode/Bond/Servo/CCjPage2.h
+++ b/SourceCode/Bond/Servo/CCjPage2.h
@@ -6,9 +6,11 @@
 
 
 struct PJWarp {
+	BOOL addToCj;
 	void* pj;
 	int port;
-	bool checkSlot[8];
+	BOOL checkSlot[8];
+	int material[8];
 };
 
 // CPjPage1 瀵硅瘽妗�
@@ -26,7 +28,7 @@
 
 protected:
 	void Resize();
-	virtual void OnApply();
+	virtual int OnApply();
 	virtual void OnSetContext(void* pContext);
 
 private:
@@ -35,6 +37,7 @@
 private:
 	CCarrierSlotGrid m_grid;
 	std::vector<PJWarp> m_pjWarps;
+	int m_nSelRadioId;
 
 // 瀵硅瘽妗嗘暟鎹�
 #ifdef AFX_DESIGN_TIME
@@ -54,4 +57,6 @@
 	afx_msg void OnBnClickedRadio2();
 	afx_msg void OnBnClickedRadio3();
 	afx_msg void OnBnClickedRadio4();
+	afx_msg void OnGridSelChanged(NMHDR* pNMHDR, LRESULT* pResult);
+	afx_msg void OnGridMatChanged(NMHDR* pNMHDR, LRESULT* pResult);
 };
diff --git a/SourceCode/Bond/Servo/CCjPage3.cpp b/SourceCode/Bond/Servo/CCjPage3.cpp
index 44428e9..6576f00 100644
--- a/SourceCode/Bond/Servo/CCjPage3.cpp
+++ b/SourceCode/Bond/Servo/CCjPage3.cpp
@@ -67,7 +67,7 @@
         */
 }
 
-void CCjPage3::OnApply()
+int CCjPage3::OnApply()
 {
-
+    return 0;
 }
\ No newline at end of file
diff --git a/SourceCode/Bond/Servo/CCjPage3.h b/SourceCode/Bond/Servo/CCjPage3.h
index 1593973..4c4472a 100644
--- a/SourceCode/Bond/Servo/CCjPage3.h
+++ b/SourceCode/Bond/Servo/CCjPage3.h
@@ -14,7 +14,7 @@
 
 protected:
 	void Resize();
-	virtual void OnApply();
+	virtual int OnApply();
 
 // 瀵硅瘽妗嗘暟鎹�
 #ifdef AFX_DESIGN_TIME
diff --git a/SourceCode/Bond/Servo/CCjPageBase.h b/SourceCode/Bond/Servo/CCjPageBase.h
index 3b90763..03688f7 100644
--- a/SourceCode/Bond/Servo/CCjPageBase.h
+++ b/SourceCode/Bond/Servo/CCjPageBase.h
@@ -18,7 +18,7 @@
 
 public:
 	void SetTitle(CString strTitle);
-	virtual void OnApply() {};
+	virtual int OnApply() { return 0; };
 	void SetOnContentChanged(ONCONTENTCHANGED onContentChanged);
 	void SetContext(void* pContext, int type);
 	void* GetContext();
diff --git a/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp b/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
index e9b9479..0bf7d67 100644
--- a/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
+++ b/SourceCode/Bond/Servo/CControlJobManagerDlg.cpp
@@ -51,9 +51,14 @@
 BEGIN_MESSAGE_MAP(CControlJobManagerDlg, CDialogEx)
 	ON_WM_SIZE()
 	ON_WM_GETMINMAXINFO()
-	ON_NOTIFY(TVN_SELCHANGED, IDC_TREE1, &CControlJobManagerDlg::OnTvnSelchangedTree1)
+	ON_NOTIFY(TVN_ITEMCHANGED, IDC_TREE1, &CControlJobManagerDlg::OnTvnItemChangedTree)
+	ON_NOTIFY(NM_CLICK, IDC_TREE1, &CControlJobManagerDlg::OnTreeClick)          // 鏂板
+	ON_NOTIFY(TVN_KEYDOWN, IDC_TREE1, &CControlJobManagerDlg::OnTreeKeyDown)        // 鏂板
+	ON_MESSAGE(WM_AFTER_TVCHECK, &CControlJobManagerDlg::OnAfterTvCheck)                // 鏂板
 	ON_WM_DESTROY()
 	ON_BN_CLICKED(IDC_BUTTON_APPLY, &CControlJobManagerDlg::OnBnClickedButtonApply)
+	ON_NOTIFY(TVN_SELCHANGING, IDC_TREE1, &CControlJobManagerDlg::OnTvnSelchangingTree1)
+	ON_BN_CLICKED(IDC_BUTTON_BATH_COMPLETION, &CControlJobManagerDlg::OnBnClickedButtonBathCompletion)
 END_MESSAGE_MAP()
 
 
@@ -184,8 +189,7 @@
 
 void CControlJobManagerDlg::UpdateCtrlState()
 {
-	auto& master = theApp.m_model.getMaster();
-	GetDlgItem(IDC_BUTTON_BATH_COMPLETION)->EnableWindow(false);
+	GetDlgItem(IDC_BUTTON_BATH_COMPLETION)->EnableWindow(true);
 }
 
 void CControlJobManagerDlg::UpdateControlJob()
@@ -199,7 +203,7 @@
 	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.SetItemState(hItem, INDEXTOSTATEIMAGEMASK(item.addToCj ? 2 : 1), TVIS_STATEIMAGEMASK);
 	}
 	m_tree.Expand(hRoot, TVE_EXPAND);
 }
@@ -216,35 +220,127 @@
 	return m_pControlJob->removePjPointer(pj->id());
 }
 
-void CControlJobManagerDlg::OnTvnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult)
+void CControlJobManagerDlg::OnTvnItemChangedTree(NMHDR* pNMHDR, LRESULT* pResult)
+{
+	auto* p = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
+	UINT oldState = p->itemOld.state, newState = p->itemNew.state;
+	HTREEITEM hItem = p->itemNew.hItem;
+
+	if (((oldState ^ newState) & TVIS_STATEIMAGEMASK) != 0) {
+		const int idx = (newState & TVIS_STATEIMAGEMASK) >> 12; // 1=鏈��,2=宸查��
+		const bool checked = (idx == 2);
+
+		PJWarp* pjWarp = (PJWarp*)m_tree.GetItemData(hItem);
+		if (pjWarp != nullptr) {
+			CString s; s.Format("%s %d", ((SERVO::CProcessJob*)pjWarp->pj)->id().c_str(), 
+				checked ? "" : "");
+			AfxMessageBox(s);
+		}
+	}
+
+
+	*pResult = 0;
+}
+
+// 鍛戒腑澶嶉�夋锛氱敤 NM_CLICK 鍋氬懡涓祴璇曪紝鐒跺悗鈥滄粸鍚庘�濊鍙栨柊鐘舵��
+void CControlJobManagerDlg::OnTreeClick(NMHDR* pNMHDR, LRESULT* pResult)
+{
+	*pResult = 0;
+
+	DWORD pos = ::GetMessagePos();
+	CPoint pt(GET_X_LPARAM(pos), GET_Y_LPARAM(pos));
+	m_tree.ScreenToClient(&pt);
+
+	TVHITTESTINFO ht{}; ht.pt = pt;
+	HTREEITEM hItem = m_tree.HitTest(&ht);
+	if (hItem && (ht.flags & TVHT_ONITEMSTATEICON)) {
+		// 璁� TreeView 鍏堝垏鎹紝鍐嶅紓姝ヨ鍙栨渶缁堢姸鎬�
+		PostMessage(WM_AFTER_TVCHECK, (WPARAM)hItem, 0);
+	}
+}
+
+// 绌烘牸閿篃浼氬垏鎹㈠閫夋
+void CControlJobManagerDlg::OnTreeKeyDown(NMHDR* pNMHDR, LRESULT* pResult)
+{
+	*pResult = 0;
+	auto* p = reinterpret_cast<LPNMTVKEYDOWN>(pNMHDR);
+	if (p->wVKey == VK_SPACE) {
+		HTREEITEM hItem = m_tree.GetSelectedItem();
+		if (hItem) PostMessage(WM_AFTER_TVCHECK, (WPARAM)hItem, 0);
+	}
+}
+
+// 缁熶竴澶勭悊锛堣鏈�缁堢姸鎬� + 浣犵殑涓氬姟锛�
+LRESULT CControlJobManagerDlg::OnAfterTvCheck(WPARAM wParam, LPARAM /*lParam*/)
+{
+	HTREEITEM hItem = (HTREEITEM)wParam;
+	if (!hItem) return 0;
+
+	// 鍙鐞嗙浜屽眰锛氭牴鐨勭洿鎺ュ瓙鑺傜偣锛堝彲閫夛級
+	auto getLevel = [&](HTREEITEM h) {
+		int lv = 0; for (HTREEITEM p = m_tree.GetParentItem(h); p; p = m_tree.GetParentItem(p)) ++lv; return lv;
+	};
+	if (getLevel(hItem) != 1) return 0;
+
+	BOOL checked = m_tree.GetCheck(hItem);
+
+	// 浣犵殑涓氬姟閫昏緫锛堜慨姝d簡 CString::Format 鐨勫弬鏁扮被鍨嬶級
+	auto* pjWarp = reinterpret_cast<PJWarp*>(m_tree.GetItemData(hItem));
+	if (pjWarp) {
+		pjWarp->addToCj = checked;
+	}
+
+	return 0;
+}
+
+void CControlJobManagerDlg::OnTvnSelchangingTree1(NMHDR* pNMHDR, LRESULT* pResult)
 {
 	LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);
 
+	bool allow = FALSE;
+	HTREEITEM hOldSel = pNMTreeView->itemOld.hItem;
 	HTREEITEM hSel = pNMTreeView->itemNew.hItem;
 	if (hSel != nullptr) {
 		HTREEITEM hParent = m_tree.GetParentItem(hSel);
 		if (hParent == nullptr) {
 			SERVO::CControlJob* cj = (SERVO::CControlJob*)m_tree.GetItemData(hSel);
 			ASSERT(m_pages.size() == 3);
-			m_pages[0]->ShowWindow(SW_HIDE);
-			m_pages[1]->ShowWindow(SW_HIDE);
-			m_pages[2]->ShowWindow(SW_SHOW);
+			if (0 == ShowPage(2)) {
+
+			}
 		}
 		else if (m_tree.GetParentItem(hParent) == nullptr) {
-			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);
-
-			((CCjPage2*)m_pages[1])->SetPjWarps(m_pjWarps);
+			if (0 == ShowPage(1)) {
+				PJWarp* pjWarp = (PJWarp*)m_tree.GetItemData(hSel);
+				((CCjPage2*)m_pages[1])->SetPjWarps(m_pjWarps);
+				m_pages[1]->SetContext(pjWarp, 1);
+			}
+			else {
+				allow = TRUE;
+			}
 		}
 		else {
 			// 鏈夌鍏� 鈫� 绗笁灞傚強浠ヤ笅 鈫� Glass
 		}
 	}
 
-	*pResult = 0;
+	*pResult = allow;
+}
+
+int CControlJobManagerDlg::ShowPage(int index)
+{
+	ASSERT(0 <= index && index <= 2);
+
+	for (int i = 0; i < 3; i++) {
+		if (m_pages[i]->IsWindowVisible()) {
+			int ret = m_pages[i]->OnApply();
+			if (ret != 0) return -1;
+		}
+
+		m_pages[i]->ShowWindow(index == i ? SW_SHOW : SW_HIDE);
+	}
+
+	return 0;
 }
 
 void CControlJobManagerDlg::OnDestroy()
@@ -269,7 +365,7 @@
 	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 pjWarp = {};
 		pjWarp.pj = pj;
 		pjWarp.port = -1;
 		m_pjWarps.push_back(pjWarp);
@@ -279,9 +375,12 @@
 void CControlJobManagerDlg::OnBnClickedButtonApply()
 {
 	for (auto item : m_pages) {
-		item->OnApply();
+		if (item->IsWindowVisible()) {
+			if (0 == item->OnApply()) {
+				GetDlgItem(IDC_BUTTON_APPLY)->EnableWindow(FALSE);
+			}
+		}
 	}
-	GetDlgItem(IDC_BUTTON_APPLY)->EnableWindow(FALSE);
 }
 
 void CControlJobManagerDlg::UpProcessJobId(PJWarp* pjWarp)
@@ -321,3 +420,75 @@
 	m_state.pjWarps = m_pjWarps;
 	m_bHasState = true;
 }
+
+void CControlJobManagerDlg::OnBnClickedButtonBathCompletion()
+{
+	// 鍏堝簲鐢�
+	for (int i = 0; i < 3; i++) {
+		if (m_pages[i]->IsWindowVisible()) {
+			int ret = m_pages[i]->OnApply();
+			if (ret != 0) return ;
+		}
+	}
+	GetDlgItem(IDC_BUTTON_APPLY)->EnableWindow(FALSE);
+
+
+	// 鍏堟鏌ユ暟鎹纭��
+	int checkCount = 0;
+	for (auto item : m_pjWarps) {
+		if (!item.addToCj) continue;
+		checkCount++;
+	}
+	if (checkCount == 0) {
+		AfxMessageBox(_T("鎮ㄦ病鏈夐�夋嫨瑕佽繘琛屽伐鑹哄鐞嗙殑Process Job!\n璇峰湪瑕佽繘琛屽伐鑹哄鐞嗙殑Process Job鍓嶆墦鍕俱��"));
+		return;
+	}
+
+
+
+	auto& master = theApp.m_model.getMaster();
+
+	std::vector<SERVO::CProcessJob*> pjs;
+	for (auto item : m_pjWarps) {
+		if (!item.addToCj) continue;
+		if (item.port == -1) continue;
+		BOOL bCheck = FALSE;
+		for (int i = 0; i < 8; i++) {
+			if (item.checkSlot[i]) {
+				bCheck = TRUE;
+				break;
+			}
+		}
+		if (!bCheck) continue;
+
+
+		SERVO::CProcessJob* pScr = (SERVO::CProcessJob*)item.pj;
+		SERVO::CProcessJob * pj = new SERVO::CProcessJob(pScr->id());
+		pj->setRecipe(SERVO::RecipeMethod::NoTuning, pScr->recipeSpec());
+
+		std::vector<SERVO::CarrierSlotInfo> carriers;
+		SERVO::CarrierSlotInfo csi;
+		csi.carrierId = "Port" + std::to_string(item.port + 1);
+		for (int i = 0; i < 8; i++) {
+			if (item.checkSlot[i]) {
+				csi.slots.push_back(i);
+			}
+		}
+		carriers.push_back(csi);
+		pj->setCarriers(carriers);
+		pjs.push_back(pj);
+
+		m_pControlJob->addPJ(pScr->id());
+	}
+
+
+	if (pjs.empty()) {
+		AfxMessageBox(_T("娌℃湁闇�瑕佽繘琛屽伐鑹哄鐞嗙殑Process Job!\n鍙兘鏈�夋嫨Port鎴栭�夋嫨浠讳綍鐗╂枡銆�"));
+		return;
+	}
+
+
+	m_pControlJob->setPJs(pjs);
+	int nRet = master.setProcessJobs(pjs);
+	master.setControlJob(*m_pControlJob);
+}
diff --git a/SourceCode/Bond/Servo/CControlJobManagerDlg.h b/SourceCode/Bond/Servo/CControlJobManagerDlg.h
index 1df9313..2407c30 100644
--- a/SourceCode/Bond/Servo/CControlJobManagerDlg.h
+++ b/SourceCode/Bond/Servo/CControlJobManagerDlg.h
@@ -5,6 +5,8 @@
 #include "ApredTreeCtrl2.h"
 
 
+#define WM_AFTER_TVCHECK (WM_USER + 1000)
+
 // CControlJobManagerDlg 瀵硅瘽妗�
 
 class CControlJobManagerDlg : public CDialogEx
@@ -26,6 +28,7 @@
 	void InitData();
 	void LoadState();
 	void SaveState();
+	int ShowPage(int index);
 
 private:
 	std::vector<CCjPageBase*> m_pages;
@@ -54,7 +57,12 @@
 	virtual BOOL OnInitDialog();
 	afx_msg void OnSize(UINT nType, int cx, int cy);
 	afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI);
-	afx_msg void OnTvnSelchangedTree1(NMHDR* pNMHDR, LRESULT* pResult);
+	afx_msg void OnTvnItemChangedTree(NMHDR* pNMHDR, LRESULT* pResult);
 	afx_msg void OnDestroy();
 	afx_msg void OnBnClickedButtonApply();
+	afx_msg void OnTvnSelchangingTree1(NMHDR* pNMHDR, LRESULT* pResult);
+	afx_msg void OnBnClickedButtonBathCompletion();
+	afx_msg void OnTreeClick(NMHDR* pNMHDR, LRESULT* pResult);
+	afx_msg void OnTreeKeyDown(NMHDR* pNMHDR, LRESULT* pResult);
+	afx_msg LRESULT OnAfterTvCheck(WPARAM wParam, LPARAM lParam);
 };
diff --git a/SourceCode/Bond/Servo/CLoadPort.cpp b/SourceCode/Bond/Servo/CLoadPort.cpp
index 096f845..ebd1690 100644
--- a/SourceCode/Bond/Servo/CLoadPort.cpp
+++ b/SourceCode/Bond/Servo/CLoadPort.cpp
@@ -505,6 +505,9 @@
 	void CLoadPort::setIndex(unsigned int index)
 	{
 		m_nIndex = index;
+
+		std::string id = "Port" + std::to_string(index + 1);
+		m_portStatusReport.setCassetteId(id.c_str());
 	}
 
 	unsigned int CLoadPort::getIndex()
diff --git a/SourceCode/Bond/Servo/Servo.rc b/SourceCode/Bond/Servo/Servo.rc
index 0aedf92..c8aacbf 100644
--- a/SourceCode/Bond/Servo/Servo.rc
+++ b/SourceCode/Bond/Servo/Servo.rc
Binary files differ
diff --git a/SourceCode/Bond/Servo/ServoDlg.cpp b/SourceCode/Bond/Servo/ServoDlg.cpp
index 4158bae..d3cba73 100644
--- a/SourceCode/Bond/Servo/ServoDlg.cpp
+++ b/SourceCode/Bond/Servo/ServoDlg.cpp
@@ -1039,14 +1039,16 @@
 		}
 	}
 	else if (id == IDC_BUTTON_JOBS) {
-		CControlJobManagerDlg dlg;
-		dlg.DoModal();
-
-		/*
-		CControlJobDlg dlg;
-		dlg.SetControlJob(theApp.m_model.m_master.getControlJob());
-		dlg.DoModal();
-		*/
+		static int i = 0; i++;
+		if (i % 2 == 0) {
+			CControlJobManagerDlg dlg;
+			dlg.DoModal();
+		}
+		else {
+			CControlJobDlg dlg;
+			dlg.SetControlJob(theApp.m_model.m_master.getControlJob());
+			dlg.DoModal();
+		}
 	}
 	else if (id == IDC_BUTTON_PORT_CONFIG) {
 		CPortConfigurationDlg dlg;

--
Gitblit v1.9.3