"feat:Enable-hover-highlight-and-refactor"
This commit is contained in:
@@ -18,8 +18,8 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
// 이동 경로 정보 추가
|
||||
Point? PrevPosition { get; }
|
||||
string CurrentNodeId { get; }
|
||||
string PrevNodeId { get; }
|
||||
MapNode CurrentNode { get; }
|
||||
MapNode PrevNode { get; }
|
||||
DockingDirection DockingDirection { get; }
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,10 @@
|
||||
using AGVNavigationCore.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using AGVNavigationCore.Models;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace AGVNavigationCore.Controls
|
||||
{
|
||||
@@ -15,7 +17,7 @@ namespace AGVNavigationCore.Controls
|
||||
Focus(); // 포커스 설정
|
||||
|
||||
var worldPoint = ScreenToWorld(e.Location);
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
|
||||
// 에뮬레이터 모드 처리
|
||||
if (_canvasMode == CanvasMode.Emulator)
|
||||
@@ -48,7 +50,11 @@ namespace AGVNavigationCore.Controls
|
||||
// 마지막 선택된 노드 업데이트 (단일 참조용)
|
||||
_selectedNode = _selectedNodes.Count > 0 ? _selectedNodes[_selectedNodes.Count - 1] : null;
|
||||
|
||||
// 다중 선택 이벤트만 발생 (OnNodesSelected에서 단일/다중 구분 처리)
|
||||
// 단일/다중 선택 이벤트 발생
|
||||
if (_selectedNodes.Count == 1)
|
||||
{
|
||||
NodeSelect?.Invoke(this, _selectedNodes[0], e);
|
||||
}
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
Invalidate();
|
||||
}
|
||||
@@ -59,7 +65,8 @@ namespace AGVNavigationCore.Controls
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(hitNode);
|
||||
|
||||
// NodesSelected 이벤트만 발생 (OnNodesSelected에서 단일/다중 구분 처리)
|
||||
// 단일/다중 선택 이벤트 발생
|
||||
NodeSelect?.Invoke(this, hitNode, e);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
Invalidate();
|
||||
}
|
||||
@@ -94,7 +101,7 @@ namespace AGVNavigationCore.Controls
|
||||
break;
|
||||
|
||||
case EditMode.Connect:
|
||||
HandleConnectClick(hitNode);
|
||||
HandleConnectClick(hitNode as MapNode);
|
||||
break;
|
||||
|
||||
case EditMode.Delete:
|
||||
@@ -110,38 +117,29 @@ namespace AGVNavigationCore.Controls
|
||||
private void UnifiedAGVCanvas_MouseDoubleClick(object sender, MouseEventArgs e)
|
||||
{
|
||||
var worldPoint = ScreenToWorld(e.Location);
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
|
||||
if (hitNode != null)
|
||||
if (hitNode == null) return;
|
||||
|
||||
if (hitNode.Type == NodeType.Normal)
|
||||
{
|
||||
// 노드 타입별 더블클릭 액션
|
||||
switch (hitNode.Type)
|
||||
{
|
||||
case NodeType.Normal:
|
||||
case NodeType.Loader:
|
||||
case NodeType.UnLoader:
|
||||
case NodeType.Clearner:
|
||||
case NodeType.Buffer:
|
||||
case NodeType.Charging:
|
||||
HandleNormalNodeDoubleClick(hitNode);
|
||||
break;
|
||||
|
||||
case NodeType.Label:
|
||||
HandleLabelNodeDoubleClick(hitNode);
|
||||
break;
|
||||
|
||||
case NodeType.Image:
|
||||
HandleImageNodeDoubleClick(hitNode);
|
||||
break;
|
||||
|
||||
default:
|
||||
// 기본 동작: 노드 선택 이벤트 발생
|
||||
_selectedNode = hitNode;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(hitNode);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
break;
|
||||
}
|
||||
HandleNormalNodeDoubleClick(hitNode as MapNode);
|
||||
}
|
||||
else if (hitNode.Type == NodeType.Label)
|
||||
{
|
||||
HandleLabelDoubleClick(hitNode as MapLabel);
|
||||
}
|
||||
else if (hitNode.Type == NodeType.Image)
|
||||
{
|
||||
HandleImageDoubleClick(hitNode as MapImage);
|
||||
}
|
||||
else if (hitNode.Type == NodeType.Mark)
|
||||
{
|
||||
HandleMarkDoubleClick(hitNode as MapMark);
|
||||
}
|
||||
else if (hitNode.Type == NodeType.Magnet)
|
||||
{
|
||||
HandleMagnetDoubleClick(hitNode as MapMagnet);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +148,7 @@ namespace AGVNavigationCore.Controls
|
||||
// RFID 입력창 표시
|
||||
string currentRfid = node.RfidId ?? "";
|
||||
string newRfid = Microsoft.VisualBasic.Interaction.InputBox(
|
||||
$"노드 '{node.Name}'의 RFID를 입력하세요:",
|
||||
$"노드 '{node.RfidId}[{node.Id}]'의 RFID를 입력하세요:",
|
||||
"RFID 설정",
|
||||
currentRfid);
|
||||
|
||||
@@ -167,11 +165,19 @@ namespace AGVNavigationCore.Controls
|
||||
_selectedNodes.Add(node);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
}
|
||||
private void HandleMarkDoubleClick(MapMark label)
|
||||
{
|
||||
//TODO:
|
||||
}
|
||||
private void HandleMagnetDoubleClick(MapMagnet label)
|
||||
{
|
||||
//TODO:
|
||||
}
|
||||
|
||||
private void HandleLabelNodeDoubleClick(MapNode node)
|
||||
private void HandleLabelDoubleClick(MapLabel label)
|
||||
{
|
||||
// 라벨 텍스트 입력창 표시
|
||||
string currentText = node.LabelText ?? "새 라벨";
|
||||
string currentText = label.Text ?? "새 라벨";
|
||||
string newText = Microsoft.VisualBasic.Interaction.InputBox(
|
||||
"라벨 텍스트를 입력하세요:",
|
||||
"라벨 편집",
|
||||
@@ -179,28 +185,24 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(newText) && newText != currentText)
|
||||
{
|
||||
node.LabelText = newText.Trim();
|
||||
label.Text = newText.Trim();
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
// 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
|
||||
_selectedNode = node;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(node);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
_selectedNode = label;
|
||||
LabelDoubleClicked?.Invoke(this, label);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private void HandleImageNodeDoubleClick(MapNode node)
|
||||
private void HandleImageDoubleClick(MapImage image)
|
||||
{
|
||||
// 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
|
||||
_selectedNode = node;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(node);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
_selectedNode = image;
|
||||
|
||||
// 이미지 편집 이벤트 발생 (MainForm에서 처리)
|
||||
ImageNodeDoubleClicked?.Invoke(this, node);
|
||||
ImageDoubleClicked?.Invoke(this, image);
|
||||
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private void UnifiedAGVCanvas_MouseDown(object sender, MouseEventArgs e)
|
||||
@@ -211,24 +213,23 @@ namespace AGVNavigationCore.Controls
|
||||
{
|
||||
if (_editMode == EditMode.Move)
|
||||
{
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
// 1. 노드 선택 확인
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
if (hitNode != null)
|
||||
{
|
||||
_isDragging = true;
|
||||
_isPanning = false; // 🔥 팬 모드 비활성화 - 중요!
|
||||
_isPanning = false;
|
||||
_selectedNode = hitNode;
|
||||
_dragStartPosition = hitNode.Position; // 원래 위치 저장 (고스트용)
|
||||
_dragOffset = new Point(
|
||||
worldPoint.X - hitNode.Position.X,
|
||||
worldPoint.Y - hitNode.Position.Y
|
||||
);
|
||||
_mouseMoveCounter = 0; // 디버그: 카운터 리셋
|
||||
_dragStartPosition = hitNode.Position;
|
||||
_dragOffset = new Point(worldPoint.X - hitNode.Position.X, worldPoint.Y - hitNode.Position.Y);
|
||||
_mouseMoveCounter = 0;
|
||||
Cursor = Cursors.SizeAll;
|
||||
Capture = true; // 🔥 마우스 캡처 활성화 - 이게 핵심!
|
||||
//System.Diagnostics.Debug.WriteLine($"MouseDown: 드래그 시작! Capture={Capture}, isDragging={_isDragging}, isPanning={_isPanning}, Node={hitNode.NodeId}");
|
||||
Capture = true;
|
||||
Invalidate();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 팬 시작 (좌클릭 - 모드에 따라)
|
||||
@@ -250,7 +251,9 @@ namespace AGVNavigationCore.Controls
|
||||
// 컨텍스트 메뉴 (편집 모드에서만)
|
||||
if (_canvasMode == CanvasMode.Edit)
|
||||
{
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
// TODO: 라벨/이미지에 대한 컨텍스트 메뉴도 지원하려면 여기서 hitLabel/hitImage 확인해서 전달
|
||||
// 현재는 ShowContextMenu가 MapNode만 받으므로 노드만 처리
|
||||
ShowContextMenu(e.Location, hitNode);
|
||||
}
|
||||
}
|
||||
@@ -266,9 +269,12 @@ namespace AGVNavigationCore.Controls
|
||||
_mouseMoveCounter++;
|
||||
}
|
||||
|
||||
// 호버 노드 업데이트
|
||||
var newHoveredNode = GetNodeAt(worldPoint);
|
||||
if (newHoveredNode != _hoveredNode)
|
||||
// 호버 업데이트
|
||||
var newHoveredNode = GetItemAt(worldPoint);
|
||||
|
||||
bool hoverChanged = (newHoveredNode != _hoveredNode) ;
|
||||
|
||||
if (hoverChanged)
|
||||
{
|
||||
_hoveredNode = newHoveredNode;
|
||||
Invalidate();
|
||||
@@ -291,24 +297,31 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
else if (_isDragging && _canvasMode == CanvasMode.Edit)
|
||||
{
|
||||
// 드래그 위치 계산
|
||||
var newPosition = new Point(
|
||||
worldPoint.X - _dragOffset.X,
|
||||
worldPoint.Y - _dragOffset.Y
|
||||
);
|
||||
|
||||
// 그리드 스냅
|
||||
if (ModifierKeys.HasFlag(Keys.Control))
|
||||
{
|
||||
newPosition.X = (newPosition.X / GRID_SIZE) * GRID_SIZE;
|
||||
newPosition.Y = (newPosition.Y / GRID_SIZE) * GRID_SIZE;
|
||||
}
|
||||
|
||||
bool moved = false;
|
||||
|
||||
// 노드 드래그
|
||||
if (_selectedNode != null)
|
||||
{
|
||||
var oldPosition = _selectedNode.Position;
|
||||
var newPosition = new Point(
|
||||
worldPoint.X - _dragOffset.X,
|
||||
worldPoint.Y - _dragOffset.Y
|
||||
);
|
||||
|
||||
// 그리드 스냅
|
||||
if (ModifierKeys.HasFlag(Keys.Control))
|
||||
{
|
||||
newPosition.X = (newPosition.X / GRID_SIZE) * GRID_SIZE;
|
||||
newPosition.Y = (newPosition.Y / GRID_SIZE) * GRID_SIZE;
|
||||
}
|
||||
|
||||
_selectedNode.Position = newPosition;
|
||||
NodeMoved?.Invoke(this, _selectedNode);
|
||||
moved = true;
|
||||
}
|
||||
|
||||
if (moved)
|
||||
{
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
Update(); // 🔥 즉시 Paint 이벤트 처리하여 화면 업데이트
|
||||
@@ -409,51 +422,77 @@ namespace AGVNavigationCore.Controls
|
||||
);
|
||||
}
|
||||
|
||||
private MapNode GetNodeAt(Point worldPoint)
|
||||
private NodeBase GetItemAt(Point worldPoint)
|
||||
{
|
||||
if (_nodes == null) return null;
|
||||
|
||||
// 역순으로 검사하여 위에 그려진 노드부터 확인
|
||||
for (int i = _nodes.Count - 1; i >= 0; i--)
|
||||
if (_labels != null)
|
||||
{
|
||||
var node = _nodes[i];
|
||||
if (IsPointInNode(worldPoint, node))
|
||||
return node;
|
||||
// 역순으로 검사하여 위에 그려진 노드부터 확인
|
||||
for (int i = _labels.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var node = _labels[i];
|
||||
if (IsPointInNode(worldPoint, node))
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
if (_nodes != null)
|
||||
{
|
||||
// 역순으로 검사하여 위에 그려진 노드부터 확인
|
||||
for (int i = _nodes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var node = _nodes[i];
|
||||
if (IsPointInNode(worldPoint, node))
|
||||
return node;
|
||||
}
|
||||
}
|
||||
if (_images != null)
|
||||
{
|
||||
// 역순으로 검사하여 위에 그려진 노드부터 확인
|
||||
for (int i = _images.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var node = _images[i];
|
||||
if (IsPointInNode(worldPoint, node))
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsPointInNode(Point point, MapNode node)
|
||||
private bool IsPointInNode(Point point, NodeBase node)
|
||||
{
|
||||
switch (node.Type)
|
||||
if (node is MapLabel label)
|
||||
{
|
||||
case NodeType.Label:
|
||||
return IsPointInLabelNode(point, node);
|
||||
case NodeType.Image:
|
||||
return IsPointInImageNode(point, node);
|
||||
default:
|
||||
return IsPointInCircularNode(point, node);
|
||||
return IsPointInLabel(point, label);
|
||||
}
|
||||
if (node is MapImage image)
|
||||
{
|
||||
return IsPointInImage(point, image);
|
||||
}
|
||||
// 라벨과 이미지는 별도 리스트로 관리되므로 여기서 처리하지 않음
|
||||
// 하지만 혹시 모를 하위 호환성을 위해 타입 체크는 유지하되,
|
||||
// 실제 로직은 CircularNode 등으로 분기
|
||||
return IsPointInCircularNode(point, node as MapNode);
|
||||
}
|
||||
|
||||
private bool IsPointInCircularNode(Point point, MapNode node)
|
||||
{
|
||||
switch (node.Type)
|
||||
switch (node.StationType)
|
||||
{
|
||||
case NodeType.Loader:
|
||||
case NodeType.UnLoader:
|
||||
case NodeType.Clearner:
|
||||
case NodeType.Buffer:
|
||||
case StationType.Loader:
|
||||
case StationType.UnLoader:
|
||||
case StationType.Clearner:
|
||||
case StationType.Buffer:
|
||||
return IsPointInPentagon(point, node);
|
||||
case NodeType.Charging:
|
||||
case StationType.Charger:
|
||||
return IsPointInTriangle(point, node);
|
||||
default:
|
||||
return IsPointInCircle(point, node);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPointInCircle(Point point, MapNode node)
|
||||
private bool IsPointInCircle(Point point, NodeBase node)
|
||||
{
|
||||
// 화면에서 최소 20픽셀 정도의 히트 영역을 확보하되, 노드 크기보다 작아지지 않게 함
|
||||
var minHitRadiusInScreen = 20;
|
||||
@@ -466,7 +505,7 @@ namespace AGVNavigationCore.Controls
|
||||
return distance <= hitRadius;
|
||||
}
|
||||
|
||||
private bool IsPointInPentagon(Point point, MapNode node)
|
||||
private bool IsPointInPentagon(Point point, NodeBase node)
|
||||
{
|
||||
// 화면에서 최소 20픽셀 정도의 히트 영역을 확보
|
||||
var minHitRadiusInScreen = 20;
|
||||
@@ -487,7 +526,7 @@ namespace AGVNavigationCore.Controls
|
||||
return IsPointInPolygon(point, points);
|
||||
}
|
||||
|
||||
private bool IsPointInTriangle(Point point, MapNode node)
|
||||
private bool IsPointInTriangle(Point point, NodeBase node)
|
||||
{
|
||||
// 화면에서 최소 20픽셀 정도의 히트 영역을 확보하되, 노드 크기보다 작아지지 않게 함
|
||||
var minHitRadiusInScreen = 20;
|
||||
@@ -532,38 +571,68 @@ namespace AGVNavigationCore.Controls
|
||||
return inside;
|
||||
}
|
||||
|
||||
private bool IsPointInLabelNode(Point point, MapNode node)
|
||||
private bool IsPointInLabel(Point point, MapLabel label)
|
||||
{
|
||||
var text = string.IsNullOrEmpty(node.LabelText) ? node.NodeId : node.LabelText;
|
||||
var text = string.IsNullOrEmpty(label.Text) ? label.Id : label.Text;
|
||||
|
||||
// 임시 Graphics로 텍스트 크기 측정
|
||||
using (var tempBitmap = new Bitmap(1, 1))
|
||||
using (var tempGraphics = Graphics.FromImage(tempBitmap))
|
||||
// Graphics 객체 임시 생성 (Using CreateGraphics is faster than new Bitmap)
|
||||
using (var g = this.CreateGraphics())
|
||||
{
|
||||
var font = new Font(node.FontFamily, node.FontSize, node.FontStyle);
|
||||
var textSize = tempGraphics.MeasureString(text, font);
|
||||
// Font 생성 로직: 사용자 정의 폰트가 있으면 생성, 없으면 기본 폰트 사용 (Dispose 주의)
|
||||
Font fontToUse = null;
|
||||
bool shouldDisposeFont = false;
|
||||
|
||||
var textRect = new Rectangle(
|
||||
(int)(node.Position.X - textSize.Width / 2),
|
||||
(int)(node.Position.Y - textSize.Height / 2),
|
||||
(int)textSize.Width,
|
||||
(int)textSize.Height
|
||||
);
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(label.FontFamily) || label.FontSize <= 0)
|
||||
{
|
||||
fontToUse = this.Font;
|
||||
shouldDisposeFont = false; // 컨트롤 폰트는 Dispose하면 안됨
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
fontToUse = new Font(label.FontFamily, label.FontSize, label.FontStyle);
|
||||
shouldDisposeFont = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
fontToUse = this.Font;
|
||||
shouldDisposeFont = false;
|
||||
}
|
||||
}
|
||||
|
||||
font.Dispose();
|
||||
return textRect.Contains(point);
|
||||
var textSize = g.MeasureString(text, fontToUse);
|
||||
|
||||
var textRect = new Rectangle(
|
||||
(int)(label.Position.X - textSize.Width / 2),
|
||||
(int)(label.Position.Y - textSize.Height / 2),
|
||||
(int)textSize.Width,
|
||||
(int)textSize.Height
|
||||
);
|
||||
|
||||
return textRect.Contains(point);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (shouldDisposeFont && fontToUse != null)
|
||||
{
|
||||
fontToUse.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPointInImageNode(Point point, MapNode node)
|
||||
private bool IsPointInImage(Point point, MapImage image)
|
||||
{
|
||||
var displaySize = node.GetDisplaySize();
|
||||
var displaySize = image.GetDisplaySize();
|
||||
if (displaySize.IsEmpty)
|
||||
displaySize = new Size(50, 50); // 기본 크기
|
||||
|
||||
var imageRect = new Rectangle(
|
||||
node.Position.X - displaySize.Width / 2,
|
||||
node.Position.Y - displaySize.Height / 2,
|
||||
image.Position.X - displaySize.Width / 2,
|
||||
image.Position.Y - displaySize.Height / 2,
|
||||
displaySize.Width,
|
||||
displaySize.Height
|
||||
);
|
||||
@@ -571,6 +640,32 @@ namespace AGVNavigationCore.Controls
|
||||
return imageRect.Contains(point);
|
||||
}
|
||||
|
||||
//private MapLabel GetLabelAt(Point worldPoint)
|
||||
//{
|
||||
// if (_labels == null) return null;
|
||||
// // 역순으로 검사
|
||||
// for (int i = _labels.Count - 1; i >= 0; i--)
|
||||
// {
|
||||
// var label = _labels[i];
|
||||
// if (IsPointInLabel(worldPoint, label))
|
||||
// return label;
|
||||
// }
|
||||
// return null;
|
||||
//}
|
||||
|
||||
//private MapImage GetImageAt(Point worldPoint)
|
||||
//{
|
||||
// if (_images == null) return null;
|
||||
// // 역순으로 검사
|
||||
// for (int i = _images.Count - 1; i >= 0; i--)
|
||||
// {
|
||||
// var image = _images[i];
|
||||
// if (IsPointInImage(worldPoint, image))
|
||||
// return image;
|
||||
// }
|
||||
// return null;
|
||||
//}
|
||||
|
||||
private IAGV GetAGVAt(Point worldPoint)
|
||||
{
|
||||
if (_agvList == null) return null;
|
||||
@@ -590,7 +685,7 @@ namespace AGVNavigationCore.Controls
|
||||
});
|
||||
}
|
||||
|
||||
private void HandleSelectClick(MapNode hitNode, Point worldPoint)
|
||||
private void HandleSelectClick(NodeBase hitNode, Point worldPoint)
|
||||
{
|
||||
if (hitNode != null)
|
||||
{
|
||||
@@ -605,8 +700,8 @@ namespace AGVNavigationCore.Controls
|
||||
{
|
||||
// 연결선을 클릭했을 때 삭제 확인
|
||||
var (fromNode, toNode) = connection.Value;
|
||||
string fromDisplay = !string.IsNullOrEmpty(fromNode.RfidId) ? fromNode.RfidId : fromNode.NodeId;
|
||||
string toDisplay = !string.IsNullOrEmpty(toNode.RfidId) ? toNode.RfidId : toNode.NodeId;
|
||||
string fromDisplay = !string.IsNullOrEmpty(fromNode.RfidId) ? fromNode.RfidId : fromNode.Id;
|
||||
string toDisplay = !string.IsNullOrEmpty(toNode.RfidId) ? toNode.RfidId : toNode.Id;
|
||||
|
||||
var result = MessageBox.Show(
|
||||
$"연결을 삭제하시겠습니까?\n\n{fromDisplay} ↔ {toDisplay}",
|
||||
@@ -617,13 +712,13 @@ namespace AGVNavigationCore.Controls
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
// 단일 연결 삭제 (어느 방향에 저장되어 있는지 확인 후 삭제)
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.NodeId))
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.Id))
|
||||
{
|
||||
fromNode.RemoveConnection(toNode.NodeId);
|
||||
fromNode.RemoveConnection(toNode.Id);
|
||||
}
|
||||
else if (toNode.ConnectedNodes.Contains(fromNode.NodeId))
|
||||
else if (toNode.ConnectedNodes.Contains(fromNode.Id))
|
||||
{
|
||||
toNode.RemoveConnection(fromNode.NodeId);
|
||||
toNode.RemoveConnection(fromNode.Id);
|
||||
}
|
||||
|
||||
// 이벤트 발생
|
||||
@@ -660,9 +755,8 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
var newNode = new MapNode
|
||||
{
|
||||
NodeId = newNodeId,
|
||||
Position = worldPoint,
|
||||
Type = NodeType.Normal
|
||||
Id = newNodeId,
|
||||
Position = worldPoint
|
||||
};
|
||||
|
||||
_nodes.Add(newNode);
|
||||
@@ -681,20 +775,22 @@ namespace AGVNavigationCore.Controls
|
||||
worldPoint.Y = (worldPoint.Y / GRID_SIZE) * GRID_SIZE;
|
||||
}
|
||||
|
||||
// 고유한 NodeId 생성
|
||||
// 고유한 NodeId 생성 (라벨도 ID 공유 권장)
|
||||
string newNodeId = GenerateUniqueNodeId();
|
||||
|
||||
var newNode = new MapNode
|
||||
var newLabel = new MapLabel
|
||||
{
|
||||
NodeId = newNodeId,
|
||||
Id = newNodeId,
|
||||
Position = worldPoint,
|
||||
Type = NodeType.Label,
|
||||
Name = "새 라벨"
|
||||
Text = "New Label",
|
||||
FontSize = 10,
|
||||
FontFamily = "Arial"
|
||||
};
|
||||
|
||||
_nodes.Add(newNode);
|
||||
if (_labels == null) _labels = new List<MapLabel>();
|
||||
_labels.Add(newLabel);
|
||||
|
||||
NodeAdded?.Invoke(this, newNode);
|
||||
//NodeAdded?.Invoke(this, newNode); // TODO: 라벨 추가 이벤트 필요?
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
@@ -711,17 +807,17 @@ namespace AGVNavigationCore.Controls
|
||||
// 고유한 NodeId 생성
|
||||
string newNodeId = GenerateUniqueNodeId();
|
||||
|
||||
var newNode = new MapNode
|
||||
var newImage = new MapImage
|
||||
{
|
||||
NodeId = newNodeId,
|
||||
Id = newNodeId,
|
||||
Position = worldPoint,
|
||||
Type = NodeType.Image,
|
||||
Name = "새 이미지"
|
||||
Name = "New Image"
|
||||
};
|
||||
|
||||
_nodes.Add(newNode);
|
||||
if (_images == null) _images = new List<MapImage>();
|
||||
_images.Add(newImage);
|
||||
|
||||
NodeAdded?.Invoke(this, newNode);
|
||||
//NodeAdded?.Invoke(this, newNode); // TODO: 이미지 추가 이벤트 필요?
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
@@ -739,7 +835,9 @@ namespace AGVNavigationCore.Controls
|
||||
nodeId = $"N{counter:D3}";
|
||||
counter++;
|
||||
}
|
||||
while (_nodes.Any(n => n.NodeId == nodeId));
|
||||
while (_nodes.Any(n => n.Id == nodeId) ||
|
||||
(_labels != null && _labels.Any(l => l.Id == nodeId)) ||
|
||||
(_images != null && _images.Any(i => i.Id == nodeId)));
|
||||
|
||||
_nodeCounter = counter;
|
||||
return nodeId;
|
||||
@@ -776,7 +874,7 @@ namespace AGVNavigationCore.Controls
|
||||
// 연결된 모든 연결선도 제거
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
node.RemoveConnection(hitNode.NodeId);
|
||||
node.RemoveConnection(hitNode.Id);
|
||||
}
|
||||
|
||||
_nodes.Remove(hitNode);
|
||||
@@ -792,13 +890,13 @@ namespace AGVNavigationCore.Controls
|
||||
private void CreateConnection(MapNode fromNode, MapNode toNode)
|
||||
{
|
||||
// 중복 연결 체크 (양방향)
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.NodeId) ||
|
||||
toNode.ConnectedNodes.Contains(fromNode.NodeId))
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.Id) ||
|
||||
toNode.ConnectedNodes.Contains(fromNode.Id))
|
||||
return;
|
||||
|
||||
// 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록)
|
||||
fromNode.AddConnection(toNode.NodeId);
|
||||
toNode.AddConnection(fromNode.NodeId);
|
||||
fromNode.AddConnection(toNode.Id);
|
||||
toNode.AddConnection(fromNode.Id);
|
||||
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
@@ -810,20 +908,26 @@ namespace AGVNavigationCore.Controls
|
||||
return (float)Math.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
private void ShowContextMenu(Point location, MapNode hitNode)
|
||||
private void ShowContextMenu(Point location, NodeBase hitItem)
|
||||
{
|
||||
_contextMenu.Items.Clear();
|
||||
|
||||
if (hitNode != null)
|
||||
if (hitItem != null)
|
||||
{
|
||||
_contextMenu.Items.Add("노드 속성...", null, (s, e) =>
|
||||
string typeName = "항목";
|
||||
if (hitItem is MapNode) typeName = "노드";
|
||||
else if (hitItem is MapLabel) typeName = "라벨";
|
||||
else if (hitItem is MapImage) typeName = "이미지";
|
||||
|
||||
_contextMenu.Items.Add($"{typeName} 속성...", null, (s, e) =>
|
||||
{
|
||||
_selectedNode = hitNode;
|
||||
_selectedNode = hitItem;
|
||||
_selectedNodes.Clear();
|
||||
_selectedNodes.Add(hitNode);
|
||||
_selectedNodes.Add(hitItem);
|
||||
NodesSelected?.Invoke(this, _selectedNodes);
|
||||
Invalidate();
|
||||
});
|
||||
_contextMenu.Items.Add("노드 삭제", null, (s, e) => HandleDeleteClick(hitNode));
|
||||
_contextMenu.Items.Add($"{typeName} 삭제", null, (s, e) => HandleDeleteClick(hitItem));
|
||||
_contextMenu.Items.Add("-");
|
||||
}
|
||||
|
||||
@@ -848,13 +952,13 @@ namespace AGVNavigationCore.Controls
|
||||
var (fromNode, toNode) = connection.Value;
|
||||
|
||||
// 단일 연결 삭제 (어느 방향에 저장되어 있는지 확인 후 삭제)
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.NodeId))
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.Id))
|
||||
{
|
||||
fromNode.RemoveConnection(toNode.NodeId);
|
||||
fromNode.RemoveConnection(toNode.Id);
|
||||
}
|
||||
else if (toNode.ConnectedNodes.Contains(fromNode.NodeId))
|
||||
else if (toNode.ConnectedNodes.Contains(fromNode.Id))
|
||||
{
|
||||
toNode.RemoveConnection(fromNode.NodeId);
|
||||
toNode.RemoveConnection(fromNode.Id);
|
||||
}
|
||||
|
||||
// 이벤트 발생
|
||||
@@ -867,13 +971,14 @@ namespace AGVNavigationCore.Controls
|
||||
private (MapNode From, MapNode To)? GetConnectionAt(Point worldPoint)
|
||||
{
|
||||
const int CONNECTION_HIT_TOLERANCE = 10;
|
||||
if (_nodes == null) return null;
|
||||
|
||||
// 모든 연결선을 확인하여 클릭한 위치와 가장 가까운 연결선 찾기
|
||||
foreach (var fromNode in _nodes)
|
||||
{
|
||||
foreach (var toNodeId in fromNode.ConnectedNodes)
|
||||
{
|
||||
var toNode = _nodes.FirstOrDefault(n => n.NodeId == toNodeId);
|
||||
var toNode = _nodes.FirstOrDefault(n => n.Id == toNodeId);
|
||||
if (toNode != null)
|
||||
{
|
||||
// 연결선과 클릭 위치 간의 거리 계산
|
||||
@@ -889,6 +994,49 @@ namespace AGVNavigationCore.Controls
|
||||
return null;
|
||||
}
|
||||
|
||||
private void HandleDeleteClick(NodeBase item)
|
||||
{
|
||||
if (item == null) return;
|
||||
|
||||
if (item is MapNode hitNode)
|
||||
{
|
||||
// 연결된 모든 연결선도 제거
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
node.RemoveConnection(hitNode.Id);
|
||||
}
|
||||
|
||||
_nodes.Remove(hitNode);
|
||||
|
||||
if (_selectedNode == hitNode)
|
||||
_selectedNode = null;
|
||||
|
||||
NodeDeleted?.Invoke(this, hitNode);
|
||||
}
|
||||
else if (item is MapLabel label)
|
||||
{
|
||||
if (_labels != null) _labels.Remove(label);
|
||||
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
|
||||
}
|
||||
else if (item is MapImage image)
|
||||
{
|
||||
if (_images != null) _images.Remove(image);
|
||||
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
|
||||
}
|
||||
else if (item is MapMark mark)
|
||||
{
|
||||
if (_marks != null) _marks.Remove(mark);
|
||||
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
|
||||
}
|
||||
else if (item is MapMagnet magnet)
|
||||
{
|
||||
if (_magnets != null) _magnets.Remove(magnet);
|
||||
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
|
||||
}
|
||||
MapChanged?.Invoke(this, EventArgs.Empty);
|
||||
Invalidate();
|
||||
}
|
||||
|
||||
private float CalculatePointToLineDistance(Point point, Point lineStart, Point lineEnd)
|
||||
{
|
||||
// 점에서 선분까지의 거리 계산
|
||||
@@ -928,27 +1076,21 @@ namespace AGVNavigationCore.Controls
|
||||
{
|
||||
string tooltipText = "";
|
||||
|
||||
// 노드 툴팁
|
||||
var hitNode = GetNodeAt(worldPoint);
|
||||
var hitNode = GetItemAt(worldPoint);
|
||||
var hitAGV = GetAGVAt(worldPoint);
|
||||
|
||||
if (hitNode != null)
|
||||
tooltipText = $"노드: {hitNode.Id}\n타입: {hitNode.Type}\n위치: ({hitNode.Position.X}, {hitNode.Position.Y})";
|
||||
else if (hitAGV != null)
|
||||
{
|
||||
tooltipText = $"노드: {hitNode.NodeId}\n타입: {hitNode.Type}\n위치: ({hitNode.Position.X}, {hitNode.Position.Y})";
|
||||
}
|
||||
else
|
||||
{
|
||||
// AGV 툴팁
|
||||
var hitAGV = GetAGVAt(worldPoint);
|
||||
if (hitAGV != null)
|
||||
{
|
||||
var state = _agvStates.ContainsKey(hitAGV.AgvId) ? _agvStates[hitAGV.AgvId] : AGVState.Idle;
|
||||
tooltipText = $"AGV: {hitAGV.AgvId}\n상태: {state}\n배터리: {hitAGV.BatteryLevel:F1}%\n위치: ({hitAGV.CurrentPosition.X}, {hitAGV.CurrentPosition.Y})";
|
||||
}
|
||||
var state = _agvStates.ContainsKey(hitAGV.AgvId) ? _agvStates[hitAGV.AgvId] : AGVState.Idle;
|
||||
tooltipText = $"AGV: {hitAGV.AgvId}\n상태: {state}\n배터리: {hitAGV.BatteryLevel:F1}%\n위치: ({hitAGV.CurrentPosition.X}, {hitAGV.CurrentPosition.Y})";
|
||||
}
|
||||
|
||||
// 툴팁 업데이트 (기존 ToolTip 컨트롤 사용)
|
||||
if (!string.IsNullOrEmpty(tooltipText))
|
||||
// 툴팁 텍스트 갱신 (변경되었을 때만)
|
||||
if (_tooltip != null && _tooltip.GetToolTip(this) != tooltipText)
|
||||
{
|
||||
// ToolTip 설정 (필요시 추가 구현)
|
||||
_tooltip.SetToolTip(this, tooltipText);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1016,7 +1158,7 @@ namespace AGVNavigationCore.Controls
|
||||
/// </summary>
|
||||
public void PanToNode(string nodeId)
|
||||
{
|
||||
var node = _nodes?.FirstOrDefault(n => n.NodeId == nodeId);
|
||||
var node = _nodes?.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (node != null)
|
||||
{
|
||||
PanTo(node.Position);
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using AGVNavigationCore.Models;
|
||||
using AGVNavigationCore.PathFinding;
|
||||
using AGVNavigationCore.PathFinding.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Linq;
|
||||
using System.Reflection.Emit;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace AGVNavigationCore.Controls
|
||||
{
|
||||
@@ -65,10 +67,19 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
// 맵 데이터
|
||||
private List<MapNode> _nodes;
|
||||
private MapNode _selectedNode;
|
||||
private List<MapNode> _selectedNodes; // 다중 선택
|
||||
private MapNode _hoveredNode;
|
||||
private MapNode _destinationNode;
|
||||
private List<MapLabel> _labels; // 추가
|
||||
private List<MapImage> _images; // 추가
|
||||
private List<MapMark> _marks;
|
||||
private List<MapMagnet> _magnets;
|
||||
|
||||
// 선택된 객체들 (나중에 NodeBase로 통일 필요)
|
||||
private NodeBase _selectedNode;
|
||||
|
||||
private List<NodeBase> _selectedNodes; // 다중 선택 (NodeBase로 변경 고려)
|
||||
|
||||
private NodeBase _hoveredNode;
|
||||
|
||||
private NodeBase _destinationNode;
|
||||
|
||||
// AGV 관련
|
||||
private List<IAGV> _agvList;
|
||||
@@ -143,24 +154,31 @@ namespace AGVNavigationCore.Controls
|
||||
private Pen _pathPen;
|
||||
private Pen _agvPen;
|
||||
private Pen _highlightedConnectionPen;
|
||||
private Pen _magnetPen;
|
||||
private Pen _markPen;
|
||||
private ToolTip _tooltip;
|
||||
|
||||
// 컨텍스트 메뉴
|
||||
private ContextMenuStrip _contextMenu;
|
||||
|
||||
// 이벤트
|
||||
public event EventHandler<MapNode> NodeRightClicked;
|
||||
public event EventHandler<NodeBase> NodeRightClicked;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
|
||||
// 맵 편집 이벤트
|
||||
public event EventHandler<MapNode> NodeAdded;
|
||||
public event EventHandler<List<MapNode>> NodesSelected; // 다중 선택 이벤트
|
||||
public event EventHandler<MapNode> NodeDeleted;
|
||||
public event EventHandler<MapNode> NodeMoved;
|
||||
public delegate void NodeSelectHandler(object sender, NodeBase node, MouseEventArgs e);
|
||||
public event NodeSelectHandler NodeSelect;
|
||||
|
||||
public event EventHandler<NodeBase> NodeAdded;
|
||||
public event EventHandler<List<NodeBase>> NodesSelected; // 다중 선택 이벤트
|
||||
public event EventHandler<NodeBase> NodeDeleted;
|
||||
public event EventHandler<NodeBase> NodeMoved;
|
||||
public event EventHandler<(MapNode From, MapNode To)> ConnectionDeleted;
|
||||
public event EventHandler<MapNode> ImageNodeDoubleClicked;
|
||||
public event EventHandler<MapImage> ImageDoubleClicked;
|
||||
public event EventHandler<MapLabel> LabelDoubleClicked;
|
||||
public event EventHandler MapChanged;
|
||||
|
||||
#endregion
|
||||
@@ -184,6 +202,60 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveItem(NodeBase item)
|
||||
{
|
||||
if (item is MapImage img) RemoveImage(img);
|
||||
else if (item is MapLabel lb) RemoveLabel(lb);
|
||||
else if (item is MapNode nd) RemoveNode(nd);
|
||||
else if (item is MapMark mk) RemoveMark(mk);
|
||||
else if (item is MapMagnet mg) RemoveMagnet(mg);
|
||||
else throw new Exception("unknown type");
|
||||
|
||||
}
|
||||
public void RemoveNode(MapNode node)
|
||||
{
|
||||
if (_nodes != null && _nodes.Contains(node))
|
||||
{
|
||||
_nodes.Remove(node);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
public void RemoveLabel(MapLabel label)
|
||||
{
|
||||
if (_labels != null && _labels.Contains(label))
|
||||
{
|
||||
_labels.Remove(label);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveImage(MapImage image)
|
||||
{
|
||||
if (_images != null && _images.Contains(image))
|
||||
{
|
||||
_images.Remove(image);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveMark(MapMark mark)
|
||||
{
|
||||
if (_marks != null && _marks.Contains(mark))
|
||||
{
|
||||
_marks.Remove(mark);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveMagnet(MapMagnet magnet)
|
||||
{
|
||||
if (_magnets != null && _magnets.Contains(magnet))
|
||||
{
|
||||
_magnets.Remove(magnet);
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 편집 모드 (CanvasMode.Edit일 때만 적용)
|
||||
/// </summary>
|
||||
@@ -230,15 +302,58 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public MapImage SelectedImage
|
||||
{
|
||||
get { return this._selectedNode as MapImage; }
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public MapLabel SelectedLabel
|
||||
{
|
||||
get { return this._selectedNode as MapLabel; }
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
public MapMark SelectedMark
|
||||
{
|
||||
get { return this._selectedNode as MapMark; }
|
||||
}
|
||||
|
||||
|
||||
[Browsable(false)]
|
||||
public MapMagnet SelectedMagnet
|
||||
{
|
||||
get { return this._selectedNode as MapMagnet; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드 (단일)
|
||||
/// </summary>
|
||||
public MapNode SelectedNode => _selectedNode;
|
||||
public MapNode SelectedNode
|
||||
{
|
||||
get { return this._selectedNode as MapNode; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드들 (다중)
|
||||
/// </summary>
|
||||
public List<MapNode> SelectedNodes => _selectedNodes ?? new List<MapNode>();
|
||||
public List<NodeBase> SelectedNodes => _selectedNodes ?? new List<NodeBase>();
|
||||
|
||||
|
||||
public List<NodeBase> Items
|
||||
{
|
||||
get
|
||||
{
|
||||
List<NodeBase> items = new List<NodeBase>();
|
||||
if (Nodes != null && Nodes.Any()) items.AddRange(Nodes);
|
||||
if (Labels != null && Labels.Any()) items.AddRange(Labels);
|
||||
if (Images != null && Images.Any()) items.AddRange(Images);
|
||||
if (Marks != null && Marks.Any()) items.AddRange(Marks);
|
||||
if (Magnets != null && Magnets.Any()) items.AddRange(Magnets);
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 노드 목록
|
||||
@@ -260,6 +375,58 @@ namespace AGVNavigationCore.Controls
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 라벨 목록
|
||||
/// </summary>
|
||||
public List<MapLabel> Labels
|
||||
{
|
||||
get => _labels ?? new List<MapLabel>();
|
||||
set
|
||||
{
|
||||
_labels = value ?? new List<MapLabel>();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 이미지 목록
|
||||
/// </summary>
|
||||
public List<MapImage> Images
|
||||
{
|
||||
get => _images ?? new List<MapImage>();
|
||||
set
|
||||
{
|
||||
_images = value ?? new List<MapImage>();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마크 목록
|
||||
/// </summary>
|
||||
public List<MapMark> Marks
|
||||
{
|
||||
get => _marks ?? new List<MapMark>();
|
||||
set
|
||||
{
|
||||
_marks = value ?? new List<MapMark>();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마그넷 목록
|
||||
/// </summary>
|
||||
public List<MapMagnet> Magnets
|
||||
{
|
||||
get => _magnets ?? new List<MapMagnet>();
|
||||
set
|
||||
{
|
||||
_magnets = value ?? new List<MapMagnet>();
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AGV 목록
|
||||
/// </summary>
|
||||
@@ -389,7 +556,12 @@ namespace AGVNavigationCore.Controls
|
||||
ControlStyles.ResizeRedraw, true);
|
||||
|
||||
_nodes = new List<MapNode>();
|
||||
_selectedNodes = new List<MapNode>(); // 다중 선택 리스트 초기화
|
||||
_labels = new List<MapLabel>();
|
||||
_images = new List<MapImage>();
|
||||
_marks = new List<MapMark>();
|
||||
_magnets = new List<MapMagnet>();
|
||||
|
||||
_selectedNodes = new List<NodeBase>(); // 다중 선택 리스트 초기화
|
||||
_agvList = new List<IAGV>();
|
||||
_agvPositions = new Dictionary<string, Point>();
|
||||
_agvDirections = new Dictionary<string, AgvDirection>();
|
||||
@@ -399,6 +571,12 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
InitializeBrushesAndPens();
|
||||
CreateContextMenu();
|
||||
|
||||
_tooltip = new ToolTip();
|
||||
_tooltip.AutoPopDelay = 5000;
|
||||
_tooltip.InitialDelay = 1000;
|
||||
_tooltip.ReshowDelay = 500;
|
||||
_tooltip.ShowAlways = true;
|
||||
}
|
||||
|
||||
private void InitializeBrushesAndPens()
|
||||
@@ -421,6 +599,7 @@ namespace AGVNavigationCore.Controls
|
||||
|
||||
// 펜
|
||||
_connectionPen = new Pen(Color.DarkBlue, CONNECTION_WIDTH);
|
||||
_connectionPen.DashStyle = DashStyle.Dash;
|
||||
_connectionPen.EndCap = LineCap.ArrowAnchor;
|
||||
|
||||
_gridPen = new Pen(Color.LightGray, 1);
|
||||
@@ -430,6 +609,8 @@ namespace AGVNavigationCore.Controls
|
||||
_pathPen = new Pen(Color.Purple, 3);
|
||||
_agvPen = new Pen(Color.Red, 3);
|
||||
_highlightedConnectionPen = new Pen(Color.Red, 4) { DashStyle = DashStyle.Solid };
|
||||
_magnetPen = new Pen(Color.FromArgb(100,Color.LightSkyBlue), 15) { DashStyle = DashStyle.Solid };
|
||||
_markPen = new Pen(Color.White, 3); // 마크는 흰색 선으로 표시
|
||||
}
|
||||
|
||||
private void CreateContextMenu()
|
||||
@@ -621,6 +802,8 @@ namespace AGVNavigationCore.Controls
|
||||
_pathPen?.Dispose();
|
||||
_agvPen?.Dispose();
|
||||
_highlightedConnectionPen?.Dispose();
|
||||
_magnetPen?.Dispose();
|
||||
_markPen?.Dispose();
|
||||
|
||||
// 컨텍스트 메뉴 정리
|
||||
_contextMenu?.Dispose();
|
||||
@@ -671,7 +854,7 @@ namespace AGVNavigationCore.Controls
|
||||
for (int i = 1; i < kvp.Value.Count; i++)
|
||||
{
|
||||
int duplicateNodeIndex = kvp.Value[i];
|
||||
_duplicateRfidNodes.Add(_nodes[duplicateNodeIndex].NodeId);
|
||||
_duplicateRfidNodes.Add(_nodes[duplicateNodeIndex].Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -692,7 +875,7 @@ namespace AGVNavigationCore.Controls
|
||||
foreach (var node in _nodes)
|
||||
{
|
||||
// NodeId에서 숫자 부분 추출 (예: "N001" -> 1)
|
||||
if (node.NodeId.StartsWith("N") && int.TryParse(node.NodeId.Substring(1), out int number))
|
||||
if (node.Id.StartsWith("N") && int.TryParse(node.Id.Substring(1), out int number))
|
||||
{
|
||||
maxNumber = Math.Max(maxNumber, number);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user