From 980d4fc1690b4f8a81dc65e8573d2898f34a406f Mon Sep 17 00:00:00 2001
From: LAPTOP-SNT8I5JK\Boounion <Chenluhua@qq.com>
Date: 星期五, 21 三月 2025 09:49:26 +0800
Subject: [PATCH] 1.加入连接图示

---
 SourceCode/Bond/Servo/CPanel.cpp            |   41 
 SourceCode/Bond/Servo/Servo.vcxproj         |   14 
 SourceCode/Bond/Servo/resource.h            |    0 
 SourceCode/Bond/Servo/CPageGraph2.h         |   40 
 SourceCode/Bond/Servo/Context.cpp           |    2 
 SourceCode/Bond/Servo/MapPosWnd.h           |   86 
 SourceCode/Bond/Servo/CPageGraph1.h         |   52 
 SourceCode/Bond/Servo/Servo.cpp             |    6 
 SourceCode/Bond/Servo/ColorTransfer.cpp     |  123 +
 SourceCode/Bond/Servo/EqsGraphWnd.cpp       | 2377 ++++++++++++++++++++++++++
 SourceCode/Bond/Servo/CEquipment.cpp        |   10 
 SourceCode/Bond/Servo/Servo.rc              |    0 
 SourceCode/Bond/Servo/CMaster.cpp           |    2 
 SourceCode/Bond/Servo/CPanel.h              |   23 
 SourceCode/Bond/Servo/CBonder.h             |    1 
 SourceCode/Bond/Servo/ServoDlg.h            |   21 
 SourceCode/Bond/Servo/CPageGraph2.cpp       |  181 ++
 SourceCode/Bond/Servo/EqsGraphWnd.h         |  234 ++
 SourceCode/Bond/Servo/HmTab.cpp             |  529 +++++
 SourceCode/Bond/Servo/MapPosWnd.cpp         |  536 ++++++
 SourceCode/Bond/Servo/CLoadPort.cpp         |   41 
 SourceCode/Bond/Servo/ServoDlg.cpp          |  352 ---
 SourceCode/Bond/Servo/CPageGraph1.cpp       |  406 ++++
 SourceCode/Bond/Servo/ColorTransfer.h       |   16 
 SourceCode/Bond/Servo/Servo.vcxproj.filters |   14 
 SourceCode/Bond/Servo/CPin.cpp              |    7 
 SourceCode/Bond/Servo/CEquipment.h          |    5 
 SourceCode/Bond/Servo/HmTab.h               |  130 +
 SourceCode/Bond/Servo/CBonder.cpp           |   71 
 SourceCode/Bond/Servo/Common.h              |   12 
 30 files changed, 5,017 insertions(+), 315 deletions(-)

diff --git a/SourceCode/Bond/Servo/CBonder.cpp b/SourceCode/Bond/Servo/CBonder.cpp
index 40d014a..136a15f 100644
--- a/SourceCode/Bond/Servo/CBonder.cpp
+++ b/SourceCode/Bond/Servo/CBonder.cpp
@@ -48,6 +48,26 @@
 		CEquipment::serialize(ar);
 	}
 
+	void CBonder::getAttributeVector(CAttributeVector& attrubutes)
+	{
+		__super::getAttributeVector(attrubutes);
+
+		for (auto item : m_inputPins) {
+			attrubutes.addAttribute(new CAttribute(item->getName().c_str(),
+				std::to_string((int)item->getType()).c_str(), ""));
+		}
+
+		for (auto item : m_outputPins) {
+			attrubutes.addAttribute(new CAttribute(item->getName().c_str(),
+				std::to_string((int)item->getType()).c_str(), ""));
+		}
+
+		for (auto item : m_panelList) {
+			attrubutes.addAttribute(new CAttribute("Panel",
+				item->getID().c_str(), ""));
+		}
+	}
+
 	int CBonder::recvIntent(CPin* pPin, CIntent* pIntent)
 	{
 		ASSERT(pPin);
@@ -58,7 +78,6 @@
 		CEquipment* pFromEq = pFromPin->getEquipment();
 		ASSERT(pFromEq);
 
-
 		LOGI("<CBonder><%s-%s>收到来自<%s.%s>的Intent<%d,%s,0x%x>", 
 			this->getName().c_str(), 
 			pPin->getName().c_str(),
@@ -66,9 +85,53 @@
 			pFromPin->getName().c_str(),
 			pIntent->getCode(),
 			pIntent->getMsg(),
-			pIntent->getContext()
-			);
+			pIntent->getContext());
 
-		return 0;
+
+
+		// 以下解释处理数据
+		int code = pIntent->getCode();
+
+
+		// 测试
+		if (code == FLOW_TEST) {
+			AfxMessageBox(pIntent->getMsg());
+			return FLOW_ACCEPT;
+		}
+
+
+		// 信号
+		if (code == FLOW_SIGNAL) {
+			return FLOW_ACCEPT;
+		}
+
+
+		// 数据
+		if (code == FLOW_SIGNAL) {
+			return FLOW_ACCEPT;
+		}
+
+
+		// 物料
+		if (code == FLOW_MOVE_MATERIAL) {
+			// 如果我这里是空的,可以接受
+			Lock();
+			if (m_panelList.size() < 15) {
+				CPanel* pPanel = (CPanel*)pIntent->getContext();
+				ASSERT(pPanel);
+				pPanel->addRef();
+				m_panelList.push_back(pPanel);
+				Unlock();
+				return FLOW_ACCEPT;
+			}
+			else {
+				Unlock();
+				return FLOW_REJECT;
+			}
+		}
+
+		
+
+		return FLOW_ACCEPT;
 	}
 }
diff --git a/SourceCode/Bond/Servo/CBonder.h b/SourceCode/Bond/Servo/CBonder.h
index efa351c..1785a5c 100644
--- a/SourceCode/Bond/Servo/CBonder.h
+++ b/SourceCode/Bond/Servo/CBonder.h
@@ -17,6 +17,7 @@
         virtual void initPins();
         virtual void onTimer(UINT nTimerid);
         virtual void serialize(CArchive& ar);
+        virtual void getAttributeVector(CAttributeVector& attrubutes);
         virtual int recvIntent(CPin* pPin, CIntent* pIntent);
     };
 }
diff --git a/SourceCode/Bond/Servo/CEquipment.cpp b/SourceCode/Bond/Servo/CEquipment.cpp
index 1b29fcd..a8b8df8 100644
--- a/SourceCode/Bond/Servo/CEquipment.cpp
+++ b/SourceCode/Bond/Servo/CEquipment.cpp
@@ -437,4 +437,14 @@
 	{
 		return 0;
 	}
+
+	void CEquipment::addPanelToList(CPanel* pPanel)
+	{
+		ASSERT(pPanel);
+
+		Lock();
+		pPanel->addRef();
+		m_panelList.push_back(pPanel);
+		Unlock();
+	}
 }
diff --git a/SourceCode/Bond/Servo/CEquipment.h b/SourceCode/Bond/Servo/CEquipment.h
index 1df97fd..aa9883e 100644
--- a/SourceCode/Bond/Servo/CEquipment.h
+++ b/SourceCode/Bond/Servo/CEquipment.h
@@ -15,6 +15,8 @@
 #include "CEqVCREnableStep.h"
 #include <vector>
 #include <map>
+#include <list>
+#include "CPanel.h"
 
 
 namespace SERVO {
@@ -107,6 +109,7 @@
 	protected:
 		inline void Lock() { EnterCriticalSection(&m_criticalSection); }
 		inline void Unlock() { LeaveCriticalSection(&m_criticalSection); }
+		void addPanelToList(CPanel* pPanel);
 
 	protected:
 		EquipmentListener m_listener;
@@ -119,6 +122,8 @@
 		MemoryBlock m_blockWriteBit;
 		std::vector<CPin*> m_inputPins;
 		std::vector<CPin*> m_outputPins;
+		std::list<CPanel*> m_panelList;
+
 
 		// 以下为从CC-Link读取到的Bit标志位
 	private:
diff --git a/SourceCode/Bond/Servo/CLoadPort.cpp b/SourceCode/Bond/Servo/CLoadPort.cpp
index b455e4f..71169c4 100644
--- a/SourceCode/Bond/Servo/CLoadPort.cpp
+++ b/SourceCode/Bond/Servo/CLoadPort.cpp
@@ -61,15 +61,48 @@
 			attrubutes.addAttribute(new CAttribute(item->getName().c_str(),
 				std::to_string((int)item->getType()).c_str(), ""));
 		}
+
+		for (auto item : m_panelList) {
+			attrubutes.addAttribute(new CAttribute("Panel",
+				item->getID().c_str(), ""));
+		}
 	}
 
 	void CLoadPort::outputPanel()
 	{
 		CPin* pOutPin = getPin("Out");
 
-		CIntent intent;
-		intent.setCode(1);
-		intent.setMsg("Hello");
-		pOutPin->sendIntent(&intent);
+
+		// 如果列表中没有Panel,模拟生成10张
+		if (m_panelList.empty()) {
+			static int ii = 0;
+			char szBuffer[64];
+			LOGI("<CLoadPort>模拟生成10张PANEL");
+			for (int i = 0; i < 10; i++) {
+				sprintf_s(szBuffer, "P20250320A1A%d", ++ii);
+				CPanel* pPanel = new CPanel();
+				pPanel->setID(szBuffer);
+				addPanelToList(pPanel);
+			}
+		}
+
+
+		// 模拟取出第一张Panel,传送到下一环节
+		Lock();
+		CPanel* pContext = m_panelList.front();
+		pContext->addRef();
+
+		CIntent intent(FLOW_MOVE_MATERIAL, "", pContext);
+		int nRet = pOutPin->sendIntent(&intent);
+		if (nRet == FLOW_REJECT) {
+			AfxMessageBox("对方拒绝接受");
+		}
+		else if (nRet == FLOW_ACCEPT) {
+			m_panelList.pop_front();
+			pContext->release();		// 添加到列队时addRef, 取出时release
+		}
+
+		pContext->release();
+		Unlock();
 	}
 }
diff --git a/SourceCode/Bond/Servo/CMaster.cpp b/SourceCode/Bond/Servo/CMaster.cpp
index 11c6d29..63a8e25 100644
--- a/SourceCode/Bond/Servo/CMaster.cpp
+++ b/SourceCode/Bond/Servo/CMaster.cpp
@@ -509,7 +509,7 @@
 			AfxMessageBox("连接失败");
 		}
 		else {
-			AfxMessageBox("连接成功");
+			// AfxMessageBox("连接成功");
 		}
 	}
 }
diff --git a/SourceCode/Bond/Servo/CPageGraph1.cpp b/SourceCode/Bond/Servo/CPageGraph1.cpp
new file mode 100644
index 0000000..be12698
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPageGraph1.cpp
@@ -0,0 +1,406 @@
+锘�// CPageGraph1.cpp: 瀹炵幇鏂囦欢
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "CPageGraph1.h"
+#include "afxdialogex.h"
+#include "Common.h"
+
+
+
+// Image
+#define IMAGE_ROBOT				2
+
+#define INDICATE_BONDER1		1
+#define INDICATE_BONDER2		2
+#define INDICATE_FLIPER			3
+#define INDICATE_ALIGNER		4
+#define INDICATE_LPORT4			5
+#define INDICATE_LPORT3			6
+#define INDICATE_LPORT2			7
+#define INDICATE_LPORT1			8
+#define INDICATE_ROBOT_ARM1		9
+#define INDICATE_ROBOT_ARM2		10
+#define INDICATE_VACUUM_BAKE	11
+#define INDICATE_BAKE_COOLING	12
+#define INDICATE_MEASUREMENT	13
+
+// CPageGraph1 瀵硅瘽妗�
+
+IMPLEMENT_DYNAMIC(CPageGraph1, CDialogEx)
+
+CPageGraph1::CPageGraph1(CWnd* pParent /*=nullptr*/)
+	: CDialogEx(IDD_PAGE_GRAPH1, pParent)
+{
+	m_pGraph = nullptr;
+	m_pObserver = nullptr;
+	m_bIsRobotMoving = FALSE;
+	m_crBkgnd = PAGE_GRPAH1_BACKGROUND_COLOR;
+	m_hbrBkgnd = nullptr;
+}
+
+CPageGraph1::~CPageGraph1()
+{
+}
+
+void CPageGraph1::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+}
+
+
+BEGIN_MESSAGE_MAP(CPageGraph1, CDialogEx)
+	ON_WM_CTLCOLOR()
+	ON_WM_DESTROY()
+	ON_WM_SIZE()
+	ON_NOTIFY(BYSERVOGRAPH_ITEM_CLICKED, IDC_SERVO_GRAPH1, &CPageGraph1::OnGraphItemClicked)
+	ON_WM_ERASEBKGND()
+	ON_WM_TIMER()
+END_MESSAGE_MAP()
+
+
+// CPageGraph1 娑堟伅澶勭悊绋嬪簭
+
+
+void CPageGraph1::InitRxWindows()
+{
+	/* code */
+	// 璁㈤槄鏁版嵁
+	IRxWindows* pRxWindows = RX_GetRxWindows();
+	pRxWindows->enableLog(5);
+	if (m_pObserver == NULL) {
+		m_pObserver = pRxWindows->allocObserver([&](IAny* pAny) -> void {
+			// onNext
+			pAny->addRef();
+			int code = pAny->getCode();
+			if (RX_CODE_EQ_ALIVE == code) {
+				// 閫氱煡璁惧鐘舵��
+				SERVO::CEquipment* pEquipment = nullptr;
+				if (pAny->getPtrValue("ptr", (void*&)pEquipment)) {
+					if (pEquipment != nullptr) {
+						int nID = pEquipment->getID();
+						BOOL bAlive = pEquipment->isAlive();
+						if (EQ_ID_EFEM == nID) {
+							DeviceStatus status = bAlive ? DeviceStatus::ONLINE : DeviceStatus::OFFLINE;
+							UpdateDeviceStatus(INDICATE_ROBOT_ARM1, status);
+							UpdateDeviceStatus(INDICATE_ROBOT_ARM2, status);
+						}
+					}
+				}
+			}
+
+			pAny->release();
+			}, [&]() -> void {
+				// onComplete
+			}, [&](IThrowable* pThrowable) -> void {
+				// onErrorm
+				pThrowable->printf();
+			});
+
+		theApp.m_model.getObservable()->observeOn(pRxWindows->mainThread())
+			->subscribe(m_pObserver);
+	}
+}
+
+BOOL CPageGraph1::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+	SetTimer(1, 3000, nullptr);
+
+
+	// 鍥剧ず
+	m_pGraph = CServoGraph::Hook(GetDlgItem(IDC_SERVO_GRAPH1)->GetSafeHwnd());
+	CString strPath;
+	strPath.Format(_T("%s\\res\\Servo001.bmp"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
+	m_pGraph->AddImage(1, (LPTSTR)(LPCTSTR)strPath, 0, 0);
+
+	strPath.Format(_T("%s\\res\\Robot001.bmp"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
+	m_pGraph->AddImage(IMAGE_ROBOT, (LPTSTR)(LPCTSTR)strPath, 170, 270);
+
+	// 娣诲姞鎸囩ず鍣�
+	// Bonder
+	m_pGraph->AddIndicateBox(INDICATE_BONDER1, 220, 172, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_BONDER1, "10", "Bonder 1");
+	m_pGraph->AddIndicateBox(INDICATE_BONDER2, 220, 516, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_BONDER2, "11", "Bonder 2");
+
+
+	// 缈昏浆
+	m_pGraph->AddIndicateBox(INDICATE_FLIPER, 338, 172, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_FLIPER, "8", "Fliper");
+
+
+	// 瀵逛綅
+	m_pGraph->AddIndicateBox(INDICATE_ALIGNER, 428, 172, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_ALIGNER, "7", "Aligner");
+
+
+	// Load port 4
+	m_pGraph->AddIndicateBox(INDICATE_LPORT4, 518, 172, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_LPORT4, "4", "LPort4");
+
+
+	// Load port 3
+	m_pGraph->AddIndicateBox(INDICATE_LPORT3, 606, 172, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_LPORT3, "3", "LPort3");
+
+
+	// Load port 2
+	m_pGraph->AddIndicateBox(INDICATE_LPORT2, 690, 172, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_LPORT2, "2", "LPort2");
+
+
+	// Load port 1
+	m_pGraph->AddIndicateBox(INDICATE_LPORT1, 774, 172, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_LPORT1, "1", "LPort1");
+
+
+	// Robot
+	m_pGraph->AddIndicateBox(INDICATE_ROBOT_ARM1, 190, 294, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_ROBOT_ARM1, "5", "Robot");
+	m_pGraph->AddIndicateBox(INDICATE_ROBOT_ARM2, 243, 294, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_ROBOT_ARM2, "6", "Robot");
+
+
+
+
+	// Vacuum bake
+	m_pGraph->AddIndicateBox(INDICATE_VACUUM_BAKE, 396, 516, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_VACUUM_BAKE, "9", "Vacuum bake");
+
+
+	// Bake cooling
+	m_pGraph->AddIndicateBox(INDICATE_BAKE_COOLING, 566, 516, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_BAKE_COOLING, "12", "Bake cooling");
+
+
+	// 绮惧害妫�
+	m_pGraph->AddIndicateBox(INDICATE_MEASUREMENT, 737, 516, 48, RGB(22, 22, 22),
+		RGB(255, 127, 39), EQ_BOX_OFFLINE);
+	m_pGraph->SetBoxText(INDICATE_MEASUREMENT, "13", "Measurement");
+
+
+	// 缁戝畾鏁版嵁
+	{
+		SERVO::CEquipment* pEquipment = theApp.m_model.m_master.getEquipment(EQ_ID_EFEM);
+		m_pGraph->SetIndicateBoxData(INDICATE_ROBOT_ARM1, pEquipment);
+	}
+
+
+	return TRUE;  // return TRUE unless you set the focus to a control
+				  // 寮傚父: OCX 灞炴�ч〉搴旇繑鍥� FALSE
+}
+
+void CPageGraph1::UpdateDeviceStatus(int id, DeviceStatus status)
+{
+	// 鏍规嵁鐘舵�佽缃鑹�
+	COLORREF newBackgroundColor;
+	COLORREF newFrameColor1;
+	COLORREF newFrameColor2;
+
+	switch (status) {
+	case ONLINE:
+		newBackgroundColor = EQ_BOX_ONLINE;
+		newFrameColor1 = EQ_BOX_FRAME1;
+		newFrameColor2 = EQ_BOX_FRAME2;
+		break;
+	case OFFLINE:
+		newBackgroundColor = RGB(222, 222, 222);
+		newFrameColor1 = EQ_BOX_FRAME1;
+		newFrameColor2 = EQ_BOX_FRAME2;
+		break;
+	default:
+		newBackgroundColor = RGB(255, 255, 255); // 榛樿鐧借壊鑳屾櫙
+		newFrameColor1 = RGB(0, 0, 0);           // 榛樿榛戣壊妗嗘灦1
+		newFrameColor2 = RGB(0, 0, 0);           // 榛樿榛戣壊妗嗘灦2
+		break;
+	}
+
+	m_pGraph->UpdateIndicateBox1Colors(id, newBackgroundColor, newFrameColor1, newFrameColor2);
+
+	// 鍒锋柊鐣岄潰
+	Invalidate();
+	UpdateWindow();
+}
+
+BOOL CPageGraph1::OnEraseBkgnd(CDC* pDC)
+{
+	// TODO: 鍦ㄦ娣诲姞娑堟伅澶勭悊绋嬪簭浠g爜鍜�/鎴栬皟鐢ㄩ粯璁ゅ��
+	if (m_bIsRobotMoving) {
+		// 绂佹鍒锋柊鑳屾櫙锛岄伩鍏嶉棯鐑�
+		return TRUE;
+	}
+
+	return CDialogEx::OnEraseBkgnd(pDC);
+}
+
+HBRUSH CPageGraph1::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
+{
+	HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
+
+	if (nCtlColor == CTLCOLOR_STATIC) {
+		pDC->SetBkColor(m_crBkgnd);
+		pDC->SetTextColor(RGB(0, 0, 0));
+	}
+
+	if (m_hbrBkgnd == nullptr) {
+		m_hbrBkgnd = CreateSolidBrush(m_crBkgnd);
+	}
+
+	return m_hbrBkgnd;
+}
+
+void CPageGraph1::OnDestroy()
+{
+	CDialogEx::OnDestroy();
+
+	if (m_hbrBkgnd != nullptr) {
+		::DeleteObject(m_hbrBkgnd);
+	}
+}
+
+void CPageGraph1::OnSize(UINT nType, int cx, int cy)
+{
+	CDialogEx::OnSize(nType, cx, cy);
+	if (GetDlgItem(IDC_SERVO_GRAPH1) == nullptr) return;
+
+	CRect rcClient;
+	GetClientRect(&rcClient);
+	GetDlgItem(IDC_SERVO_GRAPH1)->MoveWindow(0, 0, rcClient.Width(), rcClient.Height());
+}
+
+void CPageGraph1::UpdateRobotPosition(float percentage)
+{
+	// 闄愬埗鐧惧垎姣旇寖鍥村湪 [0, 1] 涔嬮棿
+	if (percentage < 0.0f) percentage = 0.0f;
+	if (percentage > 1.0f) percentage = 1.0f;
+
+	// 鏍规嵁鐧惧垎姣旇绠楃洰鏍� X 鍧愭爣
+	int startX = m_pGraph->GetImage(IMAGE_ROBOT)->x;
+	int endX = static_cast<int>(170 + percentage * (700 - 170));
+
+	int arm1Offset = 20;  // 浠庡浘鐗囧埌ARM1鐨勫亸绉�
+	int arm2Offset = 73;  // 浠庡浘鐗囧埌ARM2鐨勫亸绉�
+
+	// 璁$畻绉诲姩鎵�闇�鐨勬椂闂�
+	int distance = abs(endX - startX);
+	int duration = static_cast<int>((distance / 100.0) * 1000);
+
+	auto startTime = std::chrono::steady_clock::now();
+	auto endTime = startTime + std::chrono::milliseconds(duration);
+
+	// 寮�濮嬬Щ鍔紝璁剧疆鏍囪
+	m_bIsRobotMoving = TRUE;
+
+	// 寮�濮嬪钩婊戠Щ鍔�
+	while (std::chrono::steady_clock::now() < endTime) {
+		auto currentTime = std::chrono::steady_clock::now();
+		float progress = std::chrono::duration<float, std::milli>(currentTime - startTime).count() / duration;
+		progress = min(progress, 1.0f);
+
+		// 鏍规嵁杩涘害璁$畻褰撳墠浣嶇疆
+		int currentX = static_cast<int>(startX + progress * (endX - startX));
+		m_pGraph->UpdateImageCoordinates(IMAGE_ROBOT, currentX, 270);
+		m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, currentX + arm1Offset, 294);
+		m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, currentX + arm2Offset, 294);
+
+		// 鍒锋柊鐣岄潰
+		Invalidate();
+		UpdateWindow();
+
+		// 鎺у埗甯х巼绾︿负 60 FPS
+		std::this_thread::sleep_for(std::chrono::milliseconds(16));
+	}
+
+	// 纭繚鏈�鍚庝綅缃簿纭埌鐩爣浣嶇疆
+	m_pGraph->UpdateImageCoordinates(IMAGE_ROBOT, endX, 270);
+	m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, endX + arm1Offset, 294);
+	m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, endX + arm2Offset, 294);
+
+	// 鐣岄潰閲嶇粯
+	Invalidate();
+
+	// 鍔ㄧ敾缁撴潫锛岃缃爣璁�
+	m_bIsRobotMoving = FALSE;
+}
+
+void CPageGraph1::RotateRobot(float angleInDegrees)
+{
+	// 灏嗚搴﹁浆鎹负寮у害
+	float angleInRadians = static_cast<float>(std::acos(-1)) / 180.0f * angleInDegrees;
+
+	// 鑾峰彇鏈哄櫒浜哄浘鐗囩殑褰撳墠鍧愭爣鍜屼腑蹇�
+	auto* pImage = m_pGraph->GetImage(IMAGE_ROBOT);
+	if (!pImage) return;
+
+	// 鏇存柊 Rotate 鍥剧墖鐨勮搴︼紝纭繚瑙掑害淇濇寔鍦� [0, 360) 鑼冨洿鍐�
+	m_pGraph->UpdateImageAngle(IMAGE_ROBOT, static_cast<float>(fmod(pImage->angle + angleInDegrees + 360, 360)));
+
+	int cx = pImage->x + pImage->bmWidth / 2;  // 鍥剧墖涓績 X
+	int cy = pImage->y + pImage->bmHeight / 2; // 鍥剧墖涓績 Y
+
+	// 鏃嬭浆鎸囩ず妗嗙殑鍧愭爣
+	auto* pRobot1 = m_pGraph->GetIndicateBox(INDICATE_ROBOT_ARM1);
+	auto* pRobot2 = m_pGraph->GetIndicateBox(INDICATE_ROBOT_ARM2);
+
+	if (pRobot1 && pRobot2) {
+		int newArmX1 = pImage->x + 20;
+		int newArmY1 = 294;
+
+		int newArmX2 = pImage->x + 73;
+		int newArmY2 = 294;
+
+		if (angleInDegrees != 0.0f) {
+			// 璁$畻鎸囩ず妗�1鐨勬柊鍧愭爣
+			newArmX1 = static_cast<int>(cx + (pRobot1->x - cx) * cos(angleInRadians) - (pRobot1->y - cy) * sin(angleInRadians));
+			newArmY1 = static_cast<int>(cy + (pRobot1->x - cx) * sin(angleInRadians) + (pRobot1->y - cy) * cos(angleInRadians));
+
+			// 璁$畻鎸囩ず妗�2鐨勬柊鍧愭爣
+			newArmX2 = static_cast<int>(cx + (pRobot2->x - cx) * cos(angleInRadians) - (pRobot2->y - cy) * sin(angleInRadians));
+			newArmY2 = static_cast<int>(cy + (pRobot2->x - cx) * sin(angleInRadians) + (pRobot2->y - cy) * cos(angleInRadians));
+		}
+
+		// 鏇存柊鎸囩ず妗嗙殑浣嶇疆
+		m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, newArmX1, newArmY1);
+		m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, newArmX2, newArmY2);
+	}
+
+	// 寮哄埗閲嶇粯鐣岄潰
+	Invalidate();
+}
+
+void CPageGraph1::OnGraphItemClicked(NMHDR* pNMHDR, LRESULT* pResult)
+{
+	BYSERVOGRAPH_NMHDR* pGraphNmhdr = reinterpret_cast<BYSERVOGRAPH_NMHDR*>(pNMHDR);
+	CString s; s.Format(_T("OnGraphItemClicked %d"), pGraphNmhdr->dwData);
+	SERVO::CEquipment* pEquipment = (SERVO::CEquipment*)m_pGraph->GetIndicateBoxData(pGraphNmhdr->dwData);
+	if (pEquipment != nullptr) {
+		AfxMessageBox(pEquipment->getName().c_str());
+	}
+
+
+	*pResult = 0;
+}
+
+void CPageGraph1::OnTimer(UINT_PTR nIDEvent)
+{
+	if (1 == nIDEvent) {
+		KillTimer(1);
+		InitRxWindows();
+	}
+
+	CDialogEx::OnTimer(nIDEvent);
+}
diff --git a/SourceCode/Bond/Servo/CPageGraph1.h b/SourceCode/Bond/Servo/CPageGraph1.h
new file mode 100644
index 0000000..405af87
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPageGraph1.h
@@ -0,0 +1,52 @@
+锘�#pragma once
+#include "ServoGraph.h"
+
+
+enum DeviceStatus {
+	ONLINE,       // 鍦ㄧ嚎
+	OFFLINE,      // 绂荤嚎
+};
+
+
+// CPageGraph1 瀵硅瘽妗�
+
+class CPageGraph1 : public CDialogEx
+{
+	DECLARE_DYNAMIC(CPageGraph1)
+
+public:
+	CPageGraph1(CWnd* pParent = nullptr);   // 鏍囧噯鏋勯�犲嚱鏁�
+	virtual ~CPageGraph1();
+
+
+public:
+	void InitRxWindows();
+	void UpdateDeviceStatus(int id, DeviceStatus status);
+	void UpdateRobotPosition(float percentage);
+	void RotateRobot(float angleInDegrees);
+
+private:
+	IObserver* m_pObserver;
+	CServoGraph* m_pGraph;
+	BOOL m_bIsRobotMoving;
+	COLORREF m_crBkgnd;
+	HBRUSH m_hbrBkgnd;
+
+// 瀵硅瘽妗嗘暟鎹�
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_PAGE_GRAPH1 };
+#endif
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 鏀寔
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
+	afx_msg void OnDestroy();
+	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnGraphItemClicked(NMHDR* pNMHDR, LRESULT* pResult);
+	afx_msg BOOL OnEraseBkgnd(CDC* pDC);
+	afx_msg void OnTimer(UINT_PTR nIDEvent);
+};
diff --git a/SourceCode/Bond/Servo/CPageGraph2.cpp b/SourceCode/Bond/Servo/CPageGraph2.cpp
new file mode 100644
index 0000000..aa1cd73
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPageGraph2.cpp
@@ -0,0 +1,181 @@
+锘�// CPageGraph2.cpp: 瀹炵幇鏂囦欢
+//
+
+#include "stdafx.h"
+#include "Servo.h"
+#include "CPageGraph2.h"
+#include "afxdialogex.h"
+
+
+// CPageGraph2 瀵硅瘽妗�
+
+IMPLEMENT_DYNAMIC(CPageGraph2, CDialogEx)
+
+CPageGraph2::CPageGraph2(CWnd* pParent /*=nullptr*/)
+	: CDialogEx(IDD_PAGE_GRAPH2, pParent)
+{
+	m_pEqsGraphWnd = nullptr;
+	m_crBkgnd = PAGE_GRPAH2_BACKGROUND_COLOR;
+	m_hbrBkgnd = nullptr;
+}
+
+CPageGraph2::~CPageGraph2()
+{
+}
+
+void CPageGraph2::DoDataExchange(CDataExchange* pDX)
+{
+	CDialogEx::DoDataExchange(pDX);
+}
+
+
+BEGIN_MESSAGE_MAP(CPageGraph2, CDialogEx)
+	ON_WM_CTLCOLOR()
+	ON_WM_DESTROY()
+	ON_WM_SIZE()
+	ON_WM_TIMER()
+END_MESSAGE_MAP()
+
+
+// CPageGraph2 娑堟伅澶勭悊绋嬪簭
+
+
+BOOL CPageGraph2::OnInitDialog()
+{
+	CDialogEx::OnInitDialog();
+	SetTimer(1, 2000, nullptr);
+
+
+	// filter graph wnd
+	EqsGraphListener listener;
+	listener.onCheckConnectPin = [](PIN* pPin1, PIN* pPin2) -> bool {
+		ASSERT(pPin1);
+		ASSERT(pPin2);
+		ASSERT(pPin1->pData);
+		ASSERT(pPin2->pData);
+
+		//int nRet = ((IPin*)pPin1->pData)->checkConnectPin((IPin*)pPin2->pData);
+		//if (nRet >= 0) {
+		//	return true;
+		//}
+
+		return false;
+	};
+	listener.onConnectPin = [](PIN* pPin1, PIN* pPin2) -> bool {
+		ASSERT(pPin1);
+		ASSERT(pPin2);
+		ASSERT(pPin1->pData);
+		ASSERT(pPin2->pData);
+
+		//int nRet = ((IPin*)pPin1->pData)->connectPin((IPin*)pPin2->pData);
+		//if (nRet >= 0) {
+		//	return true;
+		//}
+
+		return false;
+	};
+	listener.onDisconnectPin = [](PIN* pPin) -> bool {
+		ASSERT(pPin);
+		ASSERT(pPin->pData);
+
+		//int nRet = ((IPin*)pPin->pData)->disconnect();
+		//if (nRet >= 0) {
+		//	return true;
+		//}
+
+		return false;
+	};
+	listener.onDeleteEqItem = [&](EQITEM* pItem) -> bool {
+		ASSERT(pItem);
+		ASSERT(pItem->pData);
+		return true;
+		// return _filterManager.unload((CFilter*)pFilter->pData) >= 0;
+	};
+	listener.onEqItemPosChanged = [&](EQITEM* pItem, int x, int y) -> void {
+		ASSERT(pItem);
+	};
+	listener.onDblckEqItem = [&](EQITEM* pItem) -> bool {
+		ASSERT(pItem);
+		return true;
+	};
+	listener.onRclickEqItem = [&](EQITEM* pItem) -> bool {
+		ASSERT(pItem);
+		return true;
+	};
+
+	m_pEqsGraphWnd = CEqsGraphWnd::FromHandle(GetDlgItem(IDC_EQSGRAPHWND1)->m_hWnd);
+	m_pEqsGraphWnd->SetBkgndColor(m_crBkgnd);
+	m_pEqsGraphWnd->SetOnListener(listener);
+
+
+	return TRUE;  // return TRUE unless you set the focus to a control
+				  // 寮傚父: OCX 灞炴�ч〉搴旇繑鍥� FALSE
+}
+
+
+HBRUSH CPageGraph2::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
+{
+	HBRUSH hbr = CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
+
+	if (nCtlColor == CTLCOLOR_STATIC) {
+		pDC->SetBkColor(m_crBkgnd);
+		pDC->SetTextColor(RGB(0, 0, 0));
+	}
+
+	if (m_hbrBkgnd == nullptr) {
+		m_hbrBkgnd = CreateSolidBrush(m_crBkgnd);
+	}
+
+	return m_hbrBkgnd;
+}
+
+
+void CPageGraph2::OnDestroy()
+{
+	CDialogEx::OnDestroy();
+
+	if (m_hbrBkgnd != nullptr) {
+		::DeleteObject(m_hbrBkgnd);
+	}
+}
+
+
+void CPageGraph2::OnSize(UINT nType, int cx, int cy)
+{
+	CDialogEx::OnSize(nType, cx, cy);
+	if (GetDlgItem(IDC_EQSGRAPHWND1) == nullptr) return;
+
+	CRect rcClient;
+	GetClientRect(&rcClient);
+	GetDlgItem(IDC_EQSGRAPHWND1)->MoveWindow(0, 0, rcClient.Width(), rcClient.Height());
+}
+
+#define INPIN		1
+#define OUTPIN		2
+void CPageGraph2::AddEqToGraphWnd(SERVO::CEquipment* pEquipment)
+{
+	EQITEM* pItem = m_pEqsGraphWnd->AddItem(0, pEquipment->getName().c_str(), (DWORD_PTR)pEquipment);
+	m_pEqsGraphWnd->SetItemType(pItem, ITEM_SMALL);
+	std::vector<SERVO::CPin*>& inPins = pEquipment->getInputPins();
+	for (auto inPin : inPins) {
+		m_pEqsGraphWnd->AddPin(pItem, INPIN, inPin->getName().c_str(), (DWORD_PTR)inPin);
+	}
+
+	std::vector<SERVO::CPin*>& outPins = pEquipment->getOutputPins();
+	for (auto outPin : outPins) {
+		m_pEqsGraphWnd->AddPin(pItem, OUTPIN, outPin->getName().c_str(), (DWORD_PTR)outPin);
+	}
+}
+
+void CPageGraph2::OnTimer(UINT_PTR nIDEvent)
+{
+	if (1 == nIDEvent) {
+		KillTimer(1);
+		std::list<SERVO::CEquipment*>& eqs = theApp.m_model.m_master.getEquipmentList();
+		for (auto item : eqs) {
+			AddEqToGraphWnd(item);
+		}
+	}
+
+	CDialogEx::OnTimer(nIDEvent);
+}
diff --git a/SourceCode/Bond/Servo/CPageGraph2.h b/SourceCode/Bond/Servo/CPageGraph2.h
new file mode 100644
index 0000000..c587ab6
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPageGraph2.h
@@ -0,0 +1,40 @@
+锘�#pragma once
+#include "EqsGraphWnd.h"
+
+
+// CPageGraph2 瀵硅瘽妗�
+
+class CPageGraph2 : public CDialogEx
+{
+	DECLARE_DYNAMIC(CPageGraph2)
+
+public:
+	CPageGraph2(CWnd* pParent = nullptr);   // 鏍囧噯鏋勯�犲嚱鏁�
+	virtual ~CPageGraph2();
+
+
+private:
+	void AddEqToGraphWnd(SERVO::CEquipment* pEquipment);
+
+private:
+	CEqsGraphWnd* m_pEqsGraphWnd;
+	COLORREF m_crBkgnd;
+	HBRUSH m_hbrBkgnd;
+
+
+// 瀵硅瘽妗嗘暟鎹�
+#ifdef AFX_DESIGN_TIME
+	enum { IDD = IDD_PAGE_GRAPH2 };
+#endif
+
+protected:
+	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 鏀寔
+
+	DECLARE_MESSAGE_MAP()
+public:
+	virtual BOOL OnInitDialog();
+	afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
+	afx_msg void OnDestroy();
+	afx_msg void OnSize(UINT nType, int cx, int cy);
+	afx_msg void OnTimer(UINT_PTR nIDEvent);
+};
diff --git a/SourceCode/Bond/Servo/CPanel.cpp b/SourceCode/Bond/Servo/CPanel.cpp
new file mode 100644
index 0000000..00b3b46
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPanel.cpp
@@ -0,0 +1,41 @@
+#include "stdafx.h"
+#include "CPanel.h"
+
+
+namespace SERVO {
+	CPanel::CPanel()
+	{
+
+	}
+
+	CPanel::~CPanel()
+	{
+
+	}
+
+	std::string& CPanel::getClassName()
+	{
+		static std::string strName = "CPanel";
+		return strName;
+	}
+
+	std::string CPanel::toString()
+	{
+		std::string strText;
+		strText += "CPanel[";
+		strText += ("ID:" + m_strID + ";");
+		strText += "]";
+
+		return strText;
+	}
+
+	void CPanel::setID(const char* pszID)
+	{
+		m_strID = pszID;
+	}
+
+	std::string& CPanel::getID()
+	{
+		return m_strID;
+	}
+}
diff --git a/SourceCode/Bond/Servo/CPanel.h b/SourceCode/Bond/Servo/CPanel.h
new file mode 100644
index 0000000..bc9c51a
--- /dev/null
+++ b/SourceCode/Bond/Servo/CPanel.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "Context.h"
+#include <string>
+
+
+namespace SERVO {
+	class CPanel : public CContext
+	{
+	public:
+		CPanel();
+		virtual ~CPanel();
+
+	public:
+		virtual std::string& getClassName();
+		virtual std::string toString();
+		void setID(const char* pszID);
+		std::string& getID();
+
+	private:
+		std::string m_strID;
+	};
+}
+
diff --git a/SourceCode/Bond/Servo/CPin.cpp b/SourceCode/Bond/Servo/CPin.cpp
index 391e250..4a57efb 100644
--- a/SourceCode/Bond/Servo/CPin.cpp
+++ b/SourceCode/Bond/Servo/CPin.cpp
@@ -115,16 +115,15 @@
 	int CPin::sendIntent(CIntent* pIntent)
 	{
 		if (m_pConnectedPin != NULL) {
-			m_pConnectedPin->recvIntent(pIntent);
+			return m_pConnectedPin->recvIntent(pIntent);
 		}
 
-		return 0;
+		return FLOW_REJECT;
 	}
 
 	int CPin::recvIntent(CIntent* pIntent)
 	{
 		assert(m_pEquipment);
-		m_pEquipment->recvIntent(this, pIntent);
-		return 0;
+		return m_pEquipment->recvIntent(this, pIntent);
 	}
 }
diff --git a/SourceCode/Bond/Servo/ColorTransfer.cpp b/SourceCode/Bond/Servo/ColorTransfer.cpp
new file mode 100644
index 0000000..161cc6d
--- /dev/null
+++ b/SourceCode/Bond/Servo/ColorTransfer.cpp
@@ -0,0 +1,123 @@
+#include "stdafx.h"
+#include "ColorTransfer.h"
+
+
+CColorTransfer::CColorTransfer()
+{
+}
+
+
+CColorTransfer::~CColorTransfer()
+{
+}
+
+COLORREF CColorTransfer::ApproximateColor(COLORREF crScr, double diff)
+{
+	double h, s, l;
+	RGB2HSL(crScr, h, s, l);
+	l -= diff;
+
+	return HSL2RGB(h, s, l);
+}
+
+void CColorTransfer::RGB2HSL(COLORREF color, double &H, double &S, double &L)
+{
+	double R, G, B, Max, Min, del_R, del_G, del_B, del_Max;
+	R = GetRValue(color) / 255.0;       //Where RGB values = 0 ÷ 255
+	G = GetGValue(color) / 255.0;
+	B = GetBValue(color) / 255.0;
+
+	Min = min(R, min(G, B));    //Min. value of RGB
+	Max = max(R, max(G, B));    //Max. value of RGB
+	del_Max = Max - Min;        //Delta RGB value
+
+	L = (Max + Min) / 2.0;
+
+	if (del_Max == 0) {
+		// This is a gray, no chroma... 
+		// H = 2.0/3.0;          //Windows下S值为0时,H值始终为160(2/3*240)
+		H = 0;                  //HSL results = 0 ÷ 1
+		S = 0;
+	}
+	else {
+		//Chromatic data...
+		if (L < 0.5) {
+			S = del_Max / (Max + Min);
+		}
+		else {
+			S = del_Max / (2 - Max - Min);
+		}
+
+		del_R = (((Max - R) / 6.0) + (del_Max / 2.0)) / del_Max;
+		del_G = (((Max - G) / 6.0) + (del_Max / 2.0)) / del_Max;
+		del_B = (((Max - B) / 6.0) + (del_Max / 2.0)) / del_Max;
+
+		if (R == Max) {
+			H = del_B - del_G;
+		}
+		else if (G == Max) {
+			H = (1.0 / 3.0) + del_R - del_B;
+		}
+		else if (B == Max) {
+			H = (2.0 / 3.0) + del_G - del_R;
+		}
+
+		if (H < 0) {
+			H += 1;
+		}
+		if (H > 1) {
+			H -= 1;
+		}
+	}
+}
+
+COLORREF CColorTransfer::HSL2RGB(double H, double S, double L)
+{
+	double R, G, B;
+	double var_1, var_2;
+	if (S == 0) {
+		// HSL values = 0 ÷ 1
+		R = L * 255.0;                   //RGB results = 0 ÷ 255
+		G = L * 255.0;
+		B = L * 255.0;
+	}
+	else {
+		if (L < 0.5) {
+			var_2 = L * (1 + S);
+		}
+		else {
+			var_2 = (L + S) - (S * L);
+		}
+		var_1 = 2.0 * L - var_2;
+
+		R = 255.0 * Hue2RGB(var_1, var_2, H + (1.0 / 3.0));
+		G = 255.0 * Hue2RGB(var_1, var_2, H);
+		B = 255.0 * Hue2RGB(var_1, var_2, H - (1.0 / 3.0));
+	}
+	return RGB(R, G, B);
+}
+
+double CColorTransfer::Hue2RGB(double v1, double v2, double vH)
+{
+	if (vH < 0) {
+		vH += 1;
+	}
+
+	if (vH > 1) {
+		vH -= 1;
+	}
+
+	if (6.0 * vH < 1) {
+		return v1 + (v2 - v1) * 6.0 * vH;
+	}
+
+	if (2.0 * vH < 1) {
+		return v2;
+	}
+
+	if (3.0 * vH < 2) {
+		return v1 + (v2 - v1) * ((2.0 / 3.0) - vH) * 6.0;
+	}
+
+	return (v1);
+}
diff --git a/SourceCode/Bond/Servo/ColorTransfer.h b/SourceCode/Bond/Servo/ColorTransfer.h
new file mode 100644
index 0000000..9525675
--- /dev/null
+++ b/SourceCode/Bond/Servo/ColorTransfer.h
@@ -0,0 +1,16 @@
+#pragma once
+class CColorTransfer
+{
+public:
+	CColorTransfer();
+	~CColorTransfer();
+
+public:
+	static void RGB2HSL(COLORREF color, double &H, double &S, double &L);
+	static COLORREF HSL2RGB(double H, double S, double L);
+	static COLORREF ApproximateColor(COLORREF crScr, double diff);
+
+private:
+	static double Hue2RGB(double v1, double v2, double vH);
+};
+
diff --git a/SourceCode/Bond/Servo/Common.h b/SourceCode/Bond/Servo/Common.h
index e113aa0..939e474 100644
--- a/SourceCode/Bond/Servo/Common.h
+++ b/SourceCode/Bond/Servo/Common.h
@@ -25,6 +25,8 @@
 #define PANEL_MASTER_BACKGROUND_COLOR		RGB(255, 255, 255)
 #define PANEL_ATTRIBUTES_BACKGROUND_COLOR	RGB(255, 255, 255)
 #define PANEL_EQUIPMENT_BACKGROUND_COLOR	RGB(255, 255, 255)
+#define PAGE_GRPAH1_BACKGROUND_COLOR		RGB(255, 255, 255)
+#define PAGE_GRPAH2_BACKGROUND_COLOR		RGB(255, 255, 255)
 #define EQ_BOX_OFFLINE						RGB(222, 222, 222)
 #define EQ_BOX_ONLINE						RGB(0, 176, 80)
 #define EQ_BOX_FRAME1						RGB(22, 22, 22)
@@ -75,6 +77,7 @@
 #define STEP_EQ_VCR_ENABLE		_T("EQVCREnable")
 
 
+/* base alarm */
 #define BASE_ALARM_EFEM		10000
 #define BASE_ALARM_BONDER1	20000
 #define BASE_ALARM_BONDER2	30000
@@ -88,3 +91,12 @@
 
 /* 自定义消息 */
 #define ID_MSG_PANEL_RESIZE			WM_USER + 1998
+
+
+/* 流程控制相关代码 */
+#define FLOW_REJECT					0x0
+#define FLOW_ACCEPT					0x1
+#define FLOW_TEST					0x1000
+#define FLOW_SIGNAL					0x1001
+#define FLOW_DATA					0x1002
+#define FLOW_MOVE_MATERIAL			0x1003
\ No newline at end of file
diff --git a/SourceCode/Bond/Servo/Context.cpp b/SourceCode/Bond/Servo/Context.cpp
index fad62bc..96aa4b1 100644
--- a/SourceCode/Bond/Servo/Context.cpp
+++ b/SourceCode/Bond/Servo/Context.cpp
@@ -5,7 +5,7 @@
 CContext::CContext()
 {
 	m_nRef = 0;
-	m_nRetCode = CRC_UNKNOWN;
+	m_nRetCode = 0;
 	m_hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
 	InitializeCriticalSection(&m_criticalSection);
 }
diff --git a/SourceCode/Bond/Servo/EqsGraphWnd.cpp b/SourceCode/Bond/Servo/EqsGraphWnd.cpp
new file mode 100644
index 0000000..46aa8a4
--- /dev/null
+++ b/SourceCode/Bond/Servo/EqsGraphWnd.cpp
@@ -0,0 +1,2377 @@
+#include "stdafx.h"
+#include "EqsGraphWnd.h"
+#include "ColorTransfer.h"
+#include "MapPosWnd.h"
+
+
+#define INPIN		1
+#define OUTPIN		2
+
+#define ITEM_CX_SMALL	150
+#define ITEM_CY_SMALL	90
+#define ITEM_CX_NORMAL	250
+#define ITEM_CY_NORMAL	150
+#define ITEM_CX_LARGE	400
+#define ITEM_CY_LARGE	240
+
+#define HT_NOWHERE		0x1
+#define HT_ITEM			0x2
+#define HT_PIN			0x4
+#define HT_LINE			0x8
+
+#define PINWIDTH		8
+#define PINHEIGHT		12
+
+#define TIMER_FLASH				1
+#define TIMER_ANIMATION_RECT	2
+
+#define MAPPOSSIZE					150
+#define MAPPOSWND_PADDING_RIGHT		12
+#define MAPPOSWND_ID				1001
+
+CEqsGraphWnd::CEqsGraphWnd()
+{
+	m_bUseGdiPlus = TRUE;
+	m_hWnd = NULL;
+	m_crFrame = GetSysColor(COLOR_WINDOWFRAME);
+	m_crBkgnd = RGB(255, 255, 255);
+	m_listener.onConnectPin = nullptr;
+	m_listener.onCheckConnectPin = nullptr;
+	m_listener.onDisconnectPin = nullptr;
+	m_listener.onDeleteEqItem = nullptr;
+	m_listener.onEqItemPosChanged = nullptr;
+	m_listener.onDblckEqItem = nullptr;
+	m_listener.onRclickEqItem = nullptr;
+	m_crItemBackground[0] = RGB(218, 218, 218);
+	m_crItemBackground[1] = RGB(193, 208, 227);
+	m_crItemFrame[0] = RGB(128, 128, 128);
+	m_crItemFrame[1] = RGB(147, 172, 206);
+	m_crItemNameText[0] = RGB(0, 0, 0);
+	m_crItemNameText[1] = RGB(0, 0, 0);
+	m_crItemIdText[0] = CColorTransfer::ApproximateColor(m_crItemNameText[0], -0.3f);
+	m_crItemIdText[1] = m_crItemIdText[0];
+	m_nCurSel = -1;
+	m_bMultiSelect = FALSE;
+	m_nItemRound = 0;
+	m_pCurItem = NULL;
+	m_pCurPin = NULL;
+	m_pSelLineOutPin = NULL;
+	m_crPinBkgnd[0] = RGB(218, 218, 218);
+	m_crPinBkgnd[1] = RGB(193, 0, 0);
+	m_crPinBkgnd[2] = RGB(193, 0, 0);
+	m_nStageCx = 4000;
+	m_nStageCy = 3000;
+	m_nOffsetX = 0;
+	m_nOffsetY = 0;
+	m_pFlashItem = NULL;
+	m_nFlashCount = 0;
+	m_hWndMapPos = NULL;
+	m_bEnableScroll = FALSE;
+	m_nMagneticLinHoz = 0;
+	m_nMagneticLinVer = 0;
+	m_hFontTitle = nullptr;
+
+}
+
+CEqsGraphWnd::~CEqsGraphWnd()
+{
+	ReleaseAllItems();
+}
+
+BOOL CEqsGraphWnd::RegisterWndClass()
+{
+	WNDCLASS wc;
+	wc.lpszClassName = EQSGRAPHWND_CLASS;
+	wc.hInstance = AfxGetInstanceHandle();
+	wc.lpfnWndProc = WindowProc;
+	wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
+	wc.hIcon = 0;
+	wc.lpszMenuName = NULL;
+	wc.hbrBackground = NULL;
+	wc.style = CS_GLOBALCLASS | CS_DBLCLKS;
+	wc.cbClsExtra = 0;
+	wc.cbWndExtra = 0;
+
+	// 注册窗口类
+	return (::RegisterClass(&wc) != 0);
+}
+
+CEqsGraphWnd* CEqsGraphWnd::FromHandle(HWND hWnd)
+{
+	CEqsGraphWnd* pEqsGraphWnd = (CEqsGraphWnd*)::GetProp(hWnd, EQSGRAPHWND_TAG);
+	return pEqsGraphWnd;
+}
+
+CEqsGraphWnd* CEqsGraphWnd::Hook(HWND hWnd)
+{
+	CEqsGraphWnd* pEqsGraphWnd = (CEqsGraphWnd*)GetProp(hWnd, EQSGRAPHWND_TAG);
+	if (pEqsGraphWnd == NULL) {
+		pEqsGraphWnd = new CEqsGraphWnd();
+		pEqsGraphWnd->m_hWnd = hWnd;
+
+		SetProp(hWnd, EQSGRAPHWND_TAG, (HANDLE)pEqsGraphWnd);
+	}
+
+
+	return pEqsGraphWnd;
+}
+
+void CEqsGraphWnd::InitFont()
+{
+	HDC hDC = GetDC(NULL);
+	HFONT hFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
+
+	{
+		LOGFONT lf = { 0 };
+		::GetObject(hFont, sizeof(LOGFONT), &lf);
+		m_hFontName = CreateFontIndirect(&lf);
+	}
+
+	{
+		LOGFONT lf = { 0 };
+		::GetObject(hFont, sizeof(LOGFONT), &lf);
+
+		int nSize = int(-lf.lfHeight * 72.0 / GetDeviceCaps(hDC, LOGPIXELSY) + 0.5);
+		lf.lfHeight = MulDiv(0 - (nSize - 2), GetDeviceCaps(hDC, LOGPIXELSX), 72);
+		m_hFontId = CreateFontIndirect(&lf);
+	}
+
+	::ReleaseDC(NULL, hDC);
+}
+
+void CEqsGraphWnd::SetItemRound(int nRound)
+{
+	m_nItemRound = nRound;
+}
+
+void CEqsGraphWnd::SetDefaultItemBackgroundColor(COLORREF crNormal, COLORREF crSel)
+{
+	m_crItemBackground[0] = crNormal;
+	m_crItemBackground[1] = crSel;
+}
+
+void CEqsGraphWnd::SetDefaultItemFrameColor(COLORREF crNormal, COLORREF crSel)
+{
+	m_crItemFrame[0] = crNormal;
+	m_crItemFrame[1] = crSel;
+}
+
+void CEqsGraphWnd::SetDefaultItemTextColor(COLORREF crNormal, COLORREF crSel)
+{
+	m_crItemNameText[0] = crNormal;
+	m_crItemNameText[1] = crSel;
+
+	m_crItemIdText[0] = CColorTransfer::ApproximateColor(m_crItemNameText[0], -0.3f);
+	m_crItemIdText[1] = CColorTransfer::ApproximateColor(m_crItemNameText[1], -0.3f);
+}
+
+void CEqsGraphWnd::EnableScroll(BOOL bEnable)
+{
+	m_bEnableScroll = bEnable;
+}
+
+void CEqsGraphWnd::EnableMultiSelect()
+{
+	m_bMultiSelect = TRUE;
+}
+
+void CEqsGraphWnd::Init()
+{
+	InitFont();
+	CalculateScollbar();
+
+	long style = GetWindowLong(m_hWnd, GWL_STYLE);
+	SetWindowLong(m_hWnd, GWL_STYLE, style | WS_CLIPCHILDREN);
+
+	// MapPosWnd
+	if (m_hWndMapPos == NULL) {
+		m_hWndMapPos = CreateWindowEx(0, MAPPOSWND_CLASS,
+			NULL, WS_CHILD | WS_VISIBLE,
+			0, 0, 400, 400,
+			m_hWnd, (HMENU)MAPPOSWND_ID, NULL, NULL);
+
+		long styleex = GetWindowLong(m_hWndMapPos, GWL_EXSTYLE);
+		SetWindowLong(m_hWndMapPos, GWL_EXSTYLE, styleex | WS_EX_CLIENTEDGE);
+
+		CMapPosWnd *pMapPosWnd = CMapPosWnd::FromHandle(m_hWndMapPos);
+		pMapPosWnd->SetWndMaxSize(MAPPOSSIZE);
+		pMapPosWnd->SetStageSize(m_nStageCx, m_nStageCy, TRUE);
+	}
+}
+
+void CEqsGraphWnd::CalculateMapPos()
+{
+	CRect rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::OffsetRect(&rcClient, m_nOffsetX, m_nOffsetY);
+	CMapPosWnd *pMapPosWnd = CMapPosWnd::FromHandle(m_hWndMapPos);
+	pMapPosWnd->SetViewPort(&rcClient, TRUE);
+}
+
+void CEqsGraphWnd::CalculateScollbar()
+{
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+
+	// vert scroll
+	if(m_bEnableScroll) {
+		SCROLLINFO scrinffo;
+		scrinffo.cbSize = sizeof(SCROLLINFO);
+		scrinffo.fMask = SIF_ALL;
+		scrinffo.nMax = m_nStageCy;
+		scrinffo.nMin = 0;
+		scrinffo.nPos = m_nOffsetY;
+		scrinffo.nTrackPos = 0;
+		scrinffo.nPage = rcClient.bottom - rcClient.top;
+		SetScrollInfo(m_hWnd, SB_VERT, &scrinffo, TRUE);
+	}
+
+	// horz scroll
+	if (m_bEnableScroll) {
+		SCROLLINFO scrinffo;
+		scrinffo.cbSize = sizeof(SCROLLINFO);
+		scrinffo.fMask = SIF_ALL;
+		scrinffo.nMax = m_nStageCx;
+		scrinffo.nMin = 0;
+		scrinffo.nPos = m_nOffsetX;
+		scrinffo.nTrackPos = 0;
+		scrinffo.nPage = rcClient.right - rcClient.left;
+		SetScrollInfo(m_hWnd, SB_HORZ, &scrinffo, TRUE);
+	}
+}
+
+/*
+ * 计算磁力线位置
+ */
+void CEqsGraphWnd::CalculateMagneticLine(EQITEM* pItem, LPRECT lprcItemRect, int &hoz, int &ver)
+{
+	hoz = 0;
+	ver = 0;
+#define MAGNETIC_DIS		10
+
+	// 检测是否接近或对齐
+	for (int i = 0; i < m_arItem.GetSize(); i++) {
+		EQITEM *pTemp = (EQITEM*)m_arItem.GetAt(i);
+		if (pTemp != pItem) {
+			if (abs(lprcItemRect->left - pTemp->rect.left) < MAGNETIC_DIS) {
+				ver = pTemp->rect.left;
+				break;
+			}
+			else if (abs(lprcItemRect->right - pTemp->rect.right) < MAGNETIC_DIS) {
+				ver = pTemp->rect.right - (lprcItemRect->right- lprcItemRect->left);
+				break;
+			}
+		}
+	}
+	
+	for (int i = 0; i < m_arItem.GetSize(); i++) {
+		EQITEM* pTemp = (EQITEM*)m_arItem.GetAt(i);
+		if (pTemp != pItem) {
+			if (abs(lprcItemRect->top - pTemp->rect.top) < MAGNETIC_DIS) {
+				hoz = pTemp->rect.top;
+				break;
+			}
+			else if (abs(lprcItemRect->bottom - pTemp->rect.bottom) < MAGNETIC_DIS) {
+				hoz = pTemp->rect.bottom - (lprcItemRect->bottom - lprcItemRect->top);
+				break;
+			}
+		}
+	}
+}
+
+
+void CEqsGraphWnd::Release()
+{
+	::DeleteObject(m_hFontName);
+	::DeleteObject(m_hFontId);
+	if (m_hFontTitle != nullptr) {
+		::DeleteObject(m_hFontTitle);
+	}
+
+	// delete
+	delete this;
+}
+
+/*
+ * 取得In Pin的区域
+ * pItem -- EQITEM
+ * lpRect -- 得到的Rect
+ * 返回是否成功
+ */
+BOOL CEqsGraphWnd::GetItemRect(EQITEM* pItem, LPRECT lpRect)
+{
+	ASSERT(pItem);
+	if (pItem == m_pAnimationItem) {
+		lpRect->left = (int)(m_rcAnimation.left - m_nOffsetX);
+		lpRect->top = (int)(m_rcAnimation.top - m_nOffsetY);
+		lpRect->right = (int)(m_rcAnimation.right - m_nOffsetX);
+		lpRect->bottom = (int)(m_rcAnimation.bottom - m_nOffsetY);
+	}
+	else {
+		lpRect->left = (int)(pItem->rect.left - m_nOffsetX);
+		lpRect->top = (int)(pItem->rect.top - m_nOffsetY);
+		lpRect->right = (int)(pItem->rect.right - m_nOffsetX);
+		lpRect->bottom = (int)(pItem->rect.bottom - m_nOffsetY);
+	}
+
+	return TRUE;
+}
+
+BOOL CEqsGraphWnd::GetItemWarperRect(EQITEM* pItem, LPRECT lpRect)
+{
+	CopyRect(lpRect, &pItem->rect);
+	lpRect->left -= PINWIDTH;
+	lpRect->right += PINWIDTH;
+
+	return TRUE;
+}
+
+/*
+ * 取得In Pin的区域
+ * pItem -- EQITEM
+ * nPinIndex -- in pin索引
+ * lpRect -- 得到的Rect
+ * 返回是否成功
+ */
+BOOL CEqsGraphWnd::GetInPinRect(EQITEM* pItem, int nPinIndex, LPRECT lpRect)
+{
+	CPtrArray * pPins = (CPtrArray *)pItem->pInPins;
+	if (nPinIndex >= pPins->GetSize()) {
+		return FALSE;
+	}
+
+	int nBottomMargin = pPins->GetCount() >= 4 ? 8 : 0;
+	int nSpace = ((pItem->rect.bottom - nBottomMargin - pItem->rect.top) - (int)pPins->GetSize() * PINHEIGHT) / (pPins->GetSize() + 1);
+	lpRect->right = pItem->rect.left+1 - m_nOffsetX;
+	lpRect->left = lpRect->right - PINWIDTH;
+	lpRect->bottom = pItem->rect.top + (nSpace + PINHEIGHT) * (nPinIndex+1) - m_nOffsetY;
+	lpRect->top = lpRect->bottom - PINHEIGHT;
+
+	return TRUE;
+}
+
+/*
+ * 取得Out Pin的区域
+ * pItem -- EQITEM
+ * nPinIndex -- in pin索引
+ * lpRect -- 得到的Rect
+ * 返回是否成功
+ */
+BOOL CEqsGraphWnd::GetOutPinRect(EQITEM* pItem, int nPinIndex, LPRECT lpRect)
+{
+	CPtrArray * pPins = (CPtrArray *)pItem->pOutPins;
+	if (nPinIndex >= pPins->GetSize()) {
+		return FALSE;
+	}
+
+	int nSpace = ((pItem->rect.bottom - pItem->rect.top) - (int)pPins->GetSize() * PINHEIGHT) / (pPins->GetSize() + 1);
+	lpRect->left = pItem->rect.right-1 - m_nOffsetX;
+	lpRect->right = lpRect->left + PINWIDTH;
+	lpRect->bottom = pItem->rect.top + (nSpace + PINHEIGHT) * (nPinIndex + 1) - m_nOffsetY;
+	lpRect->top = lpRect->bottom - PINHEIGHT;
+
+	return TRUE;
+}
+
+/*
+ * 取得Pin的Point
+ * pItem -- EQITEM
+ * nPinIndex -- in pin索引
+ * lpRect -- 得到的Rect
+ * 返回是否成功
+ */
+BOOL CEqsGraphWnd::GetPinPoint(PIN *pPin, LPPOINT lpPoint)
+{
+	ASSERT(pPin);
+	ASSERT(pPin->pItem);
+	CPtrArray * pPins;
+	RECT rcPin;
+
+	// in pin?
+	pPins = (CPtrArray *)pPin->pItem->pInPins;
+	for(int i=0; i<pPins->GetCount(); i++) {
+		if (pPins->GetAt(i) == pPin) {
+			if (GetInPinRect(pPin->pItem, i, &rcPin)) {
+				lpPoint->x = rcPin.left + (rcPin.right - rcPin.left) / 2;
+				lpPoint->y = rcPin.top + (rcPin.bottom - rcPin.top) / 2;
+				return TRUE;
+			}
+		}
+	}
+
+	// out pin?
+	pPins = (CPtrArray *)pPin->pItem->pOutPins;
+	for (int i = 0; i<pPins->GetCount(); i++) {
+		if (pPins->GetAt(i) == pPin) {
+			if (GetOutPinRect(pPin->pItem, i, &rcPin)) {
+				lpPoint->x = rcPin.left + (rcPin.right - rcPin.left) / 2;
+				lpPoint->y = rcPin.top + (rcPin.bottom - rcPin.top) / 2;
+				return TRUE;
+			}
+		}
+	}
+
+	return FALSE;
+}
+
+void CEqsGraphWnd::ReleaseItem(EQITEM* pItem)
+{
+	ASSERT(pItem);
+
+	CPtrArray *pArray = (CPtrArray *)pItem->pInPins;
+	for (int j = 0; j < pArray->GetSize(); j++) {
+		PIN *pPin = (PIN *)pArray->GetAt(j);
+		if (pPin->pConnectedPin != NULL) {
+			pPin->pConnectedPin->pConnectedPin = NULL;
+		}
+		delete pPin;
+	}
+	delete pArray;
+
+	pArray = (CPtrArray *)pItem->pOutPins;
+	for (int j = 0; j < pArray->GetSize(); j++) {
+		PIN *pPin = (PIN *)pArray->GetAt(j);
+		if (pPin->pConnectedPin != NULL) {
+			pPin->pConnectedPin->pConnectedPin = NULL;
+		}
+		delete pPin;
+	}
+	delete pArray;
+
+	delete pItem;
+}
+
+void CEqsGraphWnd::ReleaseAllItems()
+{
+	for (int i = 0; i < m_arItem.GetCount(); i++) {
+		ReleaseItem((EQITEM*)m_arItem.GetAt(i));
+	}
+	m_arItem.RemoveAll();
+}
+
+int CEqsGraphWnd::GetPinState(PIN *pPin)
+{
+	if (pPin == m_pCurPin) {
+		return 1;
+	}
+
+	return 0;
+}
+
+/*
+ * 清空PIN连接线缓存点,以便重新计算和绘制
+ */
+void CEqsGraphWnd::ClearConnectedLinePoint(EQITEM*& pItem)
+{
+	ASSERT(pItem);
+
+	CPtrArray *pPins;
+	PIN *pPin;
+
+	pPins = (CPtrArray *)pItem->pInPins;
+	for (int j = 0; j < pPins->GetSize(); j++) {
+		pPin = (PIN *)pPins->GetAt(j);
+		if (pPin->pConnectedPin != NULL) {
+			pPin->pConnectedPin->nLinePtCount = 0;
+		}
+	}
+
+	pPins = (CPtrArray *)pItem->pOutPins;
+	for (int j = 0; j < pPins->GetSize(); j++) {
+		pPin = (PIN *)pPins->GetAt(j);
+		pPin->nLinePtCount = 0;
+	}
+}
+
+void CEqsGraphWnd::SetOnListener(EqsGraphListener& listener)
+{
+	m_listener.onConnectPin = listener.onConnectPin;
+	m_listener.onCheckConnectPin = listener.onCheckConnectPin;
+	m_listener.onDisconnectPin = listener.onDisconnectPin;
+	m_listener.onDeleteEqItem = listener.onDeleteEqItem;
+	m_listener.onEqItemPosChanged = listener.onEqItemPosChanged;
+	m_listener.onDblckEqItem = listener.onDblckEqItem;
+	m_listener.onRclickEqItem = listener.onRclickEqItem;
+}
+
+BOOL CEqsGraphWnd::SetCurSel(int nSel)
+{
+	if (!(nSel == -1 || nSel < m_arItem.GetCount())) {
+		return FALSE;
+	}
+
+
+	m_nCurSel = nSel;
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+
+	return TRUE;
+}
+
+BOOL CEqsGraphWnd::SetCurSel(CString strItemName)
+{
+	int nIndex = -1;
+	for (int i = 0; i < m_arItem.GetCount(); i++) {
+		EQITEM* pItem = (EQITEM*)m_arItem.GetAt(i);
+		if (strItemName.Compare(pItem->text) == 0) {
+			nIndex = i;
+			break;
+		}
+	}
+
+	if (nIndex == -1) {
+		return FALSE;
+	}
+
+	m_nCurSel = nIndex;
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+
+	return TRUE;
+}
+
+BOOL CEqsGraphWnd::SetCurSel(DWORD_PTR pData)
+{
+	int nIndex = -1;
+	for (int i = 0; i < m_arItem.GetCount(); i++) {
+		EQITEM* pItem = (EQITEM*)m_arItem.GetAt(i);
+		if (pItem->pData == pData) {
+			nIndex = i;
+			break;
+		}
+	}
+
+	if (nIndex == -1) {
+		return FALSE;
+	}
+
+	m_nCurSel = nIndex;
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+
+	return TRUE;
+}
+
+/*
+ * nType: ITEM_SMALL, ITEM_NORMAL or ITEM_LARGE
+ */
+EQITEM* CEqsGraphWnd::AddItem(int id, CString strText, DWORD_PTR dwData, int nType/* = ITEM_NORMAL*/)
+{
+	// 需要计算一个新位置,不然全部重叠在一起
+	int x, y;
+	x = (m_arItem.GetCount() % 4) * 218;
+	y = (m_arItem.GetCount() / 4) * 168;
+
+
+	EQITEM* pItem = new EQITEM;
+	memset(pItem, 0, sizeof(EQITEM));
+	pItem->id = id;
+	pItem->nShowType = nType;
+	pItem->rect.left = x + 20 + m_nOffsetX;
+	pItem->rect.top = y + 50 + m_nOffsetY;
+	if (pItem->nShowType == ITEM_SMALL) {
+		pItem->rect.right = pItem->rect.left + ITEM_CX_SMALL;
+		pItem->rect.bottom = pItem->rect.top + ITEM_CY_SMALL;
+	}
+	else if(pItem->nShowType == ITEM_LARGE){
+		pItem->rect.right = pItem->rect.left + ITEM_CX_LARGE;
+		pItem->rect.bottom = pItem->rect.top + ITEM_CY_LARGE;
+	}
+	else {
+		pItem->rect.right = pItem->rect.left + ITEM_CX_NORMAL;
+		pItem->rect.bottom = pItem->rect.top + ITEM_CY_NORMAL;
+	}
+
+	pItem->pData = dwData;
+	pItem->pInPins = (DWORD_PTR)new CPtrArray();
+	pItem->pOutPins = (DWORD_PTR)new CPtrArray();
+
+	int len = min(63, strText.GetLength());
+	memcpy(pItem->text, (LPTSTR)(LPCTSTR)strText, len);
+	pItem->text[len] = '\0';
+	m_arItem.Add(pItem);
+
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+
+	return pItem;
+}
+
+void CEqsGraphWnd::RemoveItem(EQITEM* pItem)
+{
+	BOOL bChanged = FALSE;
+	if (m_listener.onDeleteEqItem != NULL) {
+		if (m_listener.onDeleteEqItem(m_pCurItem)) {
+			bChanged = DeleteItem(m_pCurItem) >= 0;
+		}
+	}
+
+
+	if (bChanged) {
+		if (pItem == m_pCurItem) {
+			m_pCurItem = NULL;
+		}
+		RECT rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		::InvalidateRect(m_hWnd, &rcClient, TRUE);
+	}
+}
+
+void CEqsGraphWnd::SetItemText(EQITEM* pItem, CString strText)
+{
+	int len = min(63, strText.GetLength());
+	memcpy(pItem->text, (LPTSTR)(LPCTSTR)strText, len);
+	pItem->text[len] = '\0';
+
+	::InvalidateRect(m_hWnd, &pItem->rect, TRUE);
+}
+
+void CEqsGraphWnd::SetItemType(EQITEM* pItem, int nType)
+{
+	pItem->nShowType = nType;
+	if (pItem->nShowType == ITEM_SMALL) {
+		pItem->rect.right = pItem->rect.left + ITEM_CX_SMALL;
+		pItem->rect.bottom = pItem->rect.top + ITEM_CY_SMALL;
+	}
+	else if (pItem->nShowType == ITEM_LARGE) {
+		pItem->rect.right = pItem->rect.left + ITEM_CX_LARGE;
+		pItem->rect.bottom = pItem->rect.top + ITEM_CY_LARGE;
+	}
+	else {
+		pItem->rect.right = pItem->rect.left + ITEM_CX_NORMAL;
+		pItem->rect.bottom = pItem->rect.top + ITEM_CY_NORMAL;
+	}
+
+	::InvalidateRect(m_hWnd, &pItem->rect, TRUE);
+}
+
+PIN * CEqsGraphWnd::AddPin(EQITEM* pItem, int nType, CString strName, DWORD_PTR dwData)
+{
+	ASSERT(pItem);
+	ASSERT(nType == INPIN || nType == OUTPIN);
+	ASSERT(pItem->pInPins);
+	ASSERT(pItem->pOutPins);
+
+
+
+	PIN *pPin = new PIN;
+	memset(pPin, 0, sizeof(PIN));
+	pPin->pItem = pItem;
+	pPin->nIndex = nType == INPIN ? ((CPtrArray *)pItem->pInPins)->GetSize() : ((CPtrArray *)pItem->pOutPins)->GetSize();
+	pPin->nType = nType;
+	pPin->pData = dwData;
+
+	int len = MIN(sizeof(pPin->text), strName.GetLength());
+	memcpy(pPin->text, (LPTSTR)(LPCTSTR)strName, len);
+	pPin->text[len] = '\0';
+
+	CPtrArray *pArray = NULL;
+	if (nType == INPIN) {
+		pArray = (CPtrArray *)pItem->pInPins;
+	}
+	else {
+		pArray = (CPtrArray *)pItem->pOutPins;
+	}
+
+	ASSERT(pItem->pOutPins);
+	pArray->Add(pPin);
+
+	return pPin;
+}
+
+EQITEM* CEqsGraphWnd::GetItem(DWORD_PTR dwData)
+{
+	for (int i = 0; i < m_arItem.GetCount(); i++) {
+		EQITEM* pItem = (EQITEM*)m_arItem.GetAt(i);
+		if (pItem->pData == dwData) {
+			return pItem;
+		}
+	}
+
+	return NULL;
+}
+
+PIN * CEqsGraphWnd::GetPin(DWORD_PTR dwItemData, DWORD_PTR dwPinData)
+{
+	EQITEM* pItem = GetItem(dwItemData);
+	if (pItem != NULL) {
+		CPtrArray *pArray = (CPtrArray *)pItem->pInPins;
+		for (int i = 0; i < pArray->GetCount(); i++) {
+			PIN *pPin = (PIN *)pArray->GetAt(i);
+			if (pPin->pData == dwPinData) {
+				return pPin;
+			}
+		}
+
+		pArray = (CPtrArray *)pItem->pOutPins;
+		for (int i = 0; i < pArray->GetCount(); i++) {
+			PIN *pPin = (PIN *)pArray->GetAt(i);
+			if (pPin->pData == dwPinData) {
+				return pPin;
+			}
+		}
+	}
+
+	return NULL;
+}
+
+int CEqsGraphWnd::ConnectPin(DWORD_PTR dwItem1Data, DWORD_PTR dwPin1Data, DWORD_PTR dwItem2Data, DWORD_PTR dwPin2Data)
+{
+	PIN *pPin1, *pPin2;
+	
+	pPin1 = GetPin(dwItem1Data, dwPin1Data);
+	if (pPin1 == NULL) {
+		return -1;
+	}
+	
+	pPin2 = GetPin(dwItem2Data, dwPin2Data);
+	if (pPin2 == NULL) {
+		return -2;
+	}
+
+	pPin1->pConnectedPin = pPin2;
+	pPin2->pConnectedPin = pPin1;
+
+	return 0;
+}
+
+// 删除Item, 如果pin有连接,注意先断开
+int CEqsGraphWnd::DeleteItem(EQITEM* pItem)
+{
+	for (int i = 0; i < m_arItem.GetSize(); i++) {
+		if (pItem == (EQITEM*)m_arItem.GetAt(i)) {
+			m_arItem.RemoveAt(i);
+			ReleaseItem(pItem);
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+void CEqsGraphWnd::DeleteAllItems()
+{
+	ReleaseAllItems();
+
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+}
+
+void CEqsGraphWnd::SetCurrentItem(EQITEM* pItem)
+{
+	if (m_pCurItem != NULL) {
+		m_pCurItem->bHighlight = FALSE;
+	}
+
+	m_pCurItem = pItem;
+	if (m_pCurItem != NULL) {
+		m_pCurItem->bHighlight = TRUE;
+	}
+
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+}
+
+/*
+ * 设置子项的选中状态
+ */
+void CEqsGraphWnd::SetItemSelectState(int nIndex, BOOL bSelect)
+{
+	if (nIndex >= m_arItem.GetCount()) {
+		return;
+	}
+
+	EQITEM *pItem = (EQITEM*)m_arItem.GetAt(nIndex);
+	pItem->bHighlight = bSelect;
+
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+}
+
+void CEqsGraphWnd::Notify(int nCode, int dwData, int dwData1/* = 0*/, int dwData2/* = 0*/)
+{
+	HWND hParent;
+	hParent = GetParent(m_hWnd);
+	if (hParent != NULL) {
+		EQSGRAPHWND_NMHDR nmhdr;
+		nmhdr.nmhdr.hwndFrom = m_hWnd;
+		nmhdr.nmhdr.idFrom = GetWindowLong(m_hWnd, GWL_ID);
+		nmhdr.nmhdr.code = nCode;
+		nmhdr.dwData = dwData;
+		nmhdr.dwData1 = dwData1;
+		nmhdr.dwData2 = dwData2;
+		SendMessage(hParent, WM_NOTIFY, (WPARAM)nmhdr.nmhdr.idFrom, (LPARAM)&nmhdr);
+	}
+}
+
+/*
+ * 检测坐标点所在的项
+ * 返回项类型, 如HT_ITEM, HT_PIN, HT_LINE
+ * pItem - 所在的EQITEM
+ * pPin --所在的pin, 如果在连线上,表示所属pin, out pin;
+ */
+int CEqsGraphWnd::HighTest(POINT pt, OUT EQITEM*& pItem, OUT PIN *& pPin)
+{
+	// 检测是否在某个子项
+	int nRet = HT_NOWHERE;
+	pItem = NULL;
+	pPin = NULL;
+	RECT rcItem;
+	for (int i = m_arItem.GetSize() - 1; i >= 0 ; i--) {
+		EQITEM *pTempItem = (EQITEM*)m_arItem.GetAt(i);
+		GetItemRect(pTempItem, &rcItem);
+		if (::PtInRect(&rcItem, pt)) {
+			// 在Item
+			pItem = pTempItem;
+			nRet = HT_ITEM;
+			break;
+		}
+		else {
+			RECT rcPin;
+			CPtrArray * pPins = (CPtrArray *)pTempItem->pInPins;
+			for (int j = 0; j < pPins->GetSize(); j++) {
+				if (GetInPinRect(pTempItem, j, &rcPin) && ::PtInRect(&rcPin, pt)) {
+					// 在in pin上
+					pPin = (PIN *)pPins->GetAt(j);
+					pItem = pTempItem;
+					nRet = HT_PIN;
+					break;
+				}
+			}
+
+			if (nRet == HT_NOWHERE) {
+				pPins = (CPtrArray *)pTempItem->pOutPins;
+				for (int j = 0; j < pPins->GetSize(); j++) {
+					if (GetOutPinRect(pTempItem, j, &rcPin) && ::PtInRect(&rcPin, pt)) {
+						// 在out pin
+						pPin = (PIN *)pPins->GetAt(j);
+						pItem = pTempItem;
+						nRet = HT_PIN;
+						break;
+					}
+					else {
+						// 是否在pin连接线上,即判断点是否在线上
+						// 点到直线的距离公式(先通过p1,p2用两点式求出直线的表达式,再套距离公式);abs()为取绝对值函数,sqrt()为开根号函数
+						PIN *pTempPin = (PIN *)pPins->GetAt(j);
+						if (pTempPin->pConnectedPin != NULL && pTempPin->nLinePtCount > 1) {
+							for (int i = 0; i < pTempPin->nLinePtCount - 1; i++) {
+								double distance = PointToSegDist(pt.x + m_nOffsetX, pt.y + m_nOffsetY,
+									pTempPin->ptConnectedLine[i].x, pTempPin->ptConnectedLine[i].y,
+									pTempPin->ptConnectedLine[i+1].x, pTempPin->ptConnectedLine[i+1].y);
+								if (distance < 5.0) {
+									nRet = HT_LINE;
+									pPin = pTempPin;
+									break;
+								}
+							}
+
+							if (nRet == HT_LINE) {
+								break;
+							}
+						}
+					}
+				}
+			}
+
+			if (nRet != HT_NOWHERE) {
+				break;
+			}
+		}
+	}
+
+
+	return nRet;
+}
+
+/*
+ * 绘制虚线框,代表正在拖动的item
+ */
+void CEqsGraphWnd::DrawDropItemRectangle(LPRECT lpRect1, LPRECT lpRect2)
+{
+	HDC hDC = GetDC(m_hWnd);
+	HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
+	HPEN hPen = CreatePen(PS_DASH, 1, RGB(0, 0, 0));
+	int oldRop = SetROP2(hDC, R2_NOTXORPEN);
+	HBRUSH hOldBrush = (HBRUSH)::SelectObject(hDC, hBrush);
+	HBRUSH hOldPen = (HBRUSH)::SelectObject(hDC, hPen);
+
+	if (lpRect1 != NULL) {
+		::Rectangle(hDC, lpRect1->left, lpRect1->top, lpRect1->right, lpRect1->bottom);
+	}
+
+	if (lpRect2 != NULL) {
+		::Rectangle(hDC, lpRect2->left, lpRect2->top, lpRect2->right, lpRect2->bottom);
+	}
+
+	::SetROP2(hDC, oldRop);
+	::SelectObject(hDC, hOldBrush);
+	::SelectObject(hDC, hPen);
+	::DeleteObject(hBrush);
+	::DeleteObject(hOldPen);
+	::ReleaseDC(m_hWnd, hDC);
+}
+
+/*
+ * 绘制磁吸线
+ */
+void CEqsGraphWnd::DrawMagneticLine(LPRECT lprcClient, int nHozLine1, int nHozLine2, int nVerLine1, int nVerLine2)
+{
+	HDC hDC = GetDC(m_hWnd);
+	HPEN hPen = CreatePen(PS_DASH, 1, RGB(64, 64, 64));
+	int oldRop = SetROP2(hDC, R2_NOTXORPEN);
+	HBRUSH hOldPen = (HBRUSH)::SelectObject(hDC, hPen);
+
+	if (nHozLine1) {
+		::MoveToEx(hDC, 1, nHozLine1, NULL);
+		::LineTo(hDC, lprcClient->right-1, nHozLine1);
+	}
+	if (nHozLine2) {
+		::MoveToEx(hDC, 1, nHozLine2, NULL);
+		::LineTo(hDC, lprcClient->right - 1, nHozLine2);
+	}
+
+	if (nVerLine1) {
+		::MoveToEx(hDC, nVerLine1, 1, NULL);
+		::LineTo(hDC, nVerLine1, lprcClient->bottom - 1);
+	}
+	if (nVerLine2) {
+		::MoveToEx(hDC, nVerLine2, 1, NULL);
+		::LineTo(hDC, nVerLine2, lprcClient->bottom - 1);
+	}
+
+	::SetROP2(hDC, oldRop);
+	::SelectObject(hDC, hPen);
+	::DeleteObject(hOldPen);
+	::ReleaseDC(m_hWnd, hDC);
+}
+
+/*
+ * 缓制Pin连接线
+ * pBrush -- 画刷
+ * pPen - 画笔
+ * lpPt1, lpPt2 -- Pin脚的位置
+ * lpRect1, lpRect2 -- 两个Item的Rect
+ */
+void CEqsGraphWnd::DrawPinConnectedLine(Gdiplus::Graphics *pGraphics, Gdiplus::Brush *pBrush, Gdiplus::Pen *pPen, LPPOINT lpPt1, LPPOINT lpPt2,
+	LPRECT lpRect1, LPRECT lpRect2, PIN *pOwnerPin)
+{
+	// 如果没有缓存线条的POINT,则先计算并缓存
+	ASSERT(pOwnerPin);
+
+	int nPinCount = ((CPtrArray*)pOwnerPin->pItem->pOutPins)->GetSize();
+	int nArrowLen = 8;
+	int nStartMinX = 8;
+	int nMargin = 12;
+	int x1, x2, y1;
+
+	if (pOwnerPin->nLinePtCount == 0) {						// 第一个点的最小折线长
+		::OffsetRect(lpRect1, +m_nOffsetX, +m_nOffsetY);
+		::OffsetRect(lpRect2, +m_nOffsetX, +m_nOffsetY);
+		lpPt1->x += m_nOffsetX;				// 消除偏移
+		lpPt1->y += m_nOffsetY;
+		lpPt2->x += m_nOffsetX;
+		lpPt2->y += m_nOffsetY;
+		int nMinX = 10 + nMargin * nPinCount + nArrowLen;
+		int xEnd = lpPt2->x - 5;
+		x1 = lpPt1->x + 10 + pOwnerPin->nIndex * nMargin;
+		if (lpPt2->x - lpPt1->x > nMinX) {
+			pOwnerPin->ptConnectedLine[0].x = lpPt1->x;
+			pOwnerPin->ptConnectedLine[0].y = lpPt1->y;
+			pOwnerPin->ptConnectedLine[1].x = x1;
+			pOwnerPin->ptConnectedLine[1].y = lpPt1->y;
+			pOwnerPin->ptConnectedLine[2].x = x1;
+			pOwnerPin->ptConnectedLine[2].y = lpPt2->y;
+			pOwnerPin->ptConnectedLine[3].x = xEnd;
+			pOwnerPin->ptConnectedLine[3].y = lpPt2->y;
+			pOwnerPin->nLinePtCount = 4;
+		}
+		else if (lpRect1 != NULL && lpRect2 != NULL) {
+			if (lpRect2->top - lpRect1->bottom > 20 || lpRect1->top - lpRect2->bottom > 20) {
+				if (lpRect2->top - lpRect1->bottom > 20) {
+					y1 = lpRect1->bottom + 10 + pOwnerPin->nIndex * nMargin;
+					x2 = min(lpPt2->x - nArrowLen, x1) - (nPinCount - pOwnerPin->nIndex) * nMargin;
+				}
+				else {
+					y1 = lpRect1->top - 10 - pOwnerPin->nIndex * nMargin;
+					x2 = min(lpPt2->x - nArrowLen, x1) - (nPinCount - pOwnerPin->nIndex) * nMargin;
+				}
+				pOwnerPin->ptConnectedLine[0].x = lpPt1->x;
+				pOwnerPin->ptConnectedLine[0].y = lpPt1->y;
+				pOwnerPin->ptConnectedLine[1].x = x1;
+				pOwnerPin->ptConnectedLine[1].y = lpPt1->y;
+				pOwnerPin->ptConnectedLine[2].x = x1;
+				pOwnerPin->ptConnectedLine[2].y = y1;
+				pOwnerPin->ptConnectedLine[3].x = x2;
+				pOwnerPin->ptConnectedLine[3].y = y1;
+				pOwnerPin->ptConnectedLine[4].x = x2;
+				pOwnerPin->ptConnectedLine[4].y = lpPt2->y;
+				pOwnerPin->ptConnectedLine[5].x = xEnd;
+				pOwnerPin->ptConnectedLine[5].y = lpPt2->y;
+				pOwnerPin->nLinePtCount = 6;
+			}
+			else {
+				x2 = min(lpRect1->left, lpRect2->left) - 30;
+				y1 = max(lpRect1->bottom, lpRect2->bottom) + 30;
+				pOwnerPin->ptConnectedLine[0].x = lpPt1->x;
+				pOwnerPin->ptConnectedLine[0].y = lpPt1->y;
+				pOwnerPin->ptConnectedLine[1].x = x1;
+				pOwnerPin->ptConnectedLine[1].y = lpPt1->y;
+				pOwnerPin->ptConnectedLine[2].x = x1;
+				pOwnerPin->ptConnectedLine[2].y = y1;
+				pOwnerPin->ptConnectedLine[3].x = x2;
+				pOwnerPin->ptConnectedLine[3].y = y1;
+				pOwnerPin->ptConnectedLine[4].x = x2;
+				pOwnerPin->ptConnectedLine[4].y = lpPt2->y;
+				pOwnerPin->ptConnectedLine[5].x = xEnd;
+				pOwnerPin->ptConnectedLine[5].y = lpPt2->y;
+				pOwnerPin->nLinePtCount = 6;
+			}
+		}
+	}
+
+	if (pOwnerPin->nLinePtCount >= 2) {
+		for (int i = 0; i < pOwnerPin->nLinePtCount - 1; i++) {
+			pGraphics->DrawLine(pPen, pOwnerPin->ptConnectedLine[i].x - m_nOffsetX, pOwnerPin->ptConnectedLine[i].y - m_nOffsetY,
+				pOwnerPin->ptConnectedLine[i + 1].x - m_nOffsetX, pOwnerPin->ptConnectedLine[i + 1].y - m_nOffsetY);
+		}
+
+		DrawArrow(pGraphics, pBrush, pPen, pOwnerPin->ptConnectedLine[pOwnerPin->nLinePtCount-1].x - m_nOffsetX,
+			pOwnerPin->ptConnectedLine[pOwnerPin->nLinePtCount-1].y - m_nOffsetY, nArrowLen);
+	}
+}
+
+void CEqsGraphWnd::DrawArrow(Gdiplus::Graphics *pGraphics, Gdiplus::Brush* pBrush, Gdiplus::Pen *pPen,
+	int x, int y, int nArrowLen)
+{
+	Gdiplus::Point pt[4];
+	pt[0].X = x;
+	pt[0].Y = y;
+	pt[1].X = x - nArrowLen;
+	pt[1].Y = y - 3;
+	pt[2].X = pt[1].X;
+	pt[2].Y = y + 3;
+	pt[3].X = x;
+	pt[3].Y = y;
+	pGraphics->FillPolygon(pBrush, pt, 4);
+	pGraphics->DrawPolygon(pPen, pt, 4);
+}
+
+void CEqsGraphWnd::DrawPinWillConnectLine(COLORREF color, LPPOINT lpPt1, LPPOINT lpPt2)
+{
+	HDC hDC = GetDC(m_hWnd);
+
+	HPEN hPen = CreatePen(PS_SOLID, 2, color);
+	int oldRop = SetROP2(hDC, R2_NOTXORPEN);
+	HBRUSH hOldPen = (HBRUSH)::SelectObject(hDC, hPen);
+
+	if (lpPt1 != NULL && lpPt2 != NULL) {
+		::MoveToEx(hDC, lpPt1->x, lpPt1->y, NULL);
+		::LineTo(hDC, lpPt2->x, lpPt2->y);
+	}
+
+	::SetROP2(hDC, oldRop);
+	::SelectObject(hDC, hPen);
+	::DeleteObject(hOldPen);
+	::ReleaseDC(m_hWnd, hDC);
+}
+
+/*
+ * WindowProc,窗口过程
+ */
+LRESULT CALLBACK CEqsGraphWnd::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+	CEqsGraphWnd* pEqsGraphWnd = (CEqsGraphWnd*)GetProp(hWnd, EQSGRAPHWND_TAG);
+	if (pEqsGraphWnd == NULL && uMsg != WM_NCCREATE)
+	{
+		return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
+	}
+
+
+	// 处理窗口消息
+	ASSERT(hWnd);
+	switch (uMsg)
+	{
+	case WM_NCCREATE:
+		return CEqsGraphWnd::OnNcCreate(hWnd, wParam, lParam);
+
+	case WM_DESTROY:
+		return pEqsGraphWnd->OnDestroy(wParam, lParam);
+
+	case WM_NCPAINT:
+		return pEqsGraphWnd->OnNcPaint(wParam, lParam);
+
+	case WM_PAINT:
+		return pEqsGraphWnd->OnPaint(wParam, lParam);
+
+	case WM_TIMER:
+		return pEqsGraphWnd->OnTimer(wParam, lParam);
+
+	case WM_MOUSEMOVE:
+		return pEqsGraphWnd->OnMouseMove(wParam, lParam);
+
+	case WM_LBUTTONDOWN:
+		return pEqsGraphWnd->OnLButtonDown(wParam, lParam);
+
+	case WM_RBUTTONDOWN:
+		return pEqsGraphWnd->OnRButtonDown(wParam, lParam);
+
+	case WM_LBUTTONDBLCLK:
+		return pEqsGraphWnd->OnLButtonDblclk(wParam, lParam);
+
+	case WM_MOUSEWHEEL:
+		return pEqsGraphWnd->OnMouseWheel(wParam, lParam);
+
+	case WM_MOUSEHWHEEL:
+		return pEqsGraphWnd->OnMouseHWheel(wParam, lParam);
+
+	case WM_KEYDOWN:
+		return pEqsGraphWnd->OnKeyDown(wParam, lParam);
+
+	case WM_SIZE:
+		return pEqsGraphWnd->OnSize(wParam, lParam);
+
+	case WM_VSCROLL:
+		return pEqsGraphWnd->OnVScroll(wParam, lParam);
+
+	case WM_HSCROLL:
+		return pEqsGraphWnd->OnHScroll(wParam, lParam);
+
+	case WM_NOTIFY:
+		return pEqsGraphWnd->OnNitify(wParam, lParam);
+
+	case WM_GETDLGCODE:
+		return DLGC_WANTALLKEYS;
+
+	default:
+		break;
+	}
+
+	return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
+}
+
+/*
+ * WM_NCCREATE
+ * 窗口创建
+ */
+LRESULT CEqsGraphWnd::OnNcCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
+{
+	CEqsGraphWnd* pEqsGraphWnd = (CEqsGraphWnd*)GetProp(hWnd,EQSGRAPHWND_TAG);
+	ASSERT(pEqsGraphWnd == NULL);
+
+	Hook(hWnd)->Init();
+	return ::DefWindowProc(hWnd, WM_NCCREATE, wParam, lParam);
+}
+
+/*
+ * WM_DESTROY
+ * 窗口销毁
+ */
+LRESULT CEqsGraphWnd::OnDestroy(WPARAM wParam, LPARAM lParam)
+{
+	Release();
+	return ::DefWindowProc(m_hWnd, WM_DESTROY, wParam, lParam);
+}
+
+
+/*
+ * WM_TIMER
+ */
+LRESULT CEqsGraphWnd::OnTimer(WPARAM wParam, LPARAM lParam)
+{
+	if (wParam == TIMER_FLASH) {
+		if (m_pFlashItem != NULL && m_nFlashCount > 0) {
+			m_nFlashCount--;
+			m_pFlashItem->nFlashFlag = (m_nFlashCount % 2);
+
+			RECT rcItem;
+			GetItemWarperRect(m_pFlashItem, &rcItem);
+			InvalidateRect(m_hWnd, &rcItem, TRUE);
+		}
+		else {
+			m_pFlashItem = NULL;
+			m_nFlashCount = 0;
+		}
+	}
+	else if (TIMER_ANIMATION_RECT == wParam) {
+		if (m_pAnimationItem != NULL) {
+			if (m_nAninationStep > 0) {
+				m_nAninationStep--;
+
+				m_rcAnimation.left += m_rcAninationStep.left;
+				m_rcAnimation.right += m_rcAninationStep.right;
+				m_rcAnimation.top += m_rcAninationStep.top;
+				m_rcAnimation.bottom += m_rcAninationStep.bottom;
+
+				RECT rcItem;
+				GetItemWarperRect(m_pAnimationItem, &rcItem);
+				InvalidateRect(m_hWnd, &rcItem, TRUE);
+			}
+			else {
+				KillTimer(m_hWnd, TIMER_ANIMATION_RECT);
+				RECT rcItem;
+				GetItemWarperRect(m_pAnimationItem, &rcItem);
+				InvalidateRect(m_hWnd, &rcItem, TRUE);
+				m_pAnimationItem = NULL;
+			}
+		}
+		else {
+			KillTimer(m_hWnd, TIMER_ANIMATION_RECT);
+			RECT rcClient;
+			GetClientRect(m_hWnd, &rcClient);
+			InvalidateRect(m_hWnd, &rcClient, TRUE);
+		}
+	}
+
+	return ::DefWindowProc(m_hWnd, WM_TIMER, wParam, lParam);
+}
+
+/*
+ * WM_MOUSEMOVE
+ * 鼠标滚动
+ */
+LRESULT CEqsGraphWnd::OnMouseMove(WPARAM wParam, LPARAM lParam)
+{
+	return ::DefWindowProc(m_hWnd, WM_MOUSEMOVE, wParam, lParam);
+}
+
+/*
+ * WM_LBUTTONDOWN
+ * 鼠标左键按下
+ */
+LRESULT CEqsGraphWnd::OnLButtonDown(WPARAM wParam, LPARAM lParam)
+{
+	POINT pt, ptNew;
+	pt.x = LOWORD(lParam);
+	pt.y = HIWORD(lParam);
+
+	RECT rcClient, rcItem, rcNewItem, rcLast;
+	GetClientRect(m_hWnd, &rcClient);
+	rcLast = {0, 0, 0, 0};
+	int nMaxOffsetX = m_nStageCx - (rcClient.right - rcClient.left);
+	int nMaxOffsetY = m_nStageCy - (rcClient.bottom - rcClient.top);
+	int nLastHozLine = 0;
+	int nLastVerLine = 0;
+
+
+	// 检测点击坐标是否在某一子项上,如是,则高亮显示
+	EQITEM* pLastItem = m_pCurItem;
+	PIN *pLastPin = m_pCurPin;
+	PIN *pLastSelLineOutPin = m_pSelLineOutPin;
+	BOOL bChanged = FALSE;
+	EQITEM* pHitItem = NULL;
+	PIN *pHitPin = NULL;
+	PIN *pPin2 = NULL;
+	int nRet = HighTest(pt, pHitItem, pHitPin);
+	if (pHitItem != m_pCurItem || nRet != HT_ITEM) {
+		if (m_pCurItem != NULL) {
+			m_pCurItem->bHighlight = FALSE;
+		}
+		m_pCurItem = NULL;
+	}
+
+	if (pHitPin != m_pCurPin || nRet != HT_PIN) {
+		if (m_pCurPin != NULL) {
+			m_pCurPin->bHighlight = FALSE;
+		}
+		m_pCurPin = NULL;
+	}
+
+	if (pHitPin != m_pSelLineOutPin || nRet != HT_LINE) {
+		m_pSelLineOutPin = NULL;
+	}
+
+	if (nRet == HT_ITEM) {
+		m_pCurItem = pHitItem;
+		m_pCurItem->bHighlight = TRUE;
+	}
+	else if (nRet == HT_PIN) {
+		m_pCurPin = pHitPin;
+		m_pCurPin->bHighlight = TRUE;
+	}
+	else if (nRet == HT_LINE) {
+		m_pSelLineOutPin = pHitPin;
+	}
+
+
+	bChanged = pLastItem != m_pCurItem || pLastPin != m_pCurPin || pLastSelLineOutPin != m_pSelLineOutPin;
+
+
+	// 刷新
+	SetFocus(m_hWnd);
+	if (bChanged) {
+		::InvalidateRect(m_hWnd, &rcClient, TRUE);
+	}
+
+
+	// 捕捉鼠标消息,检测是否拖动
+	if (nRet == HT_ITEM && m_pCurItem != NULL) {
+		GetItemRect(m_pCurItem, &rcItem);
+
+		if (::GetCapture() == NULL) {
+			SetCapture(m_hWnd);
+			ASSERT(m_hWnd == GetCapture());
+			AfxLockTempMaps();
+			for (;;)
+			{
+				MSG msg;
+				VERIFY(::GetMessage(&msg, NULL, 0, 0));
+
+				if (GetCapture() != m_hWnd) break;
+
+				switch (msg.message)
+				{
+				case WM_MOUSEMOVE:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+					rcNewItem.left = rcItem.left + (ptNew.x - pt.x);
+					rcNewItem.right = rcItem.right + (ptNew.x - pt.x);
+					rcNewItem.top = rcItem.top + (ptNew.y - pt.y);
+					rcNewItem.bottom = rcItem.bottom + (ptNew.y - pt.y);
+					CalculateMagneticLine(m_pCurItem, &rcNewItem, m_nMagneticLinHoz, m_nMagneticLinVer);
+					DrawDropItemRectangle(&rcNewItem, &rcLast);
+					DrawMagneticLine(&rcClient, m_nMagneticLinHoz, nLastHozLine, m_nMagneticLinVer, nLastVerLine);
+					nLastHozLine = m_nMagneticLinHoz;
+					nLastVerLine = m_nMagneticLinVer;
+					CopyRect(&rcLast, &rcNewItem);
+					break;
+
+				case WM_LBUTTONUP:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+					m_pCurItem->rect.left = m_nMagneticLinVer > 0 ? m_nMagneticLinVer : (rcItem.left + (ptNew.x - pt.x) + m_nOffsetX);
+					m_pCurItem->rect.right = m_pCurItem->rect.left + (rcItem.right - rcItem.left);
+					m_pCurItem->rect.top = m_nMagneticLinHoz > 0 ? m_nMagneticLinHoz : (rcItem.top + (ptNew.y - pt.y) + m_nOffsetY);
+					m_pCurItem->rect.bottom = m_pCurItem->rect.top + (rcItem.bottom - rcItem.top);
+					if (m_pCurItem->rect.left != rcItem.left || m_pCurItem->rect.top != rcItem.top) {
+						if (m_listener.onEqItemPosChanged != nullptr) {
+							m_listener.onEqItemPosChanged(m_pCurItem, m_pCurItem->rect.left, m_pCurItem->rect.top);
+						}
+					}
+					DrawDropItemRectangle(NULL, &rcLast);
+					ReleaseCapture();
+
+					ClearConnectedLinePoint(m_pCurItem);
+					::InvalidateRect(m_hWnd, &rcClient, TRUE);
+					goto ExitLoop;
+
+				case WM_KEYDOWN:
+					if (msg.wParam != VK_ESCAPE)
+						break;
+
+				default:
+					DispatchMessage(&msg);
+					break;
+				}
+			}
+
+			ReleaseCapture();
+		ExitLoop:
+			AfxUnlockTempMaps(FALSE);
+		}
+	}
+
+
+	// 捕捉鼠标消息,检测是否连接引脚
+	else if (nRet == HT_PIN && m_pCurPin != NULL) {
+		if (::GetCapture() == NULL) {
+			BOOL bLast = FALSE;
+			bool bCanConnect;
+			POINT ptPin, ptLast;
+			COLORREF lineColor;
+			GetPinPoint(m_pCurPin, &ptPin);
+			ptLast.x = ptPin.x;
+			ptLast.y = ptPin.y;
+
+			SetCapture(m_hWnd);
+			ASSERT(m_hWnd == GetCapture());
+			AfxLockTempMaps();
+			for (;;)
+			{
+				MSG msg;
+				VERIFY(::GetMessage(&msg, NULL, 0, 0));
+
+				if (GetCapture() != m_hWnd) break;
+
+				switch (msg.message)
+				{
+				case WM_MOUSEMOVE:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+
+					// 擦除上一次
+					if (bLast) {
+						DrawPinWillConnectLine(lineColor, &ptPin, &ptLast);
+					}
+
+					// 检测是否可以连接
+					bCanConnect = false;
+					nRet = HighTest(ptNew, pHitItem, pHitPin);
+					if (nRet == HT_PIN) {
+						if (m_listener.onCheckConnectPin != nullptr) {
+							bCanConnect = m_listener.onCheckConnectPin(m_pCurPin, pHitPin);
+						}
+					}
+					if (bCanConnect) {
+						lineColor = RGB(0, 255, 0);
+						DrawPinWillConnectLine(lineColor, &ptPin, &ptNew);
+					}
+					else {
+						lineColor = RGB(0, 0, 0);
+						DrawPinWillConnectLine(lineColor, &ptPin, &ptNew);
+					}
+
+					ptLast.x = ptNew.x;
+					ptLast.y = ptNew.y;
+					bLast = TRUE;
+					break;
+
+				case WM_LBUTTONUP:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+
+					// 擦除上一次
+					if (bLast) {
+						DrawPinWillConnectLine(lineColor, &ptPin, &ptLast);
+					}
+
+					// 检测是否可以连接
+					bCanConnect = false;
+					nRet = HighTest(ptNew, pHitItem, pHitPin);
+					if (nRet == HT_PIN) {
+						if (m_listener.onConnectPin != nullptr) {
+							bCanConnect = m_listener.onConnectPin(m_pCurPin, pHitPin);
+						}
+					}
+					if (bCanConnect) {
+						m_pCurPin->pConnectedPin = pHitPin;
+						pHitPin->pConnectedPin = m_pCurPin;
+					}
+					bLast = FALSE;
+
+					ReleaseCapture();
+					::InvalidateRect(m_hWnd, &rcClient, TRUE);
+					goto ExitLoop2;
+
+				case WM_KEYDOWN:
+					if (msg.wParam != VK_ESCAPE)
+						break;
+
+				default:
+					DispatchMessage(&msg);
+					break;
+				}
+			}
+
+			ReleaseCapture();
+		ExitLoop2:
+			AfxUnlockTempMaps(FALSE);
+		}
+	}
+
+	
+	// 检测鼠标消息,检测是否移动画布
+	else if (nRet == HT_NOWHERE) {
+		if (::GetCapture() == NULL) {
+			int nLastOffsetX = m_nOffsetX;
+			int nLastOffsetY = m_nOffsetY;
+
+			POINT ptStart;
+			ptStart.x = pt.x;
+			ptStart.y = pt.y;
+
+			SetCursor(LoadCursor(NULL, IDC_SIZEALL));
+			SetCapture(m_hWnd);
+			ASSERT(m_hWnd == GetCapture());
+			AfxLockTempMaps();
+			for (;;) {
+				MSG msg;
+				VERIFY(::GetMessage(&msg, NULL, 0, 0));
+
+				if (GetCapture() != m_hWnd) break;
+
+				switch (msg.message)
+				{
+				case WM_MOUSEMOVE:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+					m_nOffsetX = min(nMaxOffsetX, max(0, nLastOffsetX - (ptNew.x - ptStart.x)));
+					m_nOffsetY = min(nMaxOffsetY, max(0, nLastOffsetY - (ptNew.y - ptStart.y)));
+					CalculateScollbar();
+					CalculateMapPos();
+					::InvalidateRect(m_hWnd, &rcClient, TRUE);
+					break;
+
+				case WM_LBUTTONUP:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+
+					ReleaseCapture();
+					CalculateScollbar();
+					CalculateMapPos();
+					::InvalidateRect(m_hWnd, &rcClient, TRUE);
+					goto ExitLoop3;
+
+				case WM_KEYDOWN:
+					if (msg.wParam != VK_ESCAPE)
+						break;
+
+				default:
+					DispatchMessage(&msg);
+					break;
+				}
+			}
+
+			ReleaseCapture();
+		ExitLoop3:
+			AfxUnlockTempMaps(FALSE);
+		}
+	}
+
+
+	return ::DefWindowProc(m_hWnd, WM_LBUTTONDOWN, wParam, lParam);
+}
+
+/*
+ * WM_LBUTTONDBLCLK
+ * 鼠标左键双击
+ */
+LRESULT CEqsGraphWnd::OnLButtonDblclk(WPARAM wParam, LPARAM lParam)
+{
+	POINT pt;
+	pt.x = LOWORD(lParam);
+	pt.y = HIWORD(lParam);
+
+	RECT rcClient, rcLast;
+	GetClientRect(m_hWnd, &rcClient);
+	rcLast = { 0, 0, 0, 0 };
+
+	// 检测点击坐标是否在某一子项上,如是,则高亮显示
+	EQITEM* pLastItem = m_pCurItem;
+	BOOL bChanged = FALSE;
+	EQITEM* pHitItem = NULL;
+	PIN *pHitPin = NULL;
+	int nRet = HighTest(pt, pHitItem, pHitPin);
+	if ( nRet == HT_ITEM) {
+		m_pCurItem = pHitItem;
+		m_pCurItem->bHighlight = FALSE;
+		if (m_listener.onDblckEqItem != nullptr) {
+			m_listener.onDblckEqItem(pHitItem);
+		}
+	}
+
+
+	return ::DefWindowProc(m_hWnd, WM_LBUTTONDBLCLK, wParam, lParam);
+}
+
+/*
+ * WM_MOUSEWHEEL
+ * 鼠标滚动
+ */
+LRESULT CEqsGraphWnd::OnMouseWheel(WPARAM wParam, LPARAM lParam)
+{
+	short zDelta;
+	UINT nFlags;
+	CPoint pt;
+
+	nFlags = LOWORD(wParam);
+	zDelta = (short)HIWORD(wParam);
+	pt.x = (short)LOWORD(lParam);
+	pt.y = (short)HIWORD(lParam);
+
+
+	CRect rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	m_nOffsetY -= zDelta;
+	m_nOffsetY = max(0, min(m_nOffsetY, m_nStageCy - rcClient.Height()));
+	CalculateScollbar();
+	CalculateMapPos();
+
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+	return ::DefWindowProc(m_hWnd, WM_MOUSEWHEEL, wParam, lParam);
+}
+
+/*
+* WM_MOUSEHWHEEL
+* 鼠标滚动
+*/
+LRESULT CEqsGraphWnd::OnMouseHWheel(WPARAM wParam, LPARAM lParam)
+{
+	short zDelta;
+	UINT nFlags;
+	CPoint pt;
+
+	nFlags = LOWORD(wParam);
+	zDelta = (short)HIWORD(wParam);
+	pt.x = (short)LOWORD(lParam);
+	pt.y = (short)HIWORD(lParam);
+
+
+	CRect rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	m_nOffsetX += zDelta;
+	m_nOffsetX = max(0, min(m_nOffsetX, m_nStageCx - rcClient.Width()));
+	CalculateScollbar();
+	CalculateMapPos();
+
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+	return ::DefWindowProc(m_hWnd, WM_MOUSEHWHEEL, wParam, lParam);
+}
+
+/*
+ * WM_RBUTTONDOWN
+ * 鼠标左键按下
+ */
+LRESULT CEqsGraphWnd::OnRButtonDown(WPARAM wParam, LPARAM lParam)
+{
+	POINT pt, ptNew;
+	pt.x = LOWORD(lParam);
+	pt.y = HIWORD(lParam);
+
+	RECT rcClient, rcItem, rcLast;
+	GetClientRect(m_hWnd, &rcClient);
+	rcLast = { 0, 0, 0, 0 };
+
+	// 检测点击坐标是否在某一子项上,如是,则高亮显示
+	EQITEM* pLastItem = m_pCurItem;
+	PIN *pLastPin = m_pCurPin;
+	PIN *pLastSelLineOutPin = m_pSelLineOutPin;
+	BOOL bChanged = FALSE;
+	EQITEM* pHitItem = NULL;
+	PIN *pHitPin = NULL;
+	PIN *pPin2 = NULL;
+	int nRet = HighTest(pt, pHitItem, pHitPin);
+	if (pHitItem != m_pCurItem || nRet != HT_ITEM) {
+		if (m_pCurItem != NULL) {
+			m_pCurItem->bHighlight = FALSE;
+		}
+		m_pCurItem = NULL;
+	}
+
+	if (pHitPin != m_pCurPin || nRet != HT_PIN) {
+		if (m_pCurPin != NULL) {
+			m_pCurPin->bHighlight = FALSE;
+		}
+		m_pCurPin = NULL;
+	}
+
+	if (pHitPin != m_pSelLineOutPin || nRet != HT_LINE) {
+		m_pSelLineOutPin = NULL;
+	}
+
+	if (nRet == HT_ITEM) {
+		m_pCurItem = pHitItem;
+		m_pCurItem->bHighlight = TRUE;
+	}
+	else if (nRet == HT_PIN) {
+		m_pCurPin = pHitPin;
+		m_pCurPin->bHighlight = TRUE;
+	}
+	else if (nRet == HT_LINE) {
+		m_pSelLineOutPin = pHitPin;
+	}
+
+
+	bChanged = pLastItem != m_pCurItem || pLastPin != m_pCurPin || pLastSelLineOutPin != m_pSelLineOutPin;
+
+
+	// 刷新
+	SetFocus(m_hWnd);
+	if (bChanged) {
+		::InvalidateRect(m_hWnd, &rcClient, TRUE);
+	}
+
+
+	// 捕捉鼠标消息,检测是否拖动
+	if (nRet == HT_ITEM && m_pCurItem != NULL) {
+		CopyRect(&rcItem, &m_pCurItem->rect);
+
+		if (::GetCapture() == NULL) {
+			SetCapture(m_hWnd);
+			ASSERT(m_hWnd == GetCapture());
+			AfxLockTempMaps();
+			for (;;)
+			{
+				MSG msg;
+				VERIFY(::GetMessage(&msg, NULL, 0, 0));
+
+				if (GetCapture() != m_hWnd) break;
+
+				switch (msg.message)
+				{
+				case WM_MOUSEMOVE:
+					break;
+
+				case WM_RBUTTONUP:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+					nRet = HighTest(ptNew, pHitItem, pHitPin);
+					ReleaseCapture();
+					if (m_listener.onRclickEqItem != NULL) {
+						m_listener.onRclickEqItem(pHitItem);
+					}
+					::InvalidateRect(m_hWnd, &rcClient, TRUE);
+					goto ExitLoop;
+
+				case WM_KEYDOWN:
+					if (msg.wParam != VK_ESCAPE)
+						break;
+
+				default:
+					DispatchMessage(&msg);
+					break;
+				}
+			}
+
+			ReleaseCapture();
+		ExitLoop:
+			AfxUnlockTempMaps(FALSE);
+		}
+	}
+
+
+	return ::DefWindowProc(m_hWnd, WM_LBUTTONDOWN, wParam, lParam);
+}
+
+/*
+ * WM_KEYDOWN
+ * 键盘消息,按下按键
+ */
+LRESULT CEqsGraphWnd::OnKeyDown(WPARAM wParam, LPARAM lParam)
+{
+	BOOL bChanged = FALSE;
+	if (wParam == VK_DELETE) {
+		// 如果当前选择为线,则断开连接
+		if (m_pSelLineOutPin != NULL) {
+			if (m_listener.onDisconnectPin != nullptr) {
+				if (m_listener.onDisconnectPin(m_pSelLineOutPin)) {
+					m_pSelLineOutPin->pConnectedPin->pConnectedPin = NULL;
+					m_pSelLineOutPin->pConnectedPin = NULL;
+					m_pSelLineOutPin = NULL;
+					bChanged = TRUE;
+				}
+			}
+		}
+		else if (m_pCurItem != NULL) {
+			if (m_listener.onDeleteEqItem != NULL) {
+				if (m_listener.onDeleteEqItem(m_pCurItem)) {
+					bChanged = DeleteItem(m_pCurItem) >= 0;
+				}
+			}
+		}
+	}
+
+
+	if (bChanged) {
+		RECT rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		::InvalidateRect(m_hWnd, &rcClient, TRUE);
+	}
+
+	return ::DefWindowProc(m_hWnd, WM_KEYDOWN, wParam, lParam);
+}
+
+/*
+ * WM_NCPAINT
+ */
+LRESULT CEqsGraphWnd::OnNcPaint(WPARAM wParam, LPARAM lParam)
+{
+	LRESULT lRet = ::DefWindowProc(m_hWnd, WM_NCPAINT, wParam, lParam);
+
+
+	long styleEx = GetWindowLong(m_hWnd, GWL_EXSTYLE);
+	if ((styleEx & WS_EX_CLIENTEDGE) == WS_EX_CLIENTEDGE) {
+
+		RECT rect, rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		::ClientToScreen(m_hWnd, (LPPOINT)&rcClient.left);
+		::ClientToScreen(m_hWnd, (LPPOINT)&rcClient.right);
+		GetWindowRect(m_hWnd, &rect);
+		rcClient.right = rect.right - 1;
+		rcClient.bottom = rect.bottom - 1;
+		::OffsetRect(&rcClient, -rect.left, -rect.top);
+
+		rect.right -= rect.left;
+		rect.bottom -= rect.top;
+		rect.left = 0;
+		rect.top = 0;
+
+		HRGN hRgnWnd = CreateRectRgnIndirect(&rect);
+		HRGN hRgnClient = CreateRectRgnIndirect(&rcClient);
+
+		HBRUSH hBrushBK, hBrushFrame;
+		HDC hDC = ::GetWindowDC(m_hWnd);
+		::SelectClipRgn(hDC, hRgnWnd);
+		::ExtSelectClipRgn(hDC, hRgnClient, RGN_DIFF);
+
+		hBrushBK = CreateSolidBrush(m_crBkgnd);
+		::FillRect(hDC, &rect, hBrushBK);
+		DeleteObject(hBrushBK);
+
+		hBrushFrame = CreateSolidBrush(m_crFrame);
+		::FrameRect(hDC, &rect, hBrushFrame);
+
+		::DeleteObject(hRgnWnd);
+		::DeleteObject(hRgnClient);
+		DeleteObject(hBrushFrame);
+		::ReleaseDC(m_hWnd, hDC);
+	}
+
+	return lRet;
+}
+
+/*
+ * WM_PAINT
+ */
+LRESULT CEqsGraphWnd::OnPaint(WPARAM wParam, LPARAM lParam)
+{
+	HDC hDC, hMemDC;
+	HBITMAP hBitmap;
+	RECT rcClient;
+	CString strText;
+	HBRUSH hBrushBK;
+
+
+	// BeginPaint
+	PAINTSTRUCT ps;
+	hDC = BeginPaint(m_hWnd, &ps);
+	GetClientRect(m_hWnd, &rcClient);
+
+	hMemDC = ::CreateCompatibleDC(hDC);
+	hBitmap = ::CreateCompatibleBitmap(hDC, rcClient.right - rcClient.left,
+		rcClient.bottom - rcClient.top);
+	::SelectObject(hMemDC, hBitmap);
+
+
+	// 背景颜色
+	hBrushBK = CreateSolidBrush(m_crBkgnd);
+	::FillRect(hMemDC, &rcClient, hBrushBK);
+	DeleteObject(hBrushBK);
+
+
+	// 标题
+	if (m_hFontTitle == nullptr) {
+		LOGFONT lf;
+		HFONT hFontDefault = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+		::GetObject(hFontDefault, sizeof(LOGFONT), &lf);
+		lf.lfHeight -= 6;
+		lf.lfWeight = FW_SEMIBOLD;
+		m_hFontTitle = CreateFontIndirect(&lf);
+	}
+
+	{
+		char szTitle[256];
+		GetWindowText(m_hWnd, szTitle, 256);
+		RECT rcTitle;
+		rcTitle.left = rcClient.left + 5;
+		rcTitle.top = rcClient.top + 12;
+		rcTitle.bottom = rcClient.bottom - 5;
+		rcTitle.right = rcClient.right - 5;
+		::SelectObject(hMemDC, m_hFontTitle);
+		::DrawText(hMemDC, szTitle, (int)strlen(szTitle), &rcTitle, DT_LEFT | DT_TOP);
+	}
+
+
+	// 绘制子项
+	HBRUSH hbrItemBackground[2];
+	HBRUSH hbrItemFrame[2];
+	HBRUSH hbrPinBackground[3];
+	hbrItemBackground[0] = CreateSolidBrush(m_crItemBackground[0]);
+	hbrItemBackground[1] = CreateSolidBrush(m_crItemBackground[1]);
+	hbrItemFrame[0] = CreateSolidBrush(m_crItemFrame[0]);
+	hbrItemFrame[1] = CreateSolidBrush(m_crItemFrame[1]);
+	for (int i = 0; i < 3; i++) {
+		hbrPinBackground[i] = CreateSolidBrush(m_crPinBkgnd[i]);
+	}
+
+
+	// gdi+
+	Gdiplus::Graphics graphics(hMemDC);
+	Gdiplus::Pen pen1(Gdiplus::Color(255, 64, 64, 64), 2);
+	Gdiplus::Pen pen2(Gdiplus::Color(255, 255, 127, 39), 2);
+	Gdiplus::SolidBrush brush1(Gdiplus::Color(255, 64, 64, 64));
+	if (m_bUseGdiPlus) {
+		graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
+	}
+
+
+	SetBkMode(hMemDC, TRANSPARENT);
+	{
+		RECT rcItem;
+		int nPinState;
+		int nItemCount = (int)m_arItem.GetCount();
+		for (int i = 0; i < nItemCount; i++) {
+			EQITEM* pItem = (EQITEM*)m_arItem.GetAt(i);
+			if (pItem->nFlashFlag == 1) {
+				continue;
+			}
+			GetItemRect(pItem, &rcItem);
+
+
+			// 子项背景和边框
+			if (m_nItemRound == 0) {
+				::FillRect(hMemDC, &rcItem, pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]);
+				::FrameRect(hMemDC, &rcItem, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0]);
+			}
+			else {
+				HRGN hRgn = CreateRoundRectRgn(rcItem.left, rcItem.top, rcItem.right, rcItem.bottom, m_nItemRound, m_nItemRound);
+				::FillRgn(hMemDC, hRgn, pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]);
+				::FrameRgn(hMemDC, hRgn, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0], 1, 1);
+				::DeleteObject(hRgn);
+			}
+
+
+			// name和id
+			HFONT hFontOld = (HFONT)::SelectObject(hMemDC, m_hFontName);
+			::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemNameText[1] : m_crItemNameText[0]);
+			::DrawText(hMemDC, pItem->text, (int)strlen(pItem->text), &rcItem, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
+
+			if (pItem->nShowType != ITEM_SMALL) {
+				RECT rcId = rcItem;
+				rcId.left += 5;
+				rcId.bottom -= 5;
+				CString strId;
+				strId.Format(_T("ID:%d"), pItem->id);
+				::SelectObject(hMemDC, m_hFontId);
+				::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemIdText[1] : m_crItemIdText[0]);
+				::DrawText(hMemDC, strId, (int)strId.GetLength(), &rcId, DT_LEFT | DT_BOTTOM | DT_SINGLELINE | DT_END_ELLIPSIS);
+			}
+
+
+			// 动画效果不绘pin
+			if (m_pAnimationItem == pItem) {
+				continue;
+			}
+
+
+			// 绘制pin
+			RECT rcPin, rcPin2, rcPinText;
+			CPtrArray *pPins;
+			rcPinText.left = rcItem.left + 8;
+			rcPinText.right = rcItem.right - 8;
+
+
+			// in pins
+			PIN *pPin = NULL;
+			pPins = (CPtrArray *)pItem->pInPins;
+			for (int j = 0; j < pPins->GetSize(); j++) {
+				if (GetInPinRect(pItem, j, &rcPin)) {
+					pPin = (PIN *)pPins->GetAt(j);
+					::FrameRect(hMemDC, &rcPin, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0]);
+
+					rcPin2.left = rcPin.left + 1;
+					rcPin2.right = rcPin.right;
+					rcPin2.top = rcPin.top + 1;
+					rcPin2.bottom = rcPin.bottom - 1;
+					nPinState = GetPinState(pPin);
+					::FillRect(hMemDC, &rcPin2, nPinState == 0 ? (pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]) : hbrPinBackground[nPinState]);
+
+					if (pItem->nShowType != ITEM_SMALL) {
+						rcPinText.top = rcPin.top - 12;
+						rcPinText.bottom = rcPin.bottom + 12;
+						::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemIdText[1] : m_crItemIdText[0]);
+						::DrawText(hMemDC, pPin->text, (int)strlen(pPin->text), &rcPinText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
+					}
+				}
+			}
+
+
+			// out pins
+			pPins = (CPtrArray *)pItem->pOutPins;
+			for (int j = 0; j < pPins->GetSize(); j++) {
+				pPin = (PIN *)pPins->GetAt(j);
+				if (GetOutPinRect(pItem, j, &rcPin)) {
+					::FrameRect(hMemDC, &rcPin, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0]);
+
+					rcPin2.left = rcPin.left;
+					rcPin2.right = rcPin.right - 1;
+					rcPin2.top = rcPin.top + 1;
+					rcPin2.bottom = rcPin.bottom - 1;
+					nPinState = GetPinState(pPin);
+					::FillRect(hMemDC, &rcPin2, nPinState == 0 ? (pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]) : hbrPinBackground[nPinState]);
+
+					if (pItem->nShowType != ITEM_SMALL) {
+						rcPinText.top = rcPin.top - 12;
+						rcPinText.bottom = rcPin.bottom + 12;
+						::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemIdText[1] : m_crItemIdText[0]);
+						::DrawText(hMemDC, pPin->text, (int)strlen(pPin->text), &rcPinText, DT_RIGHT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
+					}
+				}
+			}
+
+			::DeleteObject(hbrItemFrame);
+			::SelectObject(hMemDC, hFontOld);
+		}
+
+
+		// 绘制连接线,保存线条在最后绘制
+		for (int i = 0; i < nItemCount; i++) {
+			EQITEM *pItem = (EQITEM*)m_arItem.GetAt(i);
+			if (pItem->nFlashFlag == 1) {
+				continue;
+			}
+
+			PIN *pPin = NULL;
+			CPtrArray *pPins;
+
+			// out pins边线
+			RECT rcItem1, rcItem2;
+			pPins = (CPtrArray *)pItem->pOutPins;
+			for (int j = 0; j < pPins->GetSize(); j++) {
+				pPin = (PIN *)pPins->GetAt(j);
+				if (pPin->pConnectedPin != NULL) {
+					POINT pt1, pt2;
+					if (GetPinPoint(pPin, &pt1) && GetPinPoint(pPin->pConnectedPin, &pt2)) {
+						GetItemRect(pItem, &rcItem1);
+						GetItemRect(pPin->pConnectedPin->pItem, &rcItem2);
+						DrawPinConnectedLine(&graphics, &brush1, pPin == m_pSelLineOutPin ? &pen2 : &pen1,
+							&pt1, &pt2, &rcItem1, &rcItem2, pPin);
+					}
+				}
+			}
+		}
+
+
+		for (int i = 0; i < 3; i++) {
+			::DeleteObject(hbrPinBackground[i]);
+		}
+		::DeleteObject(hbrItemBackground[0]);
+		::DeleteObject(hbrItemBackground[1]);
+		::DeleteObject(hbrItemFrame[0]);
+		::DeleteObject(hbrItemFrame[1]);
+	}
+
+
+
+
+	// EndPaint
+	::BitBlt(hDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
+		hMemDC, 0, 0, SRCCOPY);
+	EndPaint(m_hWnd, &ps);
+	::DeleteObject(hBitmap);
+	::DeleteDC(hMemDC);
+
+
+	return 1;
+}
+
+/*
+ * WM_SIZE
+ */
+LRESULT CEqsGraphWnd::OnSize(WPARAM wParam, LPARAM lParam)
+{
+	LRESULT lRet = ::DefWindowProc(m_hWnd, WM_SIZE, wParam, lParam);
+
+	CalculateScollbar();
+
+	if (m_hWndMapPos != NULL) {
+		CalculateMapPos();
+
+		CRect rcItem, rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		GetWindowRect(m_hWndMapPos,& rcItem);
+
+		::MoveWindow(m_hWndMapPos, rcClient.right- rcItem.Width() - MAPPOSWND_PADDING_RIGHT,
+			MAPPOSWND_PADDING_RIGHT, rcItem.Width(), rcItem.Height(), TRUE);
+	}
+
+	return lRet;
+}
+
+/*
+ * WM_VSCROLL
+ */
+LRESULT CEqsGraphWnd::OnVScroll(WPARAM wParam, LPARAM lParam)
+{
+	int nSBCode = LOWORD(wParam);
+	int nPos = HIWORD(wParam);
+
+	SCROLLINFO info = { 0 };
+	info.cbSize = sizeof(SCROLLINFO);
+	info.fMask = SIF_ALL;
+	GetScrollInfo(m_hWnd, SB_VERT, &info);
+
+	int nMaxPos = info.nMax - info.nPage;
+	int inc = 10;
+
+	switch (nSBCode)
+	{
+	case SB_BOTTOM:
+		if (info.nPos < nMaxPos) {
+			// ScrollWindow(m_hWnd, 0, -1 * inc*(iMaxPos - info.nPos), NULL, NULL);
+			info.nPos = nMaxPos;
+		}
+		break;
+
+	case SB_TOP:
+		if (info.nPos > info.nMin) {
+			// ScrollWindow(m_hWnd, 0, inc*(info.nPos - info.nMin), NULL, NULL);
+			info.nPos = info.nMin;
+		}
+		break;
+
+	case SB_LINEUP:
+		if (info.nPos > info.nMin) {
+			//ScrollWindow(m_hWnd, 0, inc, NULL, NULL);
+			info.nPos -= 1;
+		}
+		break;
+
+	case SB_LINEDOWN:
+		if (info.nPos < nMaxPos) {
+			// ScrollWindow(m_hWnd, 0, -1 * inc, NULL, NULL);
+			info.nPos += 1;
+		}
+		break;
+
+	case SB_PAGEUP:
+		if (info.nPos - 100 >= info.nMin) {
+			//ScrollWindow(m_hWnd, 0, 100 * inc, NULL, NULL);
+			info.nPos -= 100;
+		}
+		else {
+			if (info.nPos <= 0) {
+				// ScrollWindow(m_hWnd, 0, 0, NULL, NULL);
+			}
+			else {
+				// ScrollWindow(m_hWnd, 0, info.nPos, NULL, NULL);
+			}
+			info.nPos = info.nMin;
+		}
+		break;
+
+	case SB_PAGEDOWN:
+		if (info.nPos + 100 <= nMaxPos) {
+			// ScrollWindow(m_hWnd, 0, -100*inc, NULL, NULL);
+			info.nPos += 100;
+		}
+		else {
+			// ScrollWindow(m_hWnd, 0, (info.nPos - iMaxPos) * inc, NULL, NULL);
+			info.nPos = nMaxPos;
+		}
+		break;
+
+	case SB_ENDSCROLL:
+		break;
+
+	case SB_THUMBPOSITION:
+		break;
+
+	case SB_THUMBTRACK:
+		// ScrollWindow(m_hWnd, 0, inc * (info.nPos - nPos), NULL, NULL);
+		info.nPos = nPos;
+		break;
+	default:
+		break;
+	}
+
+	m_nOffsetY = info.nPos;
+	SetScrollInfo(m_hWnd, SB_VERT, &info, TRUE);
+	CalculateMapPos();
+
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+
+	LRESULT lRet = ::DefWindowProc(m_hWnd, WM_VSCROLL, wParam, lParam);
+	return lRet;
+}
+
+/*
+ * WM_HSCROLL
+ */
+LRESULT CEqsGraphWnd::OnHScroll(WPARAM wParam, LPARAM lParam)
+{
+	int nSBCode = LOWORD(wParam);
+	int nPos = HIWORD(wParam);
+
+	SCROLLINFO info = { 0 };
+	info.cbSize = sizeof(SCROLLINFO);
+	info.fMask = SIF_ALL;
+	GetScrollInfo(m_hWnd, SB_HORZ, &info);
+
+	int nMaxPos = info.nMax - info.nPage;
+	int inc = 10;
+
+	switch (nSBCode)
+	{
+	case SB_RIGHT:
+		if (info.nPos < nMaxPos) {
+			// ScrollWindow(m_hWnd, 0, -1 * inc*(iMaxPos - info.nPos), NULL, NULL);
+			info.nPos = nMaxPos;
+		}
+		break;
+
+	case SB_LEFT:
+		if (info.nPos > info.nMin) {
+			// ScrollWindow(m_hWnd, 0, inc*(info.nPos - info.nMin), NULL, NULL);
+			info.nPos = info.nMin;
+		}
+		break;
+
+	case SB_LINELEFT:
+		if (info.nPos > info.nMin) {
+			//ScrollWindow(m_hWnd, 0, inc, NULL, NULL);
+			info.nPos -= 1;
+		}
+		break;
+
+	case SB_LINERIGHT:
+		if (info.nPos < nMaxPos) {
+			// ScrollWindow(m_hWnd, 0, -1 * inc, NULL, NULL);
+			info.nPos += 1;
+		}
+		break;
+
+	case SB_PAGELEFT:
+		if (info.nPos - 100 >= info.nMin) {
+			//ScrollWindow(m_hWnd, 0, 100 * inc, NULL, NULL);
+			info.nPos -= 100;
+		}
+		else {
+			if (info.nPos <= 0) {
+				// ScrollWindow(m_hWnd, 0, 0, NULL, NULL);
+			}
+			else {
+				// ScrollWindow(m_hWnd, 0, info.nPos, NULL, NULL);
+			}
+			info.nPos = info.nMin;
+		}
+		break;
+
+	case SB_PAGERIGHT:
+		if (info.nPos + 100 <= nMaxPos) {
+			// ScrollWindow(m_hWnd, 0, -100*inc, NULL, NULL);
+			info.nPos += 100;
+		}
+		else {
+			// ScrollWindow(m_hWnd, 0, (info.nPos - iMaxPos) * inc, NULL, NULL);
+			info.nPos = nMaxPos;
+		}
+		break;
+
+	case SB_ENDSCROLL:
+		break;
+
+	case SB_THUMBPOSITION:
+		break;
+
+	case SB_THUMBTRACK:
+		// ScrollWindow(m_hWnd, 0, inc * (info.nPos - nPos), NULL, NULL);
+		info.nPos = nPos;
+		break;
+	default:
+		break;
+	}
+
+	m_nOffsetX = info.nPos;
+	SetScrollInfo(m_hWnd, SB_HORZ, &info, TRUE);
+	CalculateMapPos();
+
+	RECT rcClient;
+	GetClientRect(m_hWnd, &rcClient);
+	::InvalidateRect(m_hWnd, &rcClient, TRUE);
+
+	LRESULT lRet = ::DefWindowProc(m_hWnd, WM_HSCROLL, wParam, lParam);
+	return lRet;
+}
+
+/*
+ * WM_NOTIFY
+ */
+LRESULT CEqsGraphWnd::OnNitify(WPARAM wParam, LPARAM lParam)
+{
+	LRESULT lRet = ::DefWindowProc(m_hWnd, WM_NOTIFY, wParam, lParam);
+
+	NMHDR *pNmhdr = (NMHDR *)lParam;
+	if (pNmhdr->idFrom == MAPPOSWND_ID) {
+		MAPPOSWND_NMHDR *pNmhdr2 = (MAPPOSWND_NMHDR *)lParam;
+		m_nOffsetX = pNmhdr2->dwData;
+		m_nOffsetY = pNmhdr2->dwData1;
+		CalculateScollbar();
+		CalculateMapPos();
+
+		RECT rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		::InvalidateRect(m_hWnd, &rcClient, TRUE);
+	}
+
+	return lRet;
+}
+
+
+/*
+ * 设置背景颜色
+ * color -- 背景色
+ */
+void CEqsGraphWnd::SetBkgndColor(COLORREF color)
+{
+	m_crBkgnd = color;
+}
+
+/*
+ * 边框颜色
+ * color -- 边框色
+ */
+void CEqsGraphWnd::SetFrameColor(COLORREF color)
+{
+	m_crFrame = color;
+}
+
+void CEqsGraphWnd::FlashItem(EQITEM *pItem)
+{
+	if (m_pFlashItem != NULL) {
+		KillTimer(m_hWnd, TIMER_FLASH);
+	}
+
+	m_nFlashCount = 5;
+	m_pFlashItem = pItem;
+	SetTimer(m_hWnd, TIMER_FLASH, 100, NULL);
+}
+
+void CEqsGraphWnd::AnimationItem(EQITEM* pItem)
+{
+	if (m_pAnimationItem != NULL) {
+		KillTimer(m_hWnd, TIMER_ANIMATION_RECT);
+	}
+
+
+	m_pAnimationItem = NULL;
+	KillTimer(m_hWnd, TIMER_ANIMATION_RECT);
+
+	UINT uElpase = 50;
+	m_nAninationDuration = 200;
+	m_nAninationStep = m_nAninationDuration / uElpase;
+	m_pAnimationItem = pItem;
+	m_rcAnimation.left = pItem->rect.left + (pItem->rect.right - pItem->rect.left) / 2.0f;
+	m_rcAnimation.right = m_rcAnimation.left;
+	m_rcAnimation.top = pItem->rect.top + (pItem->rect.bottom - pItem->rect.top) / 2.0f;
+	m_rcAnimation.bottom = m_rcAnimation.top;
+	m_rcAninationStep.left = (pItem->rect.left - m_rcAnimation.left) / (float)m_nAninationStep;
+	m_rcAninationStep.right = (pItem->rect.right - m_rcAnimation.right) / (float)m_nAninationStep;
+	m_rcAninationStep.top = (pItem->rect.top - m_rcAnimation.top) / (float)m_nAninationStep;
+	m_rcAninationStep.bottom = (pItem->rect.bottom - m_rcAnimation.bottom) / (float)m_nAninationStep;
+	
+	SetTimer(m_hWnd, TIMER_ANIMATION_RECT, uElpase, NULL);
+}
+
+double CEqsGraphWnd::PointToSegDist(double x, double y, double x1, double y1, double x2, double y2)
+{
+	double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);
+	if (cross <= 0) return sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
+
+	double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
+	if (cross >= d2) return sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
+
+	double r = cross / d2;
+	double px = x1 + (x2 - x1) * r;
+	double py = y1 + (y2 - y1) * r;
+	return sqrt((x - px) * (x - px) + (py - y) * (py - y));
+}
+
diff --git a/SourceCode/Bond/Servo/EqsGraphWnd.h b/SourceCode/Bond/Servo/EqsGraphWnd.h
new file mode 100644
index 0000000..e275896
--- /dev/null
+++ b/SourceCode/Bond/Servo/EqsGraphWnd.h
@@ -0,0 +1,234 @@
+#pragma once
+#include <functional>
+
+
+#ifndef EQSGRAPHWND_TAG
+
+#ifdef _WIN32
+
+#define EQSGRAPHWND_CLASSA		"EqsGraphWnd"
+#define EQSGRAPHWND_CLASSW		L"EqsGraphWnd"
+
+#ifdef UNICODE
+#define EQSGRAPHWND_CLASS		EQSGRAPHWND_CLASSW
+#else
+#define EQSGRAPHWND_CLASS		EQSGRAPHWND_CLASSA
+#endif
+
+#else
+#define EQSGRAPHWND_CLASS      "EqsGraphWnd"
+#endif
+
+
+#define EQSGRAPHWND_TAG			_T("EQSGRAPHWND_TAG")
+
+#define EQSGRAPHWND_FIRST		(0U-2890U)
+#define EQSGRAPHWND_LAST		(0U-2850U)
+#define EQSGRAPHWND_			(EQSGRAPHWND_FIRST - 1)
+
+
+#ifndef MIN
+#define MIN(X,Y) (((X)>(Y))?(Y):(X))
+#endif
+
+#ifndef MAX
+#define MAX(X,Y) (((X)>(Y))?(X):(Y))
+#endif
+
+typedef struct tagEQSGRAPHWND_NMHDR
+{
+	NMHDR		nmhdr;
+	DWORD		dwData;
+	DWORD		dwData1;
+	DWORD		dwData2;
+} EQSGRAPHWND_NMHDR;
+
+typedef struct tagEQITEM
+{
+	unsigned int id;
+	RECT rect;
+	char text[64];
+	BOOL bHighlight;
+	int nShowType;
+	DWORD_PTR pData;
+	DWORD_PTR pInPins;
+	DWORD_PTR pOutPins;
+	int nFlashFlag;
+} EQITEM;
+
+typedef struct tagPIN
+{
+	int nIndex;
+	int nType;
+	char text[64];
+	BOOL bHighlight;
+	EQITEM* pItem;
+	tagPIN *pConnectedPin;
+	POINT ptConnectedLine[6];
+	int nLinePtCount;
+	DWORD_PTR pData;
+} PIN;
+
+#define ITEM_NORMAL		0
+#define ITEM_SMALL		1
+#define ITEM_LARGE		2
+
+#endif
+
+typedef std::function<bool(PIN *pPin1, PIN *pPin2)> ONCONNECTPIN;
+typedef std::function<bool(PIN *pPin)> ONDISCONNECTPIN;
+typedef std::function<bool(EQITEM* pItem)> ONDELETEEQITEM;
+typedef std::function<void(EQITEM* pItem, int x, int y)> ONEQITEMPOSCHANGED;
+
+typedef struct _EqsGraphListener
+{
+	ONCONNECTPIN			onConnectPin;
+	ONCONNECTPIN		    onCheckConnectPin;
+	ONDISCONNECTPIN		    onDisconnectPin;
+	ONDELETEEQITEM			onDeleteEqItem;
+	ONEQITEMPOSCHANGED		onEqItemPosChanged;
+	ONDELETEEQITEM			onDblckEqItem;
+	ONDELETEEQITEM			onRclickEqItem;
+} EqsGraphListener;
+
+class CEqsGraphWnd
+{
+	typedef struct tagRECTF
+	{
+		float    left;
+		float    top;
+		float    right;
+		float    bottom;
+	} RECTF;
+
+public:
+	CEqsGraphWnd();
+	~CEqsGraphWnd();
+
+public:
+	static BOOL RegisterWndClass();
+	static CEqsGraphWnd* FromHandle(HWND hWnd);
+	void SetFrameColor(COLORREF color);
+	void SetBkgndColor(COLORREF color);
+
+public:
+	void EnableScroll(BOOL bEnable);
+	void EnableMultiSelect();
+	void SetItemRound(int nRound);
+	void SetDefaultItemBackgroundColor(COLORREF crNormal, COLORREF crSel);
+	void SetDefaultItemFrameColor(COLORREF crNormal, COLORREF crSel);
+	void SetDefaultItemTextColor(COLORREF crNormal, COLORREF crSel);
+	void SetOnListener(EqsGraphListener& listener);
+	BOOL SetCurSel(int nSel);
+	BOOL SetCurSel(CString strItemName);
+	BOOL SetCurSel(DWORD_PTR pData);
+	EQITEM * AddItem(int id, CString strText, DWORD_PTR dwData, int nType = ITEM_NORMAL);
+	void RemoveItem(EQITEM* pItem);
+	PIN * AddPin(EQITEM* pItem, int nType, CString strName, DWORD_PTR dwData);
+	int DeleteItem(EQITEM* pItem);
+	void DeleteAllItems();
+	void SetItemSelectState(int nIndex, BOOL bSelect);
+	void SetCurrentItem(EQITEM* pItem);
+	BOOL GetItemRect(EQITEM* pItem, LPRECT lpRect);
+	BOOL GetItemWarperRect(EQITEM* pItem, LPRECT lpRect);
+	BOOL GetInPinRect(EQITEM* pItem, int nPinIndex, LPRECT lpRect);
+	BOOL GetOutPinRect(EQITEM* pItem, int nPinIndex, LPRECT lpRect);
+	BOOL GetPinPoint(PIN *pPin, LPPOINT lpPoint);
+	EQITEM* GetItem(DWORD_PTR dwData);
+	PIN * GetPin(DWORD_PTR dwItemData, DWORD_PTR dwPinData);
+	int ConnectPin(DWORD_PTR dwItem1Data, DWORD_PTR dwPin1Data, DWORD_PTR dwItem2Data, DWORD_PTR dwPin2Data);
+	void SetItemText(EQITEM* pItem, CString strText);
+	void SetItemType(EQITEM* pItem, int type);
+	void FlashItem(EQITEM* pItem);
+	void AnimationItem(EQITEM*pItem);
+
+private:
+	void Init();
+	void InitFont();
+	void Notify(int nCode, int dwData, int dwData1 = 0, int dwData2 = 0);
+	void Release();
+	void ReleaseItem(EQITEM* pItem);
+	void ReleaseAllItems();
+	void CalculateScollbar();
+	void CalculateMapPos();
+	void CalculateMagneticLine(EQITEM* pItem, LPRECT lprcItemRect, int &hoz, int &ver);
+	int HighTest(POINT pt, OUT EQITEM*& pItem, OUT PIN *& pPin);
+	int GetPinState(PIN *pPin);
+	void ClearConnectedLinePoint(EQITEM*& pItem);
+	void DrawMagneticLine(LPRECT lprcClient, int nHozLine1, int nHozLine2, int nVerLine1, int nVerLine2);
+	void DrawArrow(Gdiplus::Graphics *pGraphics, Gdiplus::Brush* pBrush, Gdiplus::Pen *pPen,
+		int x, int y, int nArrowLen);
+	void DrawDropItemRectangle(LPRECT lpRect1, LPRECT lpRect2);
+	void DrawPinConnectedLine(Gdiplus::Graphics *pGraphics, Gdiplus::Brush *pBrush, Gdiplus::Pen *pPen, LPPOINT lpPt1, LPPOINT lpPt2,
+		LPRECT lpRect1, LPRECT lpRect2, PIN *pOwnerPin);
+	void DrawPinWillConnectLine(COLORREF color, LPPOINT lpPt1, LPPOINT lpPt2);
+	double PointToSegDist(double x, double y, double x1, double y1, double x2, double y2);
+	static CEqsGraphWnd* Hook(HWND hWnd);
+	static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+	static LRESULT OnNcCreate(HWND hWnd, WPARAM wParam, LPARAM lParam);
+	LRESULT OnDestroy(WPARAM wParam, LPARAM lParam);
+	LRESULT OnTimer(WPARAM wParam, LPARAM lParam);
+	LRESULT OnNcPaint(WPARAM wParam, LPARAM lParam);
+	LRESULT OnPaint(WPARAM wParam, LPARAM lParam);
+	LRESULT OnMouseMove(WPARAM wParam, LPARAM lParam);
+	LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam);
+	LRESULT OnRButtonDown(WPARAM wParam, LPARAM lParam);
+	LRESULT OnLButtonDblclk(WPARAM wParam, LPARAM lParam);
+	LRESULT OnMouseWheel(WPARAM wParam, LPARAM lParam);
+	LRESULT OnMouseHWheel(WPARAM wParam, LPARAM lParam);
+	LRESULT OnKeyDown(WPARAM wParam, LPARAM lParam);
+	LRESULT OnSize(WPARAM wParam, LPARAM lParam);
+	LRESULT OnVScroll(WPARAM wParam, LPARAM lParam);
+	LRESULT OnHScroll(WPARAM wParam, LPARAM lParam);
+	LRESULT OnNitify(WPARAM wParam, LPARAM lParam);
+
+private:
+	EQITEM*		m_pCurItem;
+	int			m_nFlashCount;
+	EQITEM*		m_pFlashItem;
+	EQITEM*		m_pAnimationItem;
+	PIN *		m_pCurPin;
+	PIN *		m_pSelLineOutPin;		// 选中的连线的两个pin中的out pin
+
+private:
+	HWND		m_hWnd;
+	COLORREF	m_crBkgnd;
+	COLORREF	m_crFrame;
+	HFONT		m_hFontTitle;
+
+private:
+	BOOL m_bUseGdiPlus;					// 使用GDI+绘图?
+	COLORREF m_crItemBackground[2];		// item的颜色,normal, active
+	COLORREF m_crItemFrame[2];			// item的边框,normal, active
+	COLORREF m_crItemNameText[2];
+	COLORREF m_crItemIdText[2];
+	COLORREF m_crPinBkgnd[3];			// pin的颜色,normal, active, enable connect
+	int m_nCurSel;
+	EqsGraphListener m_listener;
+	CPtrArray m_arItem;
+	BOOL m_bMultiSelect;
+	int m_nItemRound;
+
+private:
+	int m_nStageCx;			// 画布大小
+	int m_nStageCy;
+	int m_nOffsetX;
+	int m_nOffsetY;
+
+	// 动画
+	RECTF m_rcAnimation;
+	RECTF m_rcAninationStep;
+	int m_nAninationStep;
+	int m_nAninationDuration;		// ms
+
+	// 字体
+	HFONT m_hFontName;
+	HFONT m_hFontId;
+
+private:
+	HWND m_hWndMapPos;
+	BOOL m_bEnableScroll;
+	int m_nMagneticLinHoz;
+	int m_nMagneticLinVer;
+};
+
diff --git a/SourceCode/Bond/Servo/HmTab.cpp b/SourceCode/Bond/Servo/HmTab.cpp
new file mode 100644
index 0000000..29e13c9
--- /dev/null
+++ b/SourceCode/Bond/Servo/HmTab.cpp
@@ -0,0 +1,529 @@
+// HmTab.cpp: implementation of the CHmTab class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#include "stdafx.h"
+#include "HmTab.h"
+
+#ifdef _DEBUG
+#undef THIS_FILE
+static char THIS_FILE[]=__FILE__;
+#define new DEBUG_NEW
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+CHmTab::CHmTab()
+{
+	m_hWnd = NULL;
+	m_crBkgnd = RGB(250, 250, 255);
+	m_nPaddingLeft = 28;
+	m_nPaddingBottom = 12;
+	m_nItemMarginLeft = 8;
+	m_crText[0] = RGB(88, 88, 88);
+	m_crText[1] = RGB(18, 18, 18);
+	m_pPressItem = nullptr;
+	m_pHighItem = nullptr;
+
+	LOGBRUSH lb;
+	lb.lbColor = RGB(225, 127, 39);
+	lb.lbHatch = 0;
+	lb.lbStyle = BS_SOLID;
+	DWORD iStyle = PS_GEOMETRIC | PS_SOLID | PS_ENDCAP_SQUARE | PS_JOIN_MITER;
+	m_hPenUnder[0] = ExtCreatePen(iStyle, 3, &lb, 0, NULL);
+	lb.lbColor = RGB(225, 127, 39);
+	m_hPenUnder[1] = ExtCreatePen(iStyle, 2, &lb, 0, NULL);
+	lb.lbColor = RGB(223, 226, 230);
+	m_hPenUnderWnd = ExtCreatePen(iStyle, 1, &lb, 0, NULL);
+	m_nCurSel = 0;
+}
+
+CHmTab::~CHmTab()
+{
+	if (m_hPenUnder[0] != nullptr) {
+		::DeleteObject(m_hPenUnder[0]);
+	}
+	if (m_hPenUnder[1] != nullptr) {
+		::DeleteObject(m_hPenUnder[1]);
+	}
+
+	if (m_hPenUnderWnd != nullptr) {
+		::DeleteObject(m_hPenUnderWnd);
+	}
+}
+
+BOOL CHmTab::RegisterWndClass()
+{
+	WNDCLASS wc;
+	wc.lpszClassName = BYHMTAB_CLASS;
+	wc.hInstance = AfxGetInstanceHandle();
+	wc.lpfnWndProc = WindowProc;
+	wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
+	wc.hIcon = 0;
+	wc.lpszMenuName = NULL;
+	wc.hbrBackground = NULL;
+	wc.style = CS_GLOBALCLASS|CS_DBLCLKS;
+	wc.cbClsExtra = 0;
+	wc.cbWndExtra = 0;
+	
+	// 注册自定义类
+	return (::RegisterClass(&wc) != 0);
+}
+
+CHmTab* CHmTab::Hook(HWND hWnd)
+{
+	CHmTab* pHmTab = (CHmTab*)GetProp(hWnd, BYSTAG_HMTAB);
+	if(pHmTab == NULL)
+	{
+		pHmTab = new CHmTab;
+		pHmTab->m_hWnd = hWnd;
+
+		SetProp(hWnd, BYSTAG_HMTAB, (HANDLE)pHmTab);
+	}
+	
+	return pHmTab;
+}
+
+void CHmTab::Release()
+{
+	// delete
+	delete this;
+}
+
+void CHmTab::SetPaddingLeft(int value)
+{
+	m_nPaddingLeft = value;
+}
+
+void CHmTab::SetItemMarginLeft(int value)
+{
+	m_nItemMarginLeft = value;
+}
+
+void CHmTab::SetTextColor(COLORREF color1, COLORREF color2)
+{
+	m_crText[0] = color1;
+	m_crText[1] = color2;
+	InvalidateRect(m_hWnd, NULL, TRUE);
+}
+
+int CHmTab::AddItem(const char* pszText,BOOL bUpdate/* = TRUE*/)
+{
+	HMTABITEM item;
+	memset(&item, 0, sizeof(HMTABITEM));
+	strcpy_s(item.szText, MHITEM_TEXT_MAX, pszText);
+	m_items.push_back(item);
+
+	if (bUpdate) {
+		InvalidateRect(m_hWnd, NULL, TRUE);
+	}
+
+	return 0;
+}
+
+int CHmTab::DeleteItem(const char* pszText, BOOL bUpdate/* = TRUE*/)
+{
+	for (auto iter = m_items.begin(); iter != m_items.end(); iter++) {
+		if (strcmp((*iter).szText, pszText) == 0) {
+			m_items.erase(iter);
+			break;
+		}
+	}
+
+	if (bUpdate) {
+		InvalidateRect(m_hWnd, NULL, TRUE);
+	}
+
+	return 0;
+}
+
+void CHmTab::Notify(int nCode, DWORD_PTR dwData, DWORD_PTR dwData1/* = 0*/, DWORD_PTR dwData2/* = 0*/)
+{
+	HWND hParent;
+	hParent = GetParent(m_hWnd);
+	if (hParent != NULL) {
+		BYHMTAB_NMHDR iii_nmhdr;
+		iii_nmhdr.nmhdr.hwndFrom = m_hWnd;
+		iii_nmhdr.nmhdr.idFrom = GetWindowLong(m_hWnd, GWL_ID);
+		iii_nmhdr.nmhdr.code = nCode;
+		iii_nmhdr.dwData = dwData;
+		iii_nmhdr.dwData1 = dwData1;
+		iii_nmhdr.dwData2 = dwData2;
+		SendMessage(hParent, WM_NOTIFY, (WPARAM)iii_nmhdr.nmhdr.idFrom, (LPARAM)&iii_nmhdr);
+	}
+}
+
+////////////////////////////////
+// 拦截窗口消息函数
+LRESULT CALLBACK CHmTab::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)   
+{
+	CHmTab* pHmTab = (CHmTab *)GetProp(hWnd, BYSTAG_HMTAB);
+	if(pHmTab == NULL && uMsg != WM_NCCREATE)
+	{
+		return ::DefWindowProc(hWnd, uMsg, wParam, lParam);   
+	}
+
+	
+	// 如果Hook则响应消息
+	ASSERT(hWnd);
+	switch(uMsg)   
+	{
+	case WM_NCCREATE:
+		return OnNcCreate(hWnd, wParam, lParam);
+
+	case WM_DESTROY:
+		return pHmTab->OnDestroy(wParam, lParam);
+
+	case WM_PAINT:
+		return pHmTab->OnPaint(wParam, lParam);
+
+	case WM_TIMER:
+		return pHmTab->OnTimer(wParam, lParam);
+
+	case WM_MOUSEMOVE:
+		return pHmTab->OnMouseMove(wParam, lParam);
+
+	case WM_LBUTTONDOWN:
+		return pHmTab->OnLButtonDown(wParam, lParam);
+
+	case WM_SETCURSOR:
+		return pHmTab->OnSetCursor(wParam, lParam);
+
+	case WM_SIZE:
+		return pHmTab->OnSize(wParam, lParam);
+
+	case WM_GETDLGCODE:
+		return DLGC_WANTALLKEYS;
+
+	default:
+		break;
+	}
+	
+	return ::DefWindowProc(hWnd, uMsg, wParam, lParam);   
+}  
+
+///////////////////////////////
+// WM_NCCREATE
+// 窗口创建前的初始化工作
+LRESULT CHmTab::OnNcCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
+{
+	CHmTab* pHmTab = (CHmTab *)GetProp(hWnd, BYSTAG_HMTAB);
+	ASSERT(pHmTab == NULL);
+
+	Hook(hWnd);
+	return ::DefWindowProc(hWnd, WM_NCCREATE, wParam, lParam);
+}
+
+///////////////////////////////
+// WM_DESTROY
+LRESULT CHmTab::OnDestroy(WPARAM wParam, LPARAM lParam)
+{
+	Release();
+	return ::DefWindowProc(m_hWnd, WM_DESTROY, wParam, lParam);
+}
+
+///////////////////////////////
+// WM_TIMER
+LRESULT CHmTab::OnTimer(WPARAM wParam, LPARAM lParam)
+{
+	if (wParam == 1) {
+		POINT pt;
+		GetCursorPos(&pt);
+		::ScreenToClient(m_hWnd, &pt);
+
+		HMTABITEM* pLastHighItem = m_pHighItem;
+		HighTest(pt, m_pHighItem, nullptr);
+		if (m_pHighItem != pLastHighItem) {
+			::InvalidateRect(m_hWnd, NULL, TRUE);
+		}
+
+		if (m_pHighItem == nullptr) {
+			::KillTimer(m_hWnd, 1);
+		}
+	}
+
+	return ::DefWindowProc(m_hWnd, WM_TIMER, wParam, lParam);
+}
+
+///////////////////////////////
+// WM_MOUSEMOVE
+LRESULT CHmTab::OnMouseMove(WPARAM wParam, LPARAM lParam)
+{
+	POINT pt;
+	pt.x = LOWORD(lParam);
+	pt.y = HIWORD(lParam);
+
+	HMTABITEM* pLastHighItem = m_pHighItem;
+	int nHitCode = HighTest(pt, m_pHighItem, nullptr);
+	if (m_pHighItem != pLastHighItem) {
+		RECT rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		::InvalidateRect(m_hWnd, &rcClient, TRUE);
+	}
+	if (m_pHighItem != NULL) {
+		::KillTimer(m_hWnd, 1);
+		::SetTimer(m_hWnd, 1, 100, NULL);
+	}
+
+	::SetProp(m_hWnd, HMTAB_HITCODETEST, (HANDLE)(__int64)nHitCode);
+	return ::DefWindowProc(m_hWnd, WM_MOUSEMOVE, wParam, lParam);
+}
+
+/*
+ * WM_LBUTTONDOWN
+ * 鼠标左键按下
+ */
+LRESULT CHmTab::OnLButtonDown(WPARAM wParam, LPARAM lParam)
+{
+	POINT pt, ptNew;
+	pt.x = LOWORD(lParam);
+	pt.y = HIWORD(lParam);
+
+	BOOL bButtonUp = FALSE;
+	int nClickIndex = -1;
+	HighTest(pt, m_pPressItem, &nClickIndex);
+	if (m_pPressItem != NULL) {
+		m_pHighItem = NULL;
+		::InvalidateRect(m_hWnd, NULL, TRUE);
+	}
+
+
+	// 捕捉鼠标消息,检测是否拖动
+	HMTABITEM* pPressItem = NULL;
+	if (m_pPressItem != NULL) {
+		::KillTimer(m_hWnd, 1);
+		if (::GetCapture() == NULL) {
+			SetCapture(m_hWnd);
+			ASSERT(m_hWnd == GetCapture());
+			AfxLockTempMaps();
+			for (;;)
+			{
+				MSG msg;
+				VERIFY(::GetMessage(&msg, NULL, 0, 0));
+
+				if (GetCapture() != m_hWnd) break;
+
+				switch (msg.message)
+				{
+				case WM_MOUSEMOVE:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+					HighTest(ptNew, pPressItem, &nClickIndex);
+					if (pPressItem != m_pPressItem) {
+						m_pPressItem = nullptr;
+						::InvalidateRect(m_hWnd, NULL, TRUE);
+					}
+					break;
+
+				case WM_LBUTTONUP:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+					HighTest(ptNew, pPressItem, &nClickIndex);
+					if (m_pPressItem != nullptr && pPressItem == m_pPressItem) {
+						m_nCurSel = nClickIndex;
+						bButtonUp = TRUE;
+					}
+					goto ExitLoop;
+
+				case WM_KEYDOWN:
+					if (msg.wParam == VK_ESCAPE) {
+						goto ExitLoop;
+					}
+					break;
+
+				default:
+					DispatchMessage(&msg);
+					break;
+				}
+			}
+
+		ExitLoop:
+			m_pPressItem = NULL;
+			ReleaseCapture();
+			::InvalidateRect(m_hWnd, NULL, TRUE);
+
+			if (bButtonUp) {
+				Notify((int)BYHMTAB_SEL_CHANGED, m_nCurSel);
+			}
+
+			AfxUnlockTempMaps(FALSE);
+		}
+	}
+
+
+	return ::DefWindowProc(m_hWnd, WM_LBUTTONDOWN, wParam, lParam);
+}
+
+///////////////////////////////
+// WM_SETCURSOR
+LRESULT CHmTab::OnSetCursor(WPARAM wParam, LPARAM lParam)
+{
+	int nHitCode = (int)(__int64)GetProp(m_hWnd, HMTAB_HITCODETEST);
+	switch (nHitCode)
+	{
+	case HMTAB_HT_NOWHERE:
+	case HMTAB_HT_ITEM:
+		SetCursor(::LoadCursor(NULL, IDC_ARROW));
+		return TRUE;
+
+	case HMTAB_HT_HIGT_ITEM:
+		SetCursor(::LoadCursor(NULL, IDC_HAND));
+		return TRUE;
+
+	default:
+		break;
+	}
+
+	return ::DefWindowProc(m_hWnd, WM_SETCURSOR, wParam, lParam);
+}
+
+/*
+ * WM_SIZE
+ */
+LRESULT CHmTab::OnSize(WPARAM wParam, LPARAM lParam)
+{
+	::InvalidateRect(m_hWnd, NULL, TRUE);
+	return ::DefWindowProc(m_hWnd, WM_SIZE, wParam, lParam);
+}
+
+///////////////////////////////
+// WM_PAINT
+LRESULT CHmTab::OnPaint(WPARAM wParam, LPARAM lParam)
+{
+	HDC hDC, hMemDC;
+	HBITMAP hBitmap;
+	RECT rcClient;
+	CString strText;
+	HFONT hFont1, hFont2;
+	HBRUSH hBrushBK;
+
+
+	// BeginPaint
+	PAINTSTRUCT ps;
+	hDC = BeginPaint(m_hWnd, &ps);
+	GetClientRect(m_hWnd, &rcClient);
+
+	hMemDC = ::CreateCompatibleDC(hDC);
+	hBitmap = ::CreateCompatibleBitmap(hDC, rcClient.right - rcClient.left,
+		rcClient.bottom - rcClient.top);
+	::SelectObject(hMemDC, hBitmap);
+	::SetBkMode(hMemDC, TRANSPARENT);
+
+
+	HFONT hFontDefault = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
+	CFont* pFont = CFont::FromHandle(hFontDefault);
+	LOGFONT lf;
+	pFont->GetLogFont(&lf);
+	hFont1 = ::CreateFontIndirect(&lf);
+	lf.lfHeight -= 5;
+	hFont2 = ::CreateFontIndirect(&lf);
+
+
+	// 背景颜色
+	hBrushBK = CreateSolidBrush(m_crBkgnd);
+	::FillRect(hMemDC, &rcClient, hBrushBK);
+	DeleteObject(hBrushBK);
+
+
+	SelectObject(hMemDC, m_hPenUnderWnd);
+	::MoveToEx(hMemDC, rcClient.left, rcClient.bottom - 2, NULL);
+	::LineTo(hMemDC, rcClient.right, rcClient.bottom - 2);
+
+
+
+	// 绘制子项
+	SIZE sizeItem;
+	RECT rcItem, rcText;
+	rcItem.top = rcClient.top;
+	rcItem.bottom = rcClient.bottom-1;
+	rcItem.left = m_nPaddingLeft;
+	int index = 0;
+	for (int i = 0; i < m_items.size(); i++) {
+		auto& item = m_items.at(i);
+		::SelectObject(hMemDC, i == m_nCurSel ? hFont2 : hFont1);
+		::SetTextColor(hMemDC, i == m_nCurSel ? m_crText[1] : m_crText[0]);
+		::GetTextExtentPoint32(hMemDC, item.szText, (int)strlen(item.szText), &sizeItem);
+		rcItem.left += m_nItemMarginLeft;
+		rcItem.right = rcItem.left + (int)sizeItem.cx;
+
+		::CopyRect(&rcText, &rcItem);
+		rcText.bottom -= m_nPaddingBottom;
+		::DrawText(hMemDC, item.szText, (int)strlen(item.szText), &rcText,
+			DT_LEFT | DT_BOTTOM | DT_SINGLELINE | DT_END_ELLIPSIS);
+		::CopyRect(&item.rect, &rcItem);
+
+
+		// 下划线?
+		if (i == m_nCurSel || &item == m_pHighItem) {
+			HPEN hOldPen = (HPEN)::SelectObject(hMemDC, i == m_nCurSel ? m_hPenUnder[0] : m_hPenUnder[1]);
+			::MoveToEx(hMemDC, item.rect.left, item.rect.bottom - 3, NULL);
+			::LineTo(hMemDC, item.rect.right, item.rect.bottom - 3);
+			::SelectObject(hMemDC, hOldPen);
+		}
+
+
+		index++;
+		rcItem.left = rcItem.right;
+	}
+	::DeleteObject(hFont1);
+	::DeleteObject(hFont2);
+
+
+	// EndPaint
+	::BitBlt(hDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
+		hMemDC, 0, 0, SRCCOPY);
+	EndPaint(m_hWnd, &ps);
+	::DeleteObject(hBitmap);
+	::DeleteDC(hMemDC);
+
+
+	return 1;
+}
+
+void CHmTab::SetBkgndColor(COLORREF cr)
+{
+	m_crBkgnd = cr;
+}
+
+/*
+ * 检测坐标点所在的项
+ * 返回, TYGTLITEM
+ */
+int CHmTab::HighTest(POINT pt, OUT HMTABITEM*& pItem, int* pnIndex)
+{
+	// 检测是否在某个子项
+	int nRet = HMTAB_HT_NOWHERE;
+	pItem = NULL;
+	for (int i = 0; i < m_items.size(); i++) {
+		auto& item = m_items.at(i);
+		if (::PtInRect(&item.rect, pt)) {
+			pItem = &item;
+			nRet = pItem == m_pHighItem ? HMTAB_HT_HIGT_ITEM : HMTAB_HT_ITEM;
+			if (pnIndex != nullptr) *pnIndex = i;
+			break;
+		}
+	}
+
+
+	return nRet;
+}
+
+int CHmTab::GetCurSel()
+{
+	return m_nCurSel;
+}
+
+void CHmTab::SetCurSel(int index)
+{
+	if (0 <= index && index <= m_items.size()) {
+		m_nCurSel = index;
+		InvalidateRect(m_hWnd, NULL, TRUE);
+	}
+}
+
+int CHmTab::GetItemCount()
+{
+	return (int)m_items.size();
+}
diff --git a/SourceCode/Bond/Servo/HmTab.h b/SourceCode/Bond/Servo/HmTab.h
new file mode 100644
index 0000000..600b5b1
--- /dev/null
+++ b/SourceCode/Bond/Servo/HmTab.h
@@ -0,0 +1,130 @@
+// HmTab.h: interface for the CHmTab class.
+//
+//////////////////////////////////////////////////////////////////////
+
+#if !defined(AFX_HMTAB_H__FBB8916A_DE77_4EA3_9C21_E51E6B06194C__INCLUDED_)
+#define AFX_HMTAB_H__FBB8916A_DE77_4EA3_9C21_E51E6B06194C__INCLUDED_
+
+
+#pragma comment(lib, "Msimg32.lib")			// TransparentBlt
+#include <vector>
+
+
+
+//====== HmTab =====================================================
+
+#ifndef NOHMTAB
+
+#ifdef _WIN32
+
+#define BYHMTAB_CLASSA       "BYHmTab"
+#define BYHMTAB_CLASSW       L"BYHmTab"
+
+#ifdef UNICODE
+#define  BYHMTAB_CLASS       BYHMTAB_CLASSW
+#else
+#define  BYHMTAB_CLASS       BYHMTAB_CLASSA
+#endif
+
+#else
+#define BYHMTAB_CLASS        "BYHmTab"
+#endif
+
+
+#define BYSTAG_HMTAB		 _T("ISHMTAB")
+
+
+//====== WM_NOTIFY codes (NMHDR.code values) ==================================
+#define BYHMTAB_FIRST				 (0U-1190U)       //
+#define BYHMTAB_LAST				 (0U-1150U)
+#define BYHMTAB_SEL_CHANGED	 		 (BYHMTAB_FIRST - 1)
+
+
+
+
+typedef struct tagBYHMTAB_NMHDR
+{
+	NMHDR		nmhdr;
+	DWORD_PTR	dwData;
+	DWORD_PTR	dwData1;
+	DWORD_PTR	dwData2;
+} BYHMTAB_NMHDR;
+
+#define MHITEM_TEXT_MAX		64
+
+typedef struct tagHMTABITEM
+{
+	int id;
+	char szText[MHITEM_TEXT_MAX];
+	RECT rect;
+} HMTABITEM;
+
+#define HMTAB_HITCODETEST			_T("HitCode")
+#define HMTAB_HT_NOWHERE			0x1
+#define HMTAB_HT_ITEM				0x2
+#define HMTAB_HT_HIGT_ITEM			0x4
+
+
+#endif
+
+
+
+
+#if _MSC_VER > 1000
+#pragma once
+#endif // _MSC_VER > 1000
+
+
+class CHmTab
+{
+public:
+	CHmTab();
+	virtual ~CHmTab();
+
+public:
+	static BOOL RegisterWndClass();
+	static CHmTab* Hook(HWND hWnd);
+	void Notify(int nCode, DWORD_PTR dwData, DWORD_PTR dwData1 = 0, DWORD_PTR dwData2 = 0);
+	void Release();
+	void SetBkgndColor(COLORREF cr);
+	static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+	static LRESULT OnNcCreate(HWND hWnd, WPARAM wParam, LPARAM lParam);
+	LRESULT OnDestroy(WPARAM wParam, LPARAM lParam);
+	LRESULT OnTimer(WPARAM wParam, LPARAM lParam);
+	LRESULT OnPaint(WPARAM wParam, LPARAM lParam);
+	LRESULT OnSetCursor(WPARAM wParam, LPARAM lParam);
+	LRESULT OnSize(WPARAM wParam, LPARAM lParam);
+	LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam);
+	LRESULT OnMouseMove(WPARAM wParam, LPARAM lParam);
+
+
+public:
+	void SetPaddingLeft(int value);
+	void SetItemMarginLeft(int value);
+	void SetTextColor(COLORREF color1, COLORREF color2);
+	int AddItem(const char* pszText, BOOL bUpdate = TRUE);
+	int DeleteItem(const char* pszText, BOOL bUpdate = TRUE);
+	int HighTest(POINT pt, OUT HMTABITEM*& pItem, int* pnIndex);
+	int GetCurSel();
+	void SetCurSel(int index);
+	int GetItemCount();
+
+
+private:
+	HWND		m_hWnd;
+	COLORREF m_crBkgnd;
+
+private:
+	int m_nCurSel;
+	std::vector< HMTABITEM > m_items;
+	HMTABITEM* m_pHighItem;
+	HMTABITEM* m_pPressItem;
+	int m_nPaddingLeft;
+	int m_nPaddingBottom;
+	int m_nItemMarginLeft;
+	COLORREF m_crText[2];
+	HPEN m_hPenUnder[2];
+	HPEN m_hPenUnderWnd;
+};
+
+#endif // !defined(AFX_HMTAB_H__FBB8916A_DE77_4EA3_9C21_E51E6B06194C__INCLUDED_)
diff --git a/SourceCode/Bond/Servo/MapPosWnd.cpp b/SourceCode/Bond/Servo/MapPosWnd.cpp
new file mode 100644
index 0000000..35ce5c0
--- /dev/null
+++ b/SourceCode/Bond/Servo/MapPosWnd.cpp
@@ -0,0 +1,536 @@
+#include "stdafx.h"
+#include "MapPosWnd.h"
+#include "ColorTransfer.h"
+
+#define HT_NOWHERE		0x1
+#define HT_INDICATOR	0x2
+
+CMapPosWnd::CMapPosWnd()
+{
+	m_hWnd = NULL;
+	m_crFrame = RGB(0, 0, 0);
+	m_crBkgnd = RGB(255, 255, 255);
+	m_crViewPort = RGB(185, 122, 87);
+
+	m_nWndMaxSize = 200;
+	m_nStageCx = 4000;
+	m_nStageCy = 3000;
+	m_rcViewPort = {200, 200, 800, 800};
+}
+
+CMapPosWnd::~CMapPosWnd()
+{
+
+}
+
+BOOL CMapPosWnd::RegisterWndClass()
+{
+	WNDCLASS wc;
+	wc.lpszClassName = MAPPOSWND_CLASS;
+	wc.hInstance = AfxGetInstanceHandle();
+	wc.lpfnWndProc = WindowProc;
+	wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
+	wc.hIcon = 0;
+	wc.lpszMenuName = NULL;
+	wc.hbrBackground = NULL;
+	wc.style = CS_GLOBALCLASS | CS_DBLCLKS;
+	wc.cbClsExtra = 0;
+	wc.cbWndExtra = 0;
+
+	// 注册窗口类
+	return (::RegisterClass(&wc) != 0);
+}
+
+CMapPosWnd * CMapPosWnd::FromHandle(HWND hWnd)
+{
+	CMapPosWnd *pMapPosWnd = (CMapPosWnd *)::GetProp(hWnd, MAPPOSWND_TAG);
+	return pMapPosWnd;
+}
+
+CMapPosWnd* CMapPosWnd::Hook(HWND hWnd)
+{
+	CMapPosWnd* pMapPosWnd = (CMapPosWnd*)GetProp(hWnd, MAPPOSWND_TAG);
+	if (pMapPosWnd == NULL) {
+		pMapPosWnd = new CMapPosWnd();
+		pMapPosWnd->m_hWnd = hWnd;
+
+		SetProp(hWnd, MAPPOSWND_TAG, (HANDLE)pMapPosWnd);
+	}
+
+
+	return pMapPosWnd;
+}
+
+void CMapPosWnd::SetWndMaxSize(int nMaxSize)
+{
+	m_nWndMaxSize = nMaxSize;
+}
+
+void CMapPosWnd::SetStageSize(int cx, int cy, BOOL bInvalidata)
+{
+	m_nStageCx = cx;
+	m_nStageCy = cy;
+
+	float scale = max(m_nStageCx / (float)m_nWndMaxSize, m_nStageCy / (float)m_nWndMaxSize);
+	int w = (int)(m_nStageCx / scale) +1;
+	int h = (int)(m_nStageCy / scale) + 1;
+	::SetWindowPos(m_hWnd, NULL, 0, 0, w, h, SWP_NOMOVE);
+
+	if (bInvalidata) {
+		RECT rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		InvalidateRect(m_hWnd, &rcClient, TRUE);
+	}
+}
+
+void CMapPosWnd::SetViewPort(LPRECT lpRect, BOOL bInvalidata)
+{
+	::CopyRect(&m_rcViewPort, lpRect);
+	if (bInvalidata) {
+		RECT rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		InvalidateRect(m_hWnd, &rcClient, TRUE);
+	}
+}
+
+void CMapPosWnd::GetViewPortRect(LPRECT lprcClient, LPRECT lprcDest)
+{
+	RECT rcClient;
+	if (lprcClient == NULL) {
+		GetClientRect(m_hWnd, &rcClient);
+		lprcClient = &rcClient;
+	}
+
+
+	float scale = max(m_nStageCx / (float)(lprcClient->right - lprcClient->left), m_nStageCy / (float)(lprcClient->bottom - lprcClient->top));
+	lprcDest->left = long(m_rcViewPort.left / scale);
+	lprcDest->top = long(m_rcViewPort.top / scale);
+	lprcDest->right = long(m_rcViewPort.right / scale);
+	lprcDest->bottom = long(m_rcViewPort.bottom / scale);
+}
+
+void CMapPosWnd::Init()
+{
+}
+
+void CMapPosWnd::Release()
+{
+
+	// delete
+	delete this;
+}
+
+void CMapPosWnd::Notify(int nCode, int dwData, int dwData1/* = 0*/, int dwData2/* = 0*/)
+{
+	HWND hParent;
+	hParent = GetParent(m_hWnd);
+	if (hParent != NULL) {
+		MAPPOSWND_NMHDR nmhdr;
+		nmhdr.nmhdr.hwndFrom = m_hWnd;
+		nmhdr.nmhdr.idFrom = GetWindowLong(m_hWnd, GWL_ID);
+		nmhdr.nmhdr.code = nCode;
+		nmhdr.dwData = dwData;
+		nmhdr.dwData1 = dwData1;
+		nmhdr.dwData2 = dwData2;
+		SendMessage(hParent, WM_NOTIFY, (WPARAM)nmhdr.nmhdr.idFrom, (LPARAM)&nmhdr);
+	}
+}
+
+/*
+ * 检测坐标点所在的项
+ * 返回项类型, 如HT_INDICATOR
+ */
+int CMapPosWnd::HighTest(POINT pt)
+{
+	// 检测是否在某个子项
+	int nRet = HT_NOWHERE;
+
+	RECT rcClient, rcViewPort;
+	GetClientRect(m_hWnd, &rcClient);
+	GetViewPortRect(&rcClient, &rcViewPort);
+	if (::PtInRect(&rcViewPort, pt)) {
+		nRet = HT_INDICATOR;
+	}
+
+	return nRet;
+}
+
+/*
+ * WindowProc,窗口过程
+ */
+LRESULT CALLBACK CMapPosWnd::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+	CMapPosWnd* pMapPosWnd = (CMapPosWnd *)GetProp(hWnd, MAPPOSWND_TAG);
+	if (pMapPosWnd == NULL && uMsg != WM_NCCREATE)
+	{
+		return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
+	}
+
+
+	// 处理窗口消息
+	ASSERT(hWnd);
+	switch (uMsg)
+	{
+	case WM_NCCREATE:
+		return CMapPosWnd::OnNcCreate(hWnd, wParam, lParam);
+
+	case WM_DESTROY:
+		return pMapPosWnd->OnDestroy(wParam, lParam);
+
+	case WM_NCPAINT:
+		return pMapPosWnd->OnNcPaint(wParam, lParam);
+
+	case WM_PAINT:
+		return pMapPosWnd->OnPaint(wParam, lParam);
+
+	case WM_TIMER:
+		return pMapPosWnd->OnTimer(wParam, lParam);
+
+	case WM_MOUSEMOVE:
+		return pMapPosWnd->OnMouseMove(wParam, lParam);
+
+	case WM_LBUTTONDOWN:
+		return pMapPosWnd->OnLButtonDown(wParam, lParam);
+
+	case WM_LBUTTONDBLCLK:
+		return pMapPosWnd->OnLButtonDblclk(wParam, lParam);
+
+	case WM_MOUSEWHEEL:
+		return pMapPosWnd->OnMouseWheel(wParam, lParam);
+
+	case WM_KEYDOWN:
+		return pMapPosWnd->OnKeyDown(wParam, lParam);
+
+	case WM_SIZE:
+		return pMapPosWnd->OnSize(wParam, lParam);
+
+	case WM_GETDLGCODE:
+		return DLGC_WANTALLKEYS;
+
+	default:
+		break;
+	}
+
+	return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
+}
+
+/*
+ * WM_NCCREATE
+ * 窗口创建
+ */
+LRESULT CMapPosWnd::OnNcCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
+{
+	CMapPosWnd* pMapPosWnd = (CMapPosWnd *)GetProp(hWnd, MAPPOSWND_TAG);
+	ASSERT(pMapPosWnd == NULL);
+
+	Hook(hWnd)->Init();
+	return ::DefWindowProc(hWnd, WM_NCCREATE, wParam, lParam);
+}
+
+/*
+ * WM_DESTROY
+ * 窗口销毁
+ */
+LRESULT CMapPosWnd::OnDestroy(WPARAM wParam, LPARAM lParam)
+{
+	Release();
+	return ::DefWindowProc(m_hWnd, WM_DESTROY, wParam, lParam);
+}
+
+
+/*
+ * WM_TIMER
+ */
+LRESULT CMapPosWnd::OnTimer(WPARAM wParam, LPARAM lParam)
+{
+
+	return ::DefWindowProc(m_hWnd, WM_TIMER, wParam, lParam);
+}
+
+/*
+ * WM_MOUSEMOVE
+ * 鼠标滚动
+ */
+LRESULT CMapPosWnd::OnMouseMove(WPARAM wParam, LPARAM lParam)
+{
+	return ::DefWindowProc(m_hWnd, WM_MOUSEMOVE, wParam, lParam);
+}
+
+/*
+ * WM_LBUTTONDOWN
+ * 鼠标左键按下
+ */
+LRESULT CMapPosWnd::OnLButtonDown(WPARAM wParam, LPARAM lParam)
+{
+	POINT pt, ptNew, ptPox;
+	pt.x = LOWORD(lParam);
+	pt.y = HIWORD(lParam);
+
+	RECT rcClient, rcLastViewPort;
+	GetClientRect(m_hWnd, &rcClient);
+	float scale = max(m_nStageCx / (float)(rcClient.right - rcClient.left), m_nStageCy / (float)(rcClient.bottom - rcClient.top));
+	CopyRect(&rcLastViewPort, &m_rcViewPort);
+
+
+	// 检测点击坐标是否在某一子项上,如是,则高亮显示
+	int nRet = HighTest(pt);
+	SetFocus(m_hWnd);
+
+
+	// 捕捉鼠标消息,检测是否拖动
+	if (nRet == HT_INDICATOR) {
+	
+		if (::GetCapture() == NULL) {
+			SetCapture(m_hWnd);
+			ASSERT(m_hWnd == GetCapture());
+			AfxLockTempMaps();
+			for (;;) {
+				MSG msg;
+				VERIFY(::GetMessage(&msg, NULL, 0, 0));
+
+				if (GetCapture() != m_hWnd) break;
+
+				switch (msg.message)
+				{
+				case WM_MOUSEMOVE:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+					ptPox.x = long(rcLastViewPort.left + (ptNew.x - pt.x) * scale);
+					ptPox.x = max(0, min(ptPox.x, m_nStageCx - (rcLastViewPort.right - rcLastViewPort.left)));
+					ptPox.y = long(rcLastViewPort.top + (ptNew.y - pt.y) * scale);
+					ptPox.y = max(0, min(ptPox.y, m_nStageCy - (rcLastViewPort.bottom - rcLastViewPort.top)));
+					Notify(MAPPOSWND_POSCHANGED, ptPox.x, ptPox.y);
+					break;
+
+				case WM_LBUTTONUP:
+					ptNew = msg.pt;
+					::ScreenToClient(m_hWnd, &ptNew);
+					ptPox.x = long(rcLastViewPort.left + (ptNew.x - pt.x) * scale);
+					ptPox.x = max(0, min(ptPox.x, m_nStageCx - (rcLastViewPort.right - rcLastViewPort.left)));
+					ptPox.y = long(rcLastViewPort.top + (ptNew.y - pt.y) * scale);
+					ptPox.y = max(0, min(ptPox.y, m_nStageCy - (rcLastViewPort.bottom - rcLastViewPort.top)));
+					Notify(MAPPOSWND_POSCHANGED, ptPox.x, ptPox.y);
+
+					ReleaseCapture();
+					::InvalidateRect(m_hWnd, &rcClient, TRUE);
+					goto ExitLoop;
+
+				case WM_KEYDOWN:
+					if (msg.wParam != VK_ESCAPE)
+						break;
+
+				default:
+					DispatchMessage(&msg);
+					break;
+				}
+			}
+
+			ReleaseCapture();
+		ExitLoop:
+			AfxUnlockTempMaps(FALSE);
+		}
+	}
+
+
+	return ::DefWindowProc(m_hWnd, WM_LBUTTONDOWN, wParam, lParam);
+}
+
+/*
+ * WM_LBUTTONDBLCLK
+ * 鼠标左键双击
+ */
+LRESULT CMapPosWnd::OnLButtonDblclk(WPARAM wParam, LPARAM lParam)
+{
+	POINT pt, ptDest;
+	pt.x = LOWORD(lParam);
+	pt.y = HIWORD(lParam);
+
+	RECT rcClient, rcLast;
+	GetClientRect(m_hWnd, &rcClient);
+	rcLast = { 0, 0, 0, 0 };
+
+	// 检测点击坐标是否在空白处
+	// 小窗坐标转换为对应坐标
+	int nRet = HighTest(pt);
+	if (nRet == HT_NOWHERE || nRet == HT_INDICATOR) {
+		float scale = max(m_nStageCx / (float)(rcClient.right - rcClient.left), m_nStageCy / (float)(rcClient.bottom - rcClient.top));
+		ptDest.x = (int)(scale * pt.x) - (m_rcViewPort.right - m_rcViewPort.left) / 2;
+		ptDest.x = max(0, min(ptDest.x, m_nStageCx - (m_rcViewPort.right - m_rcViewPort.left)));
+		ptDest.y = (int)(scale * pt.y) - (m_rcViewPort.bottom - m_rcViewPort.top) / 2;
+		ptDest.y = max(0, min(ptDest.y, m_nStageCy - (m_rcViewPort.bottom - m_rcViewPort.top)));
+		Notify(MAPPOSWND_POSCHANGED, ptDest.x, ptDest.y);
+	}
+
+
+	return ::DefWindowProc(m_hWnd, WM_LBUTTONDBLCLK, wParam, lParam);
+}
+
+/*
+ * WM_MOUSEWHEEL
+ * 鼠标滚动
+ */
+LRESULT CMapPosWnd::OnMouseWheel(WPARAM wParam, LPARAM lParam)
+{
+	return ::DefWindowProc(m_hWnd, WM_MOUSEWHEEL, wParam, lParam);
+}
+
+/*
+ * WM_KEYDOWN
+ * 键盘消息,按下按键
+ */
+LRESULT CMapPosWnd::OnKeyDown(WPARAM wParam, LPARAM lParam)
+{
+	BOOL bChanged = FALSE;
+	if (wParam == VK_DELETE) {
+
+	}
+
+
+	if (bChanged) {
+		RECT rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		::InvalidateRect(m_hWnd, &rcClient, TRUE);
+	}
+
+	return ::DefWindowProc(m_hWnd, WM_KEYDOWN, wParam, lParam);
+}
+
+/*
+ * WM_NCPAINT
+ */
+LRESULT CMapPosWnd::OnNcPaint(WPARAM wParam, LPARAM lParam)
+{
+	LRESULT lRet = ::DefWindowProc(m_hWnd, WM_NCPAINT, wParam, lParam);
+
+
+	long styleEx = GetWindowLong(m_hWnd, GWL_EXSTYLE);
+	if ((styleEx & WS_EX_CLIENTEDGE) == WS_EX_CLIENTEDGE) {
+
+		RECT rect, rcClient;
+		GetClientRect(m_hWnd, &rcClient);
+		::ClientToScreen(m_hWnd, (LPPOINT)&rcClient.left);
+		::ClientToScreen(m_hWnd, (LPPOINT)&rcClient.right);
+		GetWindowRect(m_hWnd, &rect);
+		rcClient.right = rect.right - 1;
+		rcClient.bottom = rect.bottom - 1;
+		::OffsetRect(&rcClient, -rect.left, -rect.top);
+
+		rect.right -= rect.left;
+		rect.bottom -= rect.top;
+		rect.left = 0;
+		rect.top = 0;
+
+		HRGN hRgnWnd = CreateRectRgnIndirect(&rect);
+		HRGN hRgnClient = CreateRectRgnIndirect(&rcClient);
+
+		HBRUSH hBrushBK, hBrushFrame;
+		HDC hDC = ::GetWindowDC(m_hWnd);
+		::SelectClipRgn(hDC, hRgnWnd);
+		::ExtSelectClipRgn(hDC, hRgnClient, RGN_DIFF);
+
+		hBrushBK = CreateSolidBrush(m_crBkgnd);
+		::FillRect(hDC, &rect, hBrushBK);
+		DeleteObject(hBrushBK);
+
+		hBrushFrame = CreateSolidBrush(m_crFrame);
+		::FrameRect(hDC, &rect, hBrushFrame);
+
+		::DeleteObject(hRgnWnd);
+		::DeleteObject(hRgnClient);
+		DeleteObject(hBrushFrame);
+		::ReleaseDC(m_hWnd, hDC);
+	}
+
+	return lRet;
+}
+
+/*
+ * WM_PAINT
+ */
+LRESULT CMapPosWnd::OnPaint(WPARAM wParam, LPARAM lParam)
+{
+	HDC hDC, hMemDC;
+	HBITMAP hBitmap;
+	RECT rcClient;
+	CString strText;
+	HBRUSH hBrushBK;
+
+
+	// BeginPaint
+	PAINTSTRUCT ps;
+	hDC = BeginPaint(m_hWnd, &ps);
+	GetClientRect(m_hWnd, &rcClient);
+
+	hMemDC = ::CreateCompatibleDC(hDC);
+	hBitmap = ::CreateCompatibleBitmap(hDC, rcClient.right - rcClient.left,
+		rcClient.bottom - rcClient.top);
+	::SelectObject(hMemDC, hBitmap);
+
+
+	// 背景颜色
+	hBrushBK = CreateSolidBrush(m_crBkgnd);
+	::FillRect(hMemDC, &rcClient, hBrushBK);
+	DeleteObject(hBrushBK);
+
+
+	// 标题
+	{
+		char szTitle[256];
+		GetWindowText(m_hWnd, szTitle, 256);
+		RECT rcTitle;
+		rcTitle.left = rcClient.left + 2;
+		rcTitle.top = rcClient.top + 2;
+		rcTitle.bottom = rcClient.bottom - 2;
+		rcTitle.right = rcClient.right - 2;
+		::DrawText(hMemDC, szTitle, (int)strlen(szTitle), &rcTitle, DT_LEFT | DT_TOP);
+	}
+
+
+	// View port
+	RECT rcViewPort;
+	GetViewPortRect(&rcClient, &rcViewPort);
+	HBRUSH hBrushFrame = CreateSolidBrush(m_crViewPort);
+	::FrameRect(hMemDC, &rcViewPort, hBrushFrame);
+	::DeleteObject(hBrushFrame);
+
+
+	// EndPaint
+	::BitBlt(hDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
+		hMemDC, 0, 0, SRCCOPY);
+	EndPaint(m_hWnd, &ps);
+	::DeleteObject(hBitmap);
+	::DeleteDC(hMemDC);
+
+
+	return 1;
+}
+
+/*
+ * WM_SIZE
+ */
+LRESULT CMapPosWnd::OnSize(WPARAM wParam, LPARAM lParam)
+{
+	LRESULT lRet = ::DefWindowProc(m_hWnd, WM_SIZE, wParam, lParam);
+
+	return lRet;
+}
+
+/*
+ * 设置背景颜色
+ * color -- 背景色
+ */
+void CMapPosWnd::SetBkgndColor(COLORREF color)
+{
+	m_crBkgnd = color;
+}
+
+/*
+ * 边框颜色
+ * color -- 边框色
+ */
+void CMapPosWnd::SetFrameColor(COLORREF color)
+{
+	m_crFrame = color;
+}
+
+
diff --git a/SourceCode/Bond/Servo/MapPosWnd.h b/SourceCode/Bond/Servo/MapPosWnd.h
new file mode 100644
index 0000000..502512e
--- /dev/null
+++ b/SourceCode/Bond/Servo/MapPosWnd.h
@@ -0,0 +1,86 @@
+#pragma once
+#include <functional>
+
+
+#ifndef MAPPOSWND_TAG
+
+#ifdef _WIN32
+
+#define MAPPOSWND_CLASSA		"MapPosWnd"
+#define MAPPOSWND_CLASSW		L"MapPosWnd"
+
+#ifdef UNICODE
+#define MAPPOSWND_CLASS		MAPPOSWND_CLASSW
+#else
+#define MAPPOSWND_CLASS		MAPPOSWND_CLASSA
+#endif
+
+#else
+#define MAPPOSWND_CLASS      "MapPosWnd"
+#endif
+
+
+#define MAPPOSWND_TAG				_T("MAPPOSWND_TAG")
+
+#define MAPPOSWND_FIRST				(0U-5850U)
+#define MAPPOSWND_LAST				(0U-2810U)
+#define MAPPOSWND_POSCHANGED		(MAPPOSWND_FIRST - 1)
+
+typedef struct tagMAPPOSWND_NMHDR
+{
+	NMHDR		nmhdr;
+	DWORD		dwData;
+	DWORD		dwData1;
+	DWORD		dwData2;
+} MAPPOSWND_NMHDR;
+
+#endif
+
+class CMapPosWnd
+{
+public:
+	CMapPosWnd();
+	~CMapPosWnd();
+
+public:
+	static BOOL RegisterWndClass();
+	static CMapPosWnd * FromHandle(HWND hWnd);
+	void SetFrameColor(COLORREF color);
+	void SetBkgndColor(COLORREF color);
+	void SetWndMaxSize(int nMaxSize);
+	void SetStageSize(int cx, int cy, BOOL bInvalidata);
+	void SetViewPort(LPRECT lpRect, BOOL bInvalidata);
+	void GetViewPortRect(LPRECT lprcClient, LPRECT lprcDest);
+
+private:
+	void Init();
+	void Notify(int nCode, int dwData, int dwData1 = 0, int dwData2 = 0);
+	void Release();
+	int HighTest(POINT pt);
+	static CMapPosWnd* Hook(HWND hWnd);
+	static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+	static LRESULT OnNcCreate(HWND hWnd, WPARAM wParam, LPARAM lParam);
+	LRESULT OnDestroy(WPARAM wParam, LPARAM lParam);
+	LRESULT OnTimer(WPARAM wParam, LPARAM lParam);
+	LRESULT OnNcPaint(WPARAM wParam, LPARAM lParam);
+	LRESULT OnPaint(WPARAM wParam, LPARAM lParam);
+	LRESULT OnMouseMove(WPARAM wParam, LPARAM lParam);
+	LRESULT OnLButtonDown(WPARAM wParam, LPARAM lParam);
+	LRESULT OnLButtonDblclk(WPARAM wParam, LPARAM lParam);
+	LRESULT OnMouseWheel(WPARAM wParam, LPARAM lParam);
+	LRESULT OnKeyDown(WPARAM wParam, LPARAM lParam);
+	LRESULT OnSize(WPARAM wParam, LPARAM lParam);
+
+private:
+	HWND		m_hWnd;
+	COLORREF	m_crBkgnd;
+	COLORREF	m_crFrame;
+	COLORREF	m_crViewPort;
+
+private:
+	int			m_nWndMaxSize;
+	int			m_nStageCx;
+	int			m_nStageCy;
+	RECT		m_rcViewPort;
+};
+
diff --git a/SourceCode/Bond/Servo/Servo.cpp b/SourceCode/Bond/Servo/Servo.cpp
index d88737c..1bace55 100644
--- a/SourceCode/Bond/Servo/Servo.cpp
+++ b/SourceCode/Bond/Servo/Servo.cpp
@@ -9,6 +9,9 @@
 #include "AlarmManager.h"
 #include "SECSRuntimeManager.h"
 #include "VerticalLine.h"
+#include "EqsGraphWnd.h"
+#include "MapPosWnd.h"
+#include "HmTab.h"
 
 
 // 声明全局变量,用于管理 GDI+ 初始化
@@ -95,6 +98,9 @@
 	// 注册控件
 	CServoGraph::RegisterWndClass();
 	CVerticalLine::RegisterWndClass();
+	CEqsGraphWnd::RegisterWndClass();
+	CMapPosWnd::RegisterWndClass();
+	CHmTab::RegisterWndClass();
 
 
 	// 初始化Rx库
diff --git a/SourceCode/Bond/Servo/Servo.rc b/SourceCode/Bond/Servo/Servo.rc
index de89c3d..99a9fcd 100644
--- a/SourceCode/Bond/Servo/Servo.rc
+++ b/SourceCode/Bond/Servo/Servo.rc
Binary files differ
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj b/SourceCode/Bond/Servo/Servo.vcxproj
index 3aaadd7..8b33129 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj
+++ b/SourceCode/Bond/Servo/Servo.vcxproj
@@ -219,6 +219,10 @@
     <ClInclude Include="CFliper.h" />
     <ClInclude Include="CLoadPort.h" />
     <ClInclude Include="CMeasurement.h" />
+    <ClInclude Include="ColorTransfer.h" />
+    <ClInclude Include="CPageGraph1.h" />
+    <ClInclude Include="CPageGraph2.h" />
+    <ClInclude Include="CPanel.h" />
     <ClInclude Include="CPanelAttributes.h" />
     <ClInclude Include="CPanelEquipment.h" />
     <ClInclude Include="CPanelMaster.h" />
@@ -235,6 +239,8 @@
     <ClInclude Include="Common.h" />
     <ClInclude Include="Configuration.h" />
     <ClInclude Include="Context.h" />
+    <ClInclude Include="EqsGraphWnd.h" />
+    <ClInclude Include="HmTab.h" />
     <ClInclude Include="HsmsAction.h" />
     <ClInclude Include="HsmsPassive.h" />
     <ClInclude Include="Intent.h" />
@@ -242,6 +248,7 @@
     <ClInclude Include="Log.h" />
     <ClInclude Include="LogDlg.h" />
     <ClInclude Include="LogEdit.h" />
+    <ClInclude Include="MapPosWnd.h" />
     <ClInclude Include="Model.h" />
     <ClInclude Include="Resource.h" />
     <ClInclude Include="SECSRuntimeManager.h" />
@@ -280,6 +287,10 @@
     <ClCompile Include="CFliper.cpp" />
     <ClCompile Include="CLoadPort.cpp" />
     <ClCompile Include="CMeasurement.cpp" />
+    <ClCompile Include="ColorTransfer.cpp" />
+    <ClCompile Include="CPageGraph1.cpp" />
+    <ClCompile Include="CPageGraph2.cpp" />
+    <ClCompile Include="CPanel.cpp" />
     <ClCompile Include="CPanelAttributes.cpp" />
     <ClCompile Include="CPanelEquipment.cpp" />
     <ClCompile Include="CPanelMaster.cpp" />
@@ -295,6 +306,8 @@
     <ClCompile Include="CMaster.cpp" />
     <ClCompile Include="Configuration.cpp" />
     <ClCompile Include="Context.cpp" />
+    <ClCompile Include="EqsGraphWnd.cpp" />
+    <ClCompile Include="HmTab.cpp" />
     <ClCompile Include="HsmsAction.cpp" />
     <ClCompile Include="HsmsPassive.cpp" />
     <ClCompile Include="Intent.cpp" />
@@ -302,6 +315,7 @@
     <ClCompile Include="Log.cpp" />
     <ClCompile Include="LogDlg.cpp" />
     <ClCompile Include="LogEdit.cpp" />
+    <ClCompile Include="MapPosWnd.cpp" />
     <ClCompile Include="Model.cpp" />
     <ClCompile Include="SECSRuntimeManager.cpp" />
     <ClCompile Include="SecsTestDlg.cpp" />
diff --git a/SourceCode/Bond/Servo/Servo.vcxproj.filters b/SourceCode/Bond/Servo/Servo.vcxproj.filters
index ec15cdf..ae66fd9 100644
--- a/SourceCode/Bond/Servo/Servo.vcxproj.filters
+++ b/SourceCode/Bond/Servo/Servo.vcxproj.filters
@@ -67,6 +67,13 @@
     <ClCompile Include="CBakeCooling.cpp" />
     <ClCompile Include="CVacuumBake.cpp" />
     <ClCompile Include="Intent.cpp" />
+    <ClCompile Include="CPanel.cpp" />
+    <ClCompile Include="EqsGraphWnd.cpp" />
+    <ClCompile Include="ColorTransfer.cpp" />
+    <ClCompile Include="MapPosWnd.cpp" />
+    <ClCompile Include="HmTab.cpp" />
+    <ClCompile Include="CPageGraph1.cpp" />
+    <ClCompile Include="CPageGraph2.cpp" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="AlarmManager.h" />
@@ -132,6 +139,13 @@
     <ClInclude Include="CBakeCooling.h" />
     <ClInclude Include="CVacuumBake.h" />
     <ClInclude Include="Intent.h" />
+    <ClInclude Include="CPanel.h" />
+    <ClInclude Include="EqsGraphWnd.h" />
+    <ClInclude Include="ColorTransfer.h" />
+    <ClInclude Include="MapPosWnd.h" />
+    <ClInclude Include="HmTab.h" />
+    <ClInclude Include="CPageGraph1.h" />
+    <ClInclude Include="CPageGraph2.h" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="Servo.rc" />
diff --git a/SourceCode/Bond/Servo/ServoDlg.cpp b/SourceCode/Bond/Servo/ServoDlg.cpp
index 6e52d61..fdf1eba 100644
--- a/SourceCode/Bond/Servo/ServoDlg.cpp
+++ b/SourceCode/Bond/Servo/ServoDlg.cpp
@@ -13,28 +13,12 @@
 #include <chrono>
 #include <thread>
 #include <cmath>
+#include "HmTab.h"
 
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
 #endif
-
-// Image
-#define IMAGE_ROBOT				2
-
-#define INDICATE_BONDER1		1
-#define INDICATE_BONDER2		2
-#define INDICATE_FLIPER			3
-#define INDICATE_ALIGNER		4
-#define INDICATE_LPORT4			5
-#define INDICATE_LPORT3			6
-#define INDICATE_LPORT2			7
-#define INDICATE_LPORT1			8
-#define INDICATE_ROBOT_ARM1		9
-#define INDICATE_ROBOT_ARM2		10
-#define INDICATE_VACUUM_BAKE	11
-#define INDICATE_BAKE_COOLING	12
-#define INDICATE_MEASUREMENT	13
 
 
 /* 创建终端的定时器 */
@@ -86,7 +70,6 @@
 	m_hbrBkgnd = nullptr;
 	m_bShowLogWnd = FALSE;
 	m_bShowAlarmWnd = FALSE;
-	m_bIsRobotMoving = FALSE;
 	m_pLogDlg = nullptr;
 	m_pAlarmDlg = nullptr;
 	m_pTerminalDisplayDlg = nullptr;
@@ -94,6 +77,8 @@
 	m_pPanelMaster = nullptr;
 	m_pPanelEquipment = nullptr;
 	m_pPanelAttributes = nullptr;
+	m_pPageGraph1 = nullptr;
+	m_pPageGraph2 = nullptr;
 }
 
 void CServoDlg::DoDataExchange(CDataExchange* pDX)
@@ -135,11 +120,10 @@
 	ON_COMMAND(ID_MENU_HELP_ABOUT, &CServoDlg::OnMenuHelpAbout)
 	ON_WM_INITMENUPOPUP()
 	ON_WM_TIMER()
-	ON_WM_ERASEBKGND()
 	ON_BN_CLICKED(IDC_BUTTON_ALARM, &CServoDlg::OnBnClickedButtonAlarm)
 	ON_BN_CLICKED(IDC_BUTTON_ALARM, &CServoDlg::OnBnClickedButtonAlarm)
-	ON_NOTIFY(BYSERVOGRAPH_ITEM_CLICKED, IDC_SERVO_GRAPH1, &CServoDlg::OnGraphItemClicked)
 	ON_MESSAGE(ID_MSG_PANEL_RESIZE, OnPanelResize)
+	ON_NOTIFY(BYHMTAB_SEL_CHANGED, IDC_TAB1, &CServoDlg::OnTabSelChanged)
 END_MESSAGE_MAP()
 
 
@@ -160,21 +144,6 @@
 				const char* pszText;
 				if (pAny->getStringValue("text", pszText)) {
 					ShowTerminalText(pszText);
-				}
-			}
-			else if (RX_CODE_EQ_ALIVE == code) {
-				// 通知设备状态
-				SERVO::CEquipment* pEquipment = nullptr;
-				if (pAny->getPtrValue("ptr", (void*&)pEquipment)) {
-					if (pEquipment != nullptr) {
-						int nID = pEquipment->getID();
-						BOOL bAlive = pEquipment->isAlive();
-						if (EQ_ID_EFEM == nID) {
-							DeviceStatus status = bAlive ? DeviceStatus::ONLINE : DeviceStatus::OFFLINE;
-							UpdateDeviceStatus(INDICATE_ROBOT_ARM1, status);
-							UpdateDeviceStatus(INDICATE_ROBOT_ARM2, status);
-						}
-					}
 				}
 			}
 			else if (RX_CODE_SELECT_EQUIPMENT == code) {
@@ -256,89 +225,20 @@
 
 
 
-	// 图示
-	m_pGraph = CServoGraph::Hook(GetDlgItem(IDC_SERVO_GRAPH1)->GetSafeHwnd());
-	CString strPath;
-	strPath.Format(_T("%s\\res\\Servo001.bmp"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
-	m_pGraph->AddImage(1, (LPTSTR)(LPCTSTR)strPath, 0, 0);
+	// Tab
+	m_pPageGraph1 = new CPageGraph1();
+	m_pPageGraph1->Create(IDD_PAGE_GRAPH1, this);
+	m_pPageGraph2 = new CPageGraph2();
+	m_pPageGraph2->Create(IDD_PAGE_GRAPH2, this);
 
-	strPath.Format(_T("%s\\res\\Robot001.bmp"), (LPTSTR)(LPCTSTR)theApp.m_strAppDir);
-	m_pGraph->AddImage(IMAGE_ROBOT, (LPTSTR)(LPCTSTR)strPath, 170, 270);
-
-	// 添加指示器
-	// Bonder
-	m_pGraph->AddIndicateBox(INDICATE_BONDER1, 220, 172, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_BONDER1, "10", "Bonder 1");
-	m_pGraph->AddIndicateBox(INDICATE_BONDER2, 220, 516, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_BONDER2, "11", "Bonder 2");
-
-
-	// 翻转
-	m_pGraph->AddIndicateBox(INDICATE_FLIPER, 338, 172, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_FLIPER, "8", "Fliper");
-
-
-	// 对位
-	m_pGraph->AddIndicateBox(INDICATE_ALIGNER, 428, 172, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_ALIGNER, "7", "Aligner");
-
-
-	// Load port 4
-	m_pGraph->AddIndicateBox(INDICATE_LPORT4, 518, 172, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_LPORT4, "4", "LPort4");
-
-
-	// Load port 3
-	m_pGraph->AddIndicateBox(INDICATE_LPORT3, 606, 172, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_LPORT3, "3", "LPort3");
-
-
-	// Load port 2
-	m_pGraph->AddIndicateBox(INDICATE_LPORT2, 690, 172, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_LPORT2, "2", "LPort2");
-
-
-	// Load port 1
-	m_pGraph->AddIndicateBox(INDICATE_LPORT1, 774, 172, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_LPORT1, "1", "LPort1");
-
-
-	// Robot
-	m_pGraph->AddIndicateBox(INDICATE_ROBOT_ARM1, 190, 294, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_ROBOT_ARM1, "5", "Robot");
-	m_pGraph->AddIndicateBox(INDICATE_ROBOT_ARM2, 243, 294, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_ROBOT_ARM2, "6", "Robot");
-
-
-
-
-	// Vacuum bake
-	m_pGraph->AddIndicateBox(INDICATE_VACUUM_BAKE, 396, 516, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_VACUUM_BAKE, "9", "Vacuum bake");
-
-
-	// Bake cooling
-	m_pGraph->AddIndicateBox(INDICATE_BAKE_COOLING, 566, 516, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_BAKE_COOLING, "12", "Bake cooling");
-
-
-	// 精度检
-	m_pGraph->AddIndicateBox(INDICATE_MEASUREMENT, 737, 516, 48, RGB(22, 22, 22),
-		RGB(255, 127, 39), EQ_BOX_OFFLINE);
-	m_pGraph->SetBoxText(INDICATE_MEASUREMENT, "13", "Measurement");
-
+	CHmTab* m_pTab = CHmTab::Hook(GetDlgItem(IDC_TAB1)->m_hWnd);
+	m_pTab->SetPaddingLeft(20);
+	m_pTab->SetItemMarginLeft(18);
+	m_pTab->AddItem("报表", FALSE);
+	m_pTab->AddItem("日志", TRUE);
+	m_pTab->SetCurSel(0);
+	m_pTab->SetBkgndColor(RGB(222, 222, 222));
+	ShowChildPage(0);
 
 
 	m_pPanelMaster = new CPanelMaster();
@@ -373,13 +273,6 @@
 
 	// 相当于延时调用master的初始化
 	theApp.m_model.m_master.init();
-
-
-	// 绑定数据
-	{
-		SERVO::CEquipment* pEquipment = theApp.m_model.m_master.getEquipment(EQ_ID_EFEM);
-		m_pGraph->SetIndicateBoxData(INDICATE_ROBOT_ARM1, pEquipment);
-	}
 
 
 	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
@@ -572,11 +465,17 @@
 
 void CServoDlg::OnMenuTestMessageClear()
 {
+	/*
 	SERVO::CEquipment* pEquipment = m_pPanelMaster->GetActiveEquipment();
 	if (pEquipment != nullptr) {
 		SERVO::CEqCimMessageClearStep* pStep = (SERVO::CEqCimMessageClearStep*)pEquipment->getStepWithName(STEP_CIM_MESSAGE_CLEAR);
 		pStep->clearCimMessage(2, 3);
 	}
+	*/
+
+	SERVO::CLoadPort* pLoadPort1 = 
+		(SERVO::CLoadPort*)theApp.m_model.m_master.getEquipment(EQ_ID_LOADPORT1);
+	pLoadPort1->outputPanel();
 }
 
 void CServoDlg::OnUpdateMenuTestMessageClear(CCmdUI* pCmdUI)
@@ -659,6 +558,18 @@
 		m_pPanelAttributes = nullptr;
 	}
 	
+	if (m_pPageGraph1 != nullptr) {
+		m_pPageGraph1->DestroyWindow();
+		delete m_pPageGraph1;
+		m_pPageGraph1 = nullptr;
+	}
+
+	if (m_pPageGraph2 != nullptr) {
+		m_pPageGraph2->DestroyWindow();
+		delete m_pPageGraph2;
+		m_pPageGraph2 = nullptr;
+	}
+
 	if (m_hbrBkgnd != nullptr) {
 		::DeleteObject(m_hbrBkgnd);
 	}
@@ -750,142 +661,13 @@
 	m_btnAlarm.Invalidate();
 }
 
-void CServoDlg::UpdateRobotPosition(float percentage)
-{
-	// 限制百分比范围在 [0, 1] 之间
-	if (percentage < 0.0f) percentage = 0.0f;
-	if (percentage > 1.0f) percentage = 1.0f;
-
-	// 根据百分比计算目标 X 坐标
-	int startX = m_pGraph->GetImage(IMAGE_ROBOT)->x;
-	int endX = static_cast<int>(170 + percentage * (700 - 170));
-
-	int arm1Offset = 20;  // 从图片到ARM1的偏移
-	int arm2Offset = 73;  // 从图片到ARM2的偏移
-
-	// 计算移动所需的时间
-	int distance = abs(endX - startX);
-	int duration = static_cast<int>((distance / 100.0) * 1000);
-
-	auto startTime = std::chrono::steady_clock::now();
-	auto endTime = startTime + std::chrono::milliseconds(duration);
-
-	// 开始移动,设置标记
-	m_bIsRobotMoving = TRUE;
-
-	// 开始平滑移动
-	while (std::chrono::steady_clock::now() < endTime) {
-		auto currentTime = std::chrono::steady_clock::now();
-		float progress = std::chrono::duration<float, std::milli>(currentTime - startTime).count() / duration;
-		progress = min(progress, 1.0f);
-
-		// 根据进度计算当前位置
-		int currentX = static_cast<int>(startX + progress * (endX - startX));
-		m_pGraph->UpdateImageCoordinates(IMAGE_ROBOT, currentX, 270);
-		m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, currentX + arm1Offset, 294);
-		m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, currentX + arm2Offset, 294);
-
-		// 刷新界面
-		Invalidate();
-		UpdateWindow();
-
-		// 控制帧率约为 60 FPS
-		std::this_thread::sleep_for(std::chrono::milliseconds(16));
-	}
-
-	// 确保最后位置精确到目标位置
-	m_pGraph->UpdateImageCoordinates(IMAGE_ROBOT, endX, 270);
-	m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, endX + arm1Offset, 294);
-	m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, endX + arm2Offset, 294);
-
-	// 界面重绘
-	Invalidate();
-
-	// 动画结束,设置标记
-	m_bIsRobotMoving = FALSE;
-}
-
-void CServoDlg::RotateRobot(float angleInDegrees)
-{
-	// 将角度转换为弧度
-	float angleInRadians = static_cast<float>(std::acos(-1)) / 180.0f * angleInDegrees;
-
-	// 获取机器人图片的当前坐标和中心
-	auto* pImage = m_pGraph->GetImage(IMAGE_ROBOT);
-	if (!pImage) return;
-
-	// 更新 Rotate 图片的角度,确保角度保持在 [0, 360) 范围内
-	m_pGraph->UpdateImageAngle(IMAGE_ROBOT, static_cast<float>(fmod(pImage->angle + angleInDegrees + 360, 360)));
-
-	int cx = pImage->x + pImage->bmWidth / 2;  // 图片中心 X
-	int cy = pImage->y + pImage->bmHeight / 2; // 图片中心 Y
-
-	// 旋转指示框的坐标
-	auto* pRobot1 = m_pGraph->GetIndicateBox(INDICATE_ROBOT_ARM1);
-	auto* pRobot2 = m_pGraph->GetIndicateBox(INDICATE_ROBOT_ARM2);
-
-	if (pRobot1 && pRobot2) {
-		int newArmX1 = pImage->x + 20;
-		int newArmY1 = 294;
-
-		int newArmX2 = pImage->x + 73;
-		int newArmY2 = 294;
-
-		if (angleInDegrees != 0.0f) {
-			// 计算指示框1的新坐标
-			newArmX1 = static_cast<int>(cx + (pRobot1->x - cx) * cos(angleInRadians) - (pRobot1->y - cy) * sin(angleInRadians));
-			newArmY1 = static_cast<int>(cy + (pRobot1->x - cx) * sin(angleInRadians) + (pRobot1->y - cy) * cos(angleInRadians));
-
-			// 计算指示框2的新坐标
-			newArmX2 = static_cast<int>(cx + (pRobot2->x - cx) * cos(angleInRadians) - (pRobot2->y - cy) * sin(angleInRadians));
-			newArmY2 = static_cast<int>(cy + (pRobot2->x - cx) * sin(angleInRadians) + (pRobot2->y - cy) * cos(angleInRadians));
-		}
-
-		// 更新指示框的位置
-		m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM1, newArmX1, newArmY1);
-		m_pGraph->UpdateIndicateBoxCoordinates(INDICATE_ROBOT_ARM2, newArmX2, newArmY2);
-	}
-
-	// 强制重绘界面
-	Invalidate();
-}
-
-void CServoDlg::UpdateDeviceStatus(int id, DeviceStatus status)
-{
-	// 根据状态设置颜色
-	COLORREF newBackgroundColor;
-	COLORREF newFrameColor1;
-	COLORREF newFrameColor2;
-
-	switch (status) {
-	case ONLINE:
-		newBackgroundColor = EQ_BOX_ONLINE;
-		newFrameColor1 = EQ_BOX_FRAME1;
-		newFrameColor2 = EQ_BOX_FRAME2;
-		break;
-	case OFFLINE:
-		newBackgroundColor = RGB(222, 222, 222);
-		newFrameColor1 = EQ_BOX_FRAME1;
-		newFrameColor2 = EQ_BOX_FRAME2;
-		break;
-	default:
-		newBackgroundColor = RGB(255, 255, 255); // 默认白色背景
-		newFrameColor1 = RGB(0, 0, 0);           // 默认黑色框架1
-		newFrameColor2 = RGB(0, 0, 0);           // 默认黑色框架2
-		break;
-	}
-
-	m_pGraph->UpdateIndicateBox1Colors(id, newBackgroundColor, newFrameColor1, newFrameColor2);
-
-	// 刷新界面
-	Invalidate();
-	UpdateWindow();
-}
-
 void CServoDlg::OnSize(UINT nType, int cx, int cy)
 {
 	CDialogEx::OnSize(nType, cx, cy);
-	if (GetDlgItem(IDC_SERVO_GRAPH1) == nullptr) return;
+	if (GetDlgItem(IDC_TAB1) == nullptr) return;
+	if (m_pPageGraph1 == nullptr) return;
+	if (m_pPageGraph2 == nullptr) return;
+	
 	Resize();
 	Invalidate();
 }
@@ -918,11 +700,16 @@
 		x += nPanelWidth;
 	}
 
-	pItem = GetDlgItem(IDC_SERVO_GRAPH1);
-	pItem->GetClientRect(&rcItem);
-	pItem->MoveWindow(x, y, rcItem.Width(), rcItem.Height());
+
+	pItem = GetDlgItem(IDC_TAB1);
+	pItem->GetWindowRect(rcItem);
+	pItem->MoveWindow(x, y, rcClient.Width() - x, rcItem.Height());
 	y += rcItem.Height();
-	y += 8;
+
+
+	m_pPageGraph1->MoveWindow(x, y, rcClient.Width() - x, rcClient.Height());
+	m_pPageGraph2->MoveWindow(x, y, rcClient.Width() - x, rcClient.Height());
+
 
 	x = rcClient.right - 8;
 	pItem = GetDlgItem(IDC_BUTTON_LOG);
@@ -1013,31 +800,6 @@
 	CDialogEx::OnTimer(nIDEvent);
 }
 
-
-BOOL CServoDlg::OnEraseBkgnd(CDC* pDC)
-{
-	// TODO: 在此添加消息处理程序代码和/或调用默认值
-	if (m_bIsRobotMoving) {
-		// 禁止刷新背景,避免闪烁
-		return TRUE;
-	}
-	
-	return CDialogEx::OnEraseBkgnd(pDC);
-}
-
-void CServoDlg::OnGraphItemClicked(NMHDR* pNMHDR, LRESULT* pResult)
-{
-	BYSERVOGRAPH_NMHDR* pGraphNmhdr = reinterpret_cast<BYSERVOGRAPH_NMHDR*>(pNMHDR);
-	CString s; s.Format(_T("OnGraphItemClicked %d"), pGraphNmhdr->dwData);
-	SERVO::CEquipment* pEquipment = (SERVO::CEquipment*)m_pGraph->GetIndicateBoxData(pGraphNmhdr->dwData);
-	if (pEquipment != nullptr) {
-		AfxMessageBox(pEquipment->getName().c_str());
-	}
-	
-	
-	*pResult = 0;
-}
-
 LRESULT CServoDlg::OnPanelResize(WPARAM wParam, LPARAM lParam)
 {
 	int width = wParam;
@@ -1047,3 +809,21 @@
 	return 0;
 }
 
+void CServoDlg::OnTabSelChanged(NMHDR* nmhdr, LRESULT* result)
+{
+	BYHMTAB_NMHDR* pNmhdrex = (BYHMTAB_NMHDR*)nmhdr;
+	ShowChildPage((int)pNmhdrex->dwData);
+
+	*result = 0;
+}
+
+void CServoDlg::ShowChildPage(int index)
+{
+	ASSERT(0 <= index && index < 3);
+	static CWnd* pPages[] = { m_pPageGraph1, m_pPageGraph2 };
+	for (int i = 0; i < 2; i++) {
+		pPages[i]->ShowWindow(i == index ? SW_SHOW : SW_HIDE);
+	}
+}
+
+
diff --git a/SourceCode/Bond/Servo/ServoDlg.h b/SourceCode/Bond/Servo/ServoDlg.h
index 091f0c8..85d8e85 100644
--- a/SourceCode/Bond/Servo/ServoDlg.h
+++ b/SourceCode/Bond/Servo/ServoDlg.h
@@ -3,7 +3,6 @@
 //
 
 #pragma once
-#include "ServoGraph.h"
 #include "BlButton.h"
 #include "LogDlg.h"
 #include "AlarmDlg.h"
@@ -11,12 +10,9 @@
 #include "CPanelMaster.h"
 #include "CPanelEquipment.h"
 #include "CPanelAttributes.h"
+#include "CPageGraph1.h"
+#include "CPageGraph2.h"
 
-
-enum DeviceStatus {
-	ONLINE,       // 在线
-	OFFLINE,      // 离线
-};
 
 // CServoDlg 对话框
 class CServoDlg : public CDialogEx
@@ -34,9 +30,7 @@
 	void Resize();
 	void UpdateLogBtn();
 	void UpdateAlarmBtn();
-	void UpdateRobotPosition(float percentage);
-	void RotateRobot(float angleInDegrees);
-	void UpdateDeviceStatus(int id, DeviceStatus status);
+	void ShowChildPage(int index);
 
 
 private:
@@ -46,6 +40,8 @@
 	CLogDlg* m_pLogDlg;
 	CAlarmDlg* m_pAlarmDlg;
 	CTerminalDisplayDlg* m_pTerminalDisplayDlg;
+	CPageGraph1* m_pPageGraph1;
+	CPageGraph2* m_pPageGraph2;
 
 
 // 对话框数据
@@ -57,13 +53,9 @@
 	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持
 
 
-private:
-	BOOL m_bIsRobotMoving;
-
 // 实现
 protected:
 	HICON m_hIcon;
-	CServoGraph* m_pGraph;
 	COLORREF m_crBkgnd;
 	HBRUSH m_hbrBkgnd;
 	CBlButton m_btnLog;
@@ -107,8 +99,7 @@
 	afx_msg void OnUpdateMenuTestMessageClear(CCmdUI* pCmdUI);
 	afx_msg void OnMenuHelpAbout();
 	afx_msg void OnTimer(UINT_PTR nIDEvent);
-	afx_msg BOOL OnEraseBkgnd(CDC* pDC);
 	afx_msg void OnBnClickedButtonAlarm();
-	afx_msg void OnGraphItemClicked(NMHDR* pNMHDR, LRESULT* pResult);
 	afx_msg LRESULT OnPanelResize(WPARAM wParam, LPARAM lParam);
+	afx_msg void OnTabSelChanged(NMHDR* nmhdr, LRESULT* result);
 };
diff --git a/SourceCode/Bond/Servo/resource.h b/SourceCode/Bond/Servo/resource.h
index 8c81dc6..dd1cd3d 100644
--- a/SourceCode/Bond/Servo/resource.h
+++ b/SourceCode/Bond/Servo/resource.h
Binary files differ

--
Gitblit v1.9.3