fix: RFID duplicate validation and correct magnet direction calculation
- Add real-time RFID duplicate validation in map editor with automatic rollback - Remove RFID auto-assignment to maintain data consistency between editor and simulator - Fix magnet direction calculation to use actual forward direction angles instead of arbitrary assignment - Add node names to simulator combo boxes for better identification - Improve UI layout by drawing connection lines before text for better visibility 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,35 @@ namespace AGVMapEditor.Forms
|
||||
// 파일 경로
|
||||
private string _currentMapFile = string.Empty;
|
||||
private bool _hasChanges = false;
|
||||
private bool _hasCommandLineArgs = false;
|
||||
|
||||
// 노드 연결 정보를 표현하는 클래스
|
||||
public class NodeConnectionInfo
|
||||
{
|
||||
public string FromNodeId { get; set; }
|
||||
public string FromNodeName { get; set; }
|
||||
public string FromRfidId { get; set; }
|
||||
public string ToNodeId { get; set; }
|
||||
public string ToNodeName { get; set; }
|
||||
public string ToRfidId { get; set; }
|
||||
public string ConnectionType { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
// RFID가 있으면 RFID(노드이름), 없으면 NodeID(노드이름) 형태로 표시
|
||||
string fromDisplay = !string.IsNullOrEmpty(FromRfidId)
|
||||
? $"{FromRfidId}({FromNodeName})"
|
||||
: $"---({FromNodeId})";
|
||||
|
||||
string toDisplay = !string.IsNullOrEmpty(ToRfidId)
|
||||
? $"{ToRfidId}({ToNodeName})"
|
||||
: $"---({ToNodeId})";
|
||||
|
||||
// 양방향 연결은 ↔ 기호 사용
|
||||
string arrow = ConnectionType == "양방향" ? "↔" : "→";
|
||||
return $"{fromDisplay} {arrow} {toDisplay}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
@@ -47,6 +76,7 @@ namespace AGVMapEditor.Forms
|
||||
// 명령줄 인수로 파일이 전달되었으면 자동으로 열기
|
||||
if (args != null && args.Length > 0)
|
||||
{
|
||||
_hasCommandLineArgs = true;
|
||||
string filePath = args[0];
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
@@ -54,14 +84,16 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show($"지정된 파일을 찾을 수 없습니다: {filePath}", "파일 오류",
|
||||
MessageBox.Show($"지정된 파일을 찾을 수 없습니다: {filePath}", "파일 오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
}
|
||||
// 명령줄 인수가 없는 경우는 Form_Load에서 마지막 맵 파일 자동 로드 확인
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Initialization
|
||||
|
||||
private void InitializeData()
|
||||
@@ -82,6 +114,7 @@ namespace AGVMapEditor.Forms
|
||||
_mapCanvas.NodeSelected += OnNodeSelected;
|
||||
_mapCanvas.NodeMoved += OnNodeMoved;
|
||||
_mapCanvas.NodeDeleted += OnNodeDeleted;
|
||||
_mapCanvas.ConnectionDeleted += OnConnectionDeleted;
|
||||
_mapCanvas.MapChanged += OnMapChanged;
|
||||
|
||||
// 스플리터 패널에 맵 캔버스 추가
|
||||
@@ -148,31 +181,38 @@ namespace AGVMapEditor.Forms
|
||||
btnDelete.Location = new Point(495, 3);
|
||||
btnDelete.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.Delete;
|
||||
|
||||
// 연결 삭제 버튼
|
||||
var btnDeleteConnection = new Button();
|
||||
btnDeleteConnection.Text = "연결삭제 (X)";
|
||||
btnDeleteConnection.Size = new Size(80, 28);
|
||||
btnDeleteConnection.Location = new Point(570, 3);
|
||||
btnDeleteConnection.Click += (s, e) => _mapCanvas.CurrentEditMode = UnifiedAGVCanvas.EditMode.DeleteConnection;
|
||||
|
||||
// 구분선
|
||||
var separator1 = new Label();
|
||||
separator1.Text = "|";
|
||||
separator1.Size = new Size(10, 28);
|
||||
separator1.Location = new Point(570, 3);
|
||||
separator1.Location = new Point(655, 3);
|
||||
separator1.TextAlign = ContentAlignment.MiddleCenter;
|
||||
|
||||
// 그리드 토글 버튼
|
||||
var btnToggleGrid = new Button();
|
||||
btnToggleGrid.Text = "그리드";
|
||||
btnToggleGrid.Size = new Size(60, 28);
|
||||
btnToggleGrid.Location = new Point(585, 3);
|
||||
btnToggleGrid.Location = new Point(670, 3);
|
||||
btnToggleGrid.Click += (s, e) => _mapCanvas.ShowGrid = !_mapCanvas.ShowGrid;
|
||||
|
||||
// 맵 맞춤 버튼
|
||||
var btnFitMap = new Button();
|
||||
btnFitMap.Text = "맵 맞춤";
|
||||
btnFitMap.Size = new Size(70, 28);
|
||||
btnFitMap.Location = new Point(650, 3);
|
||||
btnFitMap.Location = new Point(735, 3);
|
||||
btnFitMap.Click += (s, e) => _mapCanvas.FitToNodes();
|
||||
|
||||
// 툴바에 버튼들 추가
|
||||
toolbarPanel.Controls.AddRange(new Control[]
|
||||
{
|
||||
btnSelect, btnMove, btnAddNode, btnAddLabel, btnAddImage, btnConnect, btnDelete, separator1, btnToggleGrid, btnFitMap
|
||||
btnSelect, btnMove, btnAddNode, btnAddLabel, btnAddImage, btnConnect, btnDelete, btnDeleteConnection, separator1, btnToggleGrid, btnFitMap
|
||||
});
|
||||
|
||||
// 스플리터 패널에 툴바 추가 (맨 위에)
|
||||
@@ -186,9 +226,18 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
private void MainForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
RefreshNodeList();
|
||||
// 속성 변경 시 이벤트 연결
|
||||
_propertyGrid.PropertyValueChanged += PropertyGrid_PropertyValueChanged;
|
||||
|
||||
// 명령줄 인수가 없는 경우에만 마지막 맵 파일 자동 로드 확인
|
||||
if (!_hasCommandLineArgs)
|
||||
{
|
||||
this.Show();
|
||||
Application.DoEvents();
|
||||
CheckAndLoadLastMapFile();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNodeAdded(object sender, MapNode node)
|
||||
@@ -228,6 +277,14 @@ namespace AGVMapEditor.Forms
|
||||
UpdateNodeProperties(); // 연결 정보 업데이트
|
||||
}
|
||||
|
||||
private void OnConnectionDeleted(object sender, (MapNode From, MapNode To) connection)
|
||||
{
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
RefreshNodeConnectionList();
|
||||
UpdateNodeProperties(); // 연결 정보 업데이트
|
||||
}
|
||||
|
||||
private void OnMapChanged(object sender, EventArgs e)
|
||||
{
|
||||
_hasChanges = true;
|
||||
@@ -242,9 +299,9 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
#endregion
|
||||
|
||||
#region Menu Event Handlers
|
||||
#region ToolStrip Button Event Handlers
|
||||
|
||||
private void newToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
private void btnNew_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (CheckSaveChanges())
|
||||
{
|
||||
@@ -252,7 +309,7 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
}
|
||||
|
||||
private void openToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
private void btnOpen_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (CheckSaveChanges())
|
||||
{
|
||||
@@ -260,28 +317,70 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
}
|
||||
|
||||
private void saveToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
private void btnReopen_Click(object sender, EventArgs e)
|
||||
{
|
||||
SaveMap();
|
||||
if (string.IsNullOrEmpty(_currentMapFile))
|
||||
{
|
||||
MessageBox.Show("다시 열 파일이 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!File.Exists(_currentMapFile))
|
||||
{
|
||||
MessageBox.Show($"파일을 찾을 수 없습니다: {_currentMapFile}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (CheckSaveChanges())
|
||||
{
|
||||
LoadMapFromFile(_currentMapFile);
|
||||
UpdateStatusBar($"파일을 다시 열었습니다: {Path.GetFileName(_currentMapFile)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
SaveAsMap();
|
||||
}
|
||||
|
||||
private void closeToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
private void btnClose_Click(object sender, EventArgs e)
|
||||
{
|
||||
CloseMap();
|
||||
}
|
||||
|
||||
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
private void btnSave_Click(object sender, EventArgs e)
|
||||
{
|
||||
SaveMap();
|
||||
}
|
||||
|
||||
private void btnSaveAs_Click(object sender, EventArgs e)
|
||||
{
|
||||
SaveAsMap();
|
||||
}
|
||||
|
||||
private void btnExit_Click(object sender, EventArgs e)
|
||||
{
|
||||
this.Close();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Keyboard Shortcuts
|
||||
|
||||
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
|
||||
{
|
||||
switch (keyData)
|
||||
{
|
||||
case Keys.Control | Keys.N:
|
||||
btnNew_Click(null, null);
|
||||
return true;
|
||||
case Keys.Control | Keys.O:
|
||||
btnOpen_Click(null, null);
|
||||
return true;
|
||||
case Keys.Control | Keys.S:
|
||||
btnSave_Click(null, null);
|
||||
return true;
|
||||
}
|
||||
return base.ProcessCmdKey(ref msg, keyData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Button Event Handlers
|
||||
|
||||
private void btnAddNode_Click(object sender, EventArgs e)
|
||||
@@ -529,27 +628,55 @@ namespace AGVMapEditor.Forms
|
||||
private void LoadMapFromFile(string filePath)
|
||||
{
|
||||
var result = MapLoader.LoadMapFromFile(filePath);
|
||||
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_mapNodes = result.Nodes;
|
||||
|
||||
|
||||
// 맵 캔버스에 데이터 설정
|
||||
_mapCanvas.Nodes = _mapNodes;
|
||||
// RfidMappings 제거됨 - MapNode에 통합
|
||||
|
||||
// 현재 파일 경로 업데이트
|
||||
_currentMapFile = filePath;
|
||||
_hasChanges = false;
|
||||
|
||||
// 설정에 마지막 맵 파일 경로 저장
|
||||
EditorSettings.Instance.UpdateLastMapFile(filePath);
|
||||
|
||||
UpdateTitle();
|
||||
UpdateNodeList();
|
||||
RefreshNodeConnectionList();
|
||||
|
||||
// 맵 로드 후 자동으로 맵에 맞춤
|
||||
_mapCanvas.FitToNodes();
|
||||
|
||||
UpdateStatusBar($"맵 파일을 성공적으로 로드했습니다: {Path.GetFileName(filePath)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show($"맵 파일 로딩 실패: {result.ErrorMessage}", "오류",
|
||||
MessageBox.Show($"맵 파일 로딩 실패: {result.ErrorMessage}", "오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveMapToFile(string filePath)
|
||||
{
|
||||
if (!MapLoader.SaveMapToFile(filePath, _mapNodes))
|
||||
if (MapLoader.SaveMapToFile(filePath, _mapNodes))
|
||||
{
|
||||
MessageBox.Show("맵 파일 저장 실패", "오류",
|
||||
// 현재 파일 경로 업데이트
|
||||
_currentMapFile = filePath;
|
||||
_hasChanges = false;
|
||||
|
||||
// 설정에 마지막 맵 파일 경로 저장
|
||||
EditorSettings.Instance.UpdateLastMapFile(filePath);
|
||||
|
||||
UpdateTitle();
|
||||
UpdateStatusBar($"맵 파일을 성공적으로 저장했습니다: {Path.GetFileName(filePath)}");
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("맵 파일 저장 실패", "오류",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
@@ -559,8 +686,8 @@ namespace AGVMapEditor.Forms
|
||||
/// </summary>
|
||||
private void UpdateRfidMappings()
|
||||
{
|
||||
// 네비게이션 노드들에 RFID 자동 할당
|
||||
MapLoader.AssignAutoRfidIds(_mapNodes);
|
||||
// RFID 자동 할당 제거 - 사용자가 직접 입력한 값 유지
|
||||
// MapLoader.AssignAutoRfidIds(_mapNodes);
|
||||
}
|
||||
|
||||
private bool CheckSaveChanges()
|
||||
@@ -584,6 +711,29 @@ namespace AGVMapEditor.Forms
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 마지막 맵 파일이 있는지 확인하고 사용자에게 로드할지 물어봄
|
||||
/// </summary>
|
||||
private void CheckAndLoadLastMapFile()
|
||||
{
|
||||
var settings = EditorSettings.Instance;
|
||||
|
||||
if (settings.AutoLoadLastMapFile && settings.HasValidLastMapFile())
|
||||
{
|
||||
string fileName = Path.GetFileName(settings.LastMapFilePath);
|
||||
var result = MessageBox.Show(
|
||||
$"마지막으로 사용한 맵 파일을 찾았습니다:\n\n{fileName}\n\n이 파일을 열까요?",
|
||||
"마지막 맵 파일 로드",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
LoadMapFromFile(settings.LastMapFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UI Updates
|
||||
@@ -591,6 +741,7 @@ namespace AGVMapEditor.Forms
|
||||
private void RefreshAll()
|
||||
{
|
||||
RefreshNodeList();
|
||||
RefreshNodeConnectionList();
|
||||
RefreshMapCanvas();
|
||||
ClearNodeProperties();
|
||||
}
|
||||
@@ -672,12 +823,12 @@ namespace AGVMapEditor.Forms
|
||||
e.Graphics.FillRectangle(brush, e.Bounds);
|
||||
}
|
||||
|
||||
// 텍스트 그리기 (노드ID - 설명 - RFID 순서)
|
||||
// 텍스트 그리기 (노드ID - 노드명 - RFID 순서)
|
||||
var displayText = node.NodeId;
|
||||
|
||||
if (!string.IsNullOrEmpty(node.Description))
|
||||
|
||||
if (!string.IsNullOrEmpty(node.Name))
|
||||
{
|
||||
displayText += $" - {node.Description}";
|
||||
displayText += $" - {node.Name}";
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(node.RfidId))
|
||||
@@ -694,6 +845,92 @@ namespace AGVMapEditor.Forms
|
||||
e.DrawFocusRectangle();
|
||||
}
|
||||
|
||||
private void RefreshNodeConnectionList()
|
||||
{
|
||||
var connections = new List<NodeConnectionInfo>();
|
||||
var processedPairs = new HashSet<string>();
|
||||
|
||||
// 모든 노드의 연결 정보를 수집 (중복 방지)
|
||||
foreach (var fromNode in _mapNodes)
|
||||
{
|
||||
foreach (var toNodeId in fromNode.ConnectedNodes)
|
||||
{
|
||||
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == toNodeId);
|
||||
if (toNode != null)
|
||||
{
|
||||
// 중복 체크 (단일 연결만 표시)
|
||||
string pairKey1 = $"{fromNode.NodeId}-{toNode.NodeId}";
|
||||
string pairKey2 = $"{toNode.NodeId}-{fromNode.NodeId}";
|
||||
|
||||
if (!processedPairs.Contains(pairKey1) && !processedPairs.Contains(pairKey2))
|
||||
{
|
||||
// 사전 순으로 정렬하여 일관성 있게 표시
|
||||
var (firstNode, secondNode) = string.Compare(fromNode.NodeId, toNode.NodeId) < 0
|
||||
? (fromNode, toNode)
|
||||
: (toNode, fromNode);
|
||||
|
||||
connections.Add(new NodeConnectionInfo
|
||||
{
|
||||
FromNodeId = firstNode.NodeId,
|
||||
FromNodeName = firstNode.Name,
|
||||
FromRfidId = firstNode.RfidId,
|
||||
ToNodeId = secondNode.NodeId,
|
||||
ToNodeName = secondNode.Name,
|
||||
ToRfidId = secondNode.RfidId,
|
||||
ConnectionType = "양방향" // 모든 연결이 양방향
|
||||
});
|
||||
|
||||
processedPairs.Add(pairKey1);
|
||||
processedPairs.Add(pairKey2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 리스트박스에 표시
|
||||
lstNodeConnection.DataSource = null;
|
||||
lstNodeConnection.DataSource = connections;
|
||||
lstNodeConnection.DisplayMember = "ToString";
|
||||
|
||||
// 리스트박스 클릭 이벤트 연결
|
||||
lstNodeConnection.SelectedIndexChanged -= LstNodeConnection_SelectedIndexChanged;
|
||||
lstNodeConnection.SelectedIndexChanged += LstNodeConnection_SelectedIndexChanged;
|
||||
|
||||
// 더블클릭 이벤트 연결 (연결 삭제)
|
||||
lstNodeConnection.DoubleClick -= LstNodeConnection_DoubleClick;
|
||||
lstNodeConnection.DoubleClick += LstNodeConnection_DoubleClick;
|
||||
}
|
||||
|
||||
|
||||
private void LstNodeConnection_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (lstNodeConnection.SelectedItem is NodeConnectionInfo connectionInfo)
|
||||
{
|
||||
// 캔버스에서 해당 연결선 강조 표시
|
||||
_mapCanvas?.HighlightConnection(connectionInfo.FromNodeId, connectionInfo.ToNodeId);
|
||||
|
||||
// 연결된 노드들을 맵에서 하이라이트 표시 (선택적)
|
||||
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.FromNodeId);
|
||||
if (fromNode != null)
|
||||
{
|
||||
_selectedNode = fromNode;
|
||||
UpdateNodeProperties();
|
||||
_mapCanvas?.Invalidate();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 선택 해제 시 강조 표시 제거
|
||||
_mapCanvas?.ClearHighlightedConnection();
|
||||
}
|
||||
}
|
||||
|
||||
private void LstNodeConnection_DoubleClick(object sender, EventArgs e)
|
||||
{
|
||||
// 더블클릭으로 연결 삭제
|
||||
DeleteSelectedConnection();
|
||||
}
|
||||
|
||||
private void RefreshMapCanvas()
|
||||
{
|
||||
_mapCanvas?.Invalidate();
|
||||
@@ -735,6 +972,31 @@ namespace AGVMapEditor.Forms
|
||||
this.Text = title;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 노드 목록을 업데이트
|
||||
/// </summary>
|
||||
private void UpdateNodeList()
|
||||
{
|
||||
if (listBoxNodes != null)
|
||||
{
|
||||
listBoxNodes.DataSource = null;
|
||||
listBoxNodes.DataSource = _mapNodes;
|
||||
listBoxNodes.DisplayMember = "DisplayText";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 상태바에 메시지 표시
|
||||
/// </summary>
|
||||
/// <param name="message">표시할 메시지</param>
|
||||
private void UpdateStatusBar(string message)
|
||||
{
|
||||
if (toolStripStatusLabel1 != null)
|
||||
{
|
||||
toolStripStatusLabel1.Text = message;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Form Events
|
||||
@@ -754,10 +1016,27 @@ namespace AGVMapEditor.Forms
|
||||
|
||||
private void PropertyGrid_PropertyValueChanged(object s, PropertyValueChangedEventArgs e)
|
||||
{
|
||||
// RFID 값 변경시 중복 검사
|
||||
if (e.ChangedItem.PropertyDescriptor.Name == "RFID")
|
||||
{
|
||||
string newRfidValue = e.ChangedItem.Value?.ToString();
|
||||
if (!string.IsNullOrEmpty(newRfidValue) && CheckRfidDuplicate(newRfidValue))
|
||||
{
|
||||
// 중복된 RFID 값 발견
|
||||
MessageBox.Show($"RFID 값 '{newRfidValue}'이(가) 이미 다른 노드에서 사용 중입니다.\n입력값을 되돌립니다.",
|
||||
"RFID 중복 오류", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
|
||||
// 원래 값으로 되돌리기 - PropertyGrid의 SelectedObject 사용
|
||||
e.ChangedItem.PropertyDescriptor.SetValue(_propertyGrid.SelectedObject, e.OldValue);
|
||||
_propertyGrid.Refresh();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 속성이 변경되었을 때 자동으로 변경사항 표시
|
||||
_hasChanges = true;
|
||||
UpdateTitle();
|
||||
|
||||
|
||||
// 현재 선택된 노드를 기억
|
||||
var currentSelectedNode = _selectedNode;
|
||||
|
||||
@@ -775,12 +1054,107 @@ namespace AGVMapEditor.Forms
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RFID 값 중복 검사
|
||||
/// </summary>
|
||||
/// <param name="rfidValue">검사할 RFID 값</param>
|
||||
/// <returns>중복되면 true, 아니면 false</returns>
|
||||
private bool CheckRfidDuplicate(string rfidValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(rfidValue) || _mapNodes == null)
|
||||
return false;
|
||||
|
||||
// 현재 편집 중인 노드 제외하고 중복 검사
|
||||
string currentNodeId = null;
|
||||
var selectedObject = _propertyGrid.SelectedObject;
|
||||
|
||||
// 다양한 PropertyWrapper 타입 처리
|
||||
if (selectedObject is NodePropertyWrapper nodeWrapper)
|
||||
{
|
||||
currentNodeId = nodeWrapper.WrappedNode?.NodeId;
|
||||
}
|
||||
else if (selectedObject is LabelNodePropertyWrapper labelWrapper)
|
||||
{
|
||||
currentNodeId = labelWrapper.WrappedNode?.NodeId;
|
||||
}
|
||||
else if (selectedObject is ImageNodePropertyWrapper imageWrapper)
|
||||
{
|
||||
currentNodeId = imageWrapper.WrappedNode?.NodeId;
|
||||
}
|
||||
|
||||
int duplicateCount = 0;
|
||||
foreach (var node in _mapNodes)
|
||||
{
|
||||
// 현재 편집 중인 노드는 제외
|
||||
if (node.NodeId == currentNodeId)
|
||||
continue;
|
||||
|
||||
// 같은 RFID 값을 가진 노드가 있는지 확인
|
||||
if (!string.IsNullOrEmpty(node.RfidId) && node.RfidId.Equals(rfidValue, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
duplicateCount++;
|
||||
break; // 하나라도 발견되면 중복
|
||||
}
|
||||
}
|
||||
|
||||
return duplicateCount > 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Model for Serialization
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
private void btNodeRemove_Click(object sender, EventArgs e)
|
||||
{
|
||||
DeleteSelectedConnection();
|
||||
}
|
||||
|
||||
private void DeleteSelectedConnection()
|
||||
{
|
||||
if (lstNodeConnection.SelectedItem is NodeConnectionInfo connectionInfo)
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
$"다음 연결을 삭제하시겠습니까?\n{connectionInfo}",
|
||||
"연결 삭제 확인",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
// 단일 연결 삭제
|
||||
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.FromNodeId);
|
||||
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.ToNodeId);
|
||||
|
||||
if (fromNode != null && toNode != null)
|
||||
{
|
||||
// 단일 연결 삭제 (어느 방향에 저장되어 있는지 확인 후 삭제)
|
||||
if (fromNode.ConnectedNodes.Contains(toNode.NodeId))
|
||||
{
|
||||
fromNode.RemoveConnection(toNode.NodeId);
|
||||
}
|
||||
else if (toNode.ConnectedNodes.Contains(fromNode.NodeId))
|
||||
{
|
||||
toNode.RemoveConnection(fromNode.NodeId);
|
||||
}
|
||||
|
||||
_hasChanges = true;
|
||||
|
||||
RefreshNodeConnectionList();
|
||||
RefreshMapCanvas();
|
||||
UpdateNodeProperties();
|
||||
UpdateTitle();
|
||||
|
||||
toolStripStatusLabel1.Text = $"연결 삭제됨: {connectionInfo.FromNodeId} ↔ {connectionInfo.ToNodeId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("삭제할 연결을 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user