diff --git a/ControlAgent/Form1.Designer.cs b/ControlAgent/Form1.Designer.cs
index d2a199b..2aab9df 100644
--- a/ControlAgent/Form1.Designer.cs
+++ b/ControlAgent/Form1.Designer.cs
@@ -28,8 +28,29 @@
///
private void InitializeComponent()
{
+ DataGridViewCellStyle dataGridViewCellStyle1 = new DataGridViewCellStyle();
+ DataGridViewCellStyle dataGridViewCellStyle2 = new DataGridViewCellStyle();
lblPCName = new Label();
lblMac = new Label();
+ grdDataList = new DataGridView();
+ Col0 = new DataGridViewTextBoxColumn();
+ Col1 = new DataGridViewTextBoxColumn();
+ Col2 = new DataGridViewTextBoxColumn();
+ Col3 = new DataGridViewTextBoxColumn();
+ Col4 = new DataGridViewTextBoxColumn();
+ Col5 = new DataGridViewTextBoxColumn();
+ Col6 = new DataGridViewTextBoxColumn();
+ panel1 = new Panel();
+ lblTime = new Label();
+ lblDate = new Label();
+ imgLogo = new PictureBox();
+ lblLogo = new Label();
+ lblTitle = new Label();
+ lblPageIndicator = new Label();
+ lblDataUpdateTime = new Label();
+ ((System.ComponentModel.ISupportInitialize)grdDataList).BeginInit();
+ panel1.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)imgLogo).BeginInit();
SuspendLayout();
//
// lblPCName
@@ -46,15 +67,177 @@
lblMac.Size = new Size(219, 39);
lblMac.TabIndex = 1;
//
+ // grdDataList
+ //
+ dataGridViewCellStyle1.Alignment = DataGridViewContentAlignment.MiddleCenter;
+ dataGridViewCellStyle1.BackColor = SystemColors.Control;
+ dataGridViewCellStyle1.Font = new Font("맑은 고딕", 12F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ dataGridViewCellStyle1.ForeColor = SystemColors.WindowText;
+ dataGridViewCellStyle1.SelectionBackColor = SystemColors.Highlight;
+ dataGridViewCellStyle1.SelectionForeColor = SystemColors.HighlightText;
+ dataGridViewCellStyle1.WrapMode = DataGridViewTriState.True;
+ grdDataList.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
+ grdDataList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+ grdDataList.Columns.AddRange(new DataGridViewColumn[] { Col0, Col1, Col2, Col3, Col4, Col5, Col6 });
+ grdDataList.Location = new Point(0, 146);
+ grdDataList.Name = "grdDataList";
+ grdDataList.RowHeadersWidth = 51;
+ dataGridViewCellStyle2.Alignment = DataGridViewContentAlignment.MiddleCenter;
+ grdDataList.RowsDefaultCellStyle = dataGridViewCellStyle2;
+ grdDataList.Size = new Size(1412, 585);
+ grdDataList.TabIndex = 2;
+ //
+ // Col0
+ //
+ Col0.HeaderText = "경매일자";
+ Col0.MinimumWidth = 6;
+ Col0.Name = "Col0";
+ Col0.Width = 200;
+ //
+ // Col1
+ //
+ Col1.HeaderText = "품목명";
+ Col1.MinimumWidth = 6;
+ Col1.Name = "Col1";
+ Col1.Width = 200;
+ //
+ // Col2
+ //
+ Col2.HeaderText = "산지";
+ Col2.MinimumWidth = 6;
+ Col2.Name = "Col2";
+ Col2.Width = 200;
+ //
+ // Col3
+ //
+ Col3.HeaderText = "전체수량";
+ Col3.MinimumWidth = 6;
+ Col3.Name = "Col3";
+ Col3.Width = 200;
+ //
+ // Col4
+ //
+ Col4.HeaderText = "정가수의 수량";
+ Col4.MinimumWidth = 6;
+ Col4.Name = "Col4";
+ Col4.Width = 200;
+ //
+ // Col5
+ //
+ Col5.HeaderText = "전체물량";
+ Col5.MinimumWidth = 6;
+ Col5.Name = "Col5";
+ Col5.Width = 200;
+ //
+ // Col6
+ //
+ Col6.HeaderText = "정가수의 물량";
+ Col6.MinimumWidth = 6;
+ Col6.Name = "Col6";
+ Col6.Width = 200;
+ //
+ // panel1
+ //
+ panel1.BackColor = SystemColors.Highlight;
+ panel1.Controls.Add(lblTime);
+ panel1.Controls.Add(lblDate);
+ panel1.Controls.Add(imgLogo);
+ panel1.Controls.Add(lblLogo);
+ panel1.Controls.Add(lblTitle);
+ panel1.Location = new Point(0, 0);
+ panel1.Name = "panel1";
+ panel1.Size = new Size(1412, 120);
+ panel1.TabIndex = 4;
+ //
+ // lblTime
+ //
+ lblTime.BackColor = SystemColors.ControlDarkDark;
+ lblTime.Font = new Font("맑은 고딕", 24F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblTime.ForeColor = SystemColors.HighlightText;
+ lblTime.Location = new Point(1215, 47);
+ lblTime.Name = "lblTime";
+ lblTime.Size = new Size(169, 66);
+ lblTime.TabIndex = 9;
+ lblTime.TextAlign = ContentAlignment.MiddleCenter;
+ //
+ // lblDate
+ //
+ lblDate.BackColor = SystemColors.ControlDarkDark;
+ lblDate.Font = new Font("맑은 고딕", 10.2F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblDate.ForeColor = SystemColors.HighlightText;
+ lblDate.Location = new Point(1215, 20);
+ lblDate.Name = "lblDate";
+ lblDate.Size = new Size(169, 27);
+ lblDate.TabIndex = 8;
+ lblDate.TextAlign = ContentAlignment.MiddleCenter;
+ //
+ // imgLogo
+ //
+ imgLogo.Location = new Point(20, 38);
+ imgLogo.Name = "imgLogo";
+ imgLogo.Size = new Size(52, 43);
+ imgLogo.TabIndex = 6;
+ imgLogo.TabStop = false;
+ //
+ // lblLogo
+ //
+ lblLogo.Font = new Font("맑은 고딕", 10.2F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblLogo.ForeColor = SystemColors.HighlightText;
+ lblLogo.Location = new Point(91, 0);
+ lblLogo.Name = "lblLogo";
+ lblLogo.Size = new Size(172, 120);
+ lblLogo.TabIndex = 5;
+ lblLogo.Text = "수협중앙회";
+ lblLogo.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // lblTitle
+ //
+ lblTitle.Font = new Font("맑은 고딕", 25.8000011F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblTitle.ForeColor = SystemColors.HighlightText;
+ lblTitle.Location = new Point(0, 0);
+ lblTitle.Name = "lblTitle";
+ lblTitle.Size = new Size(1412, 120);
+ lblTitle.TabIndex = 4;
+ lblTitle.Text = "정가·수의매매 출하정보";
+ lblTitle.TextAlign = ContentAlignment.MiddleCenter;
+ //
+ // lblPageIndicator
+ //
+ lblPageIndicator.Dock = DockStyle.Bottom;
+ lblPageIndicator.Font = new Font("맑은 고딕", 7.8F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblPageIndicator.ForeColor = SystemColors.HotTrack;
+ lblPageIndicator.Location = new Point(0, 734);
+ lblPageIndicator.Name = "lblPageIndicator";
+ lblPageIndicator.Size = new Size(1412, 19);
+ lblPageIndicator.TabIndex = 5;
+ lblPageIndicator.TextAlign = ContentAlignment.MiddleCenter;
+ //
+ // lblDataUpdateTime
+ //
+ lblDataUpdateTime.Font = new Font("맑은 고딕", 9F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblDataUpdateTime.Location = new Point(5, 124);
+ lblDataUpdateTime.Name = "lblDataUpdateTime";
+ lblDataUpdateTime.Size = new Size(306, 19);
+ lblDataUpdateTime.TabIndex = 6;
+ lblDataUpdateTime.Text = " ";
+ lblDataUpdateTime.TextAlign = ContentAlignment.MiddleLeft;
+ //
// Form1
//
AutoScaleDimensions = new SizeF(9F, 20F);
AutoScaleMode = AutoScaleMode.Font;
- ClientSize = new Size(800, 450);
+ ClientSize = new Size(1412, 753);
+ Controls.Add(lblDataUpdateTime);
+ Controls.Add(lblPageIndicator);
+ Controls.Add(panel1);
+ Controls.Add(grdDataList);
Controls.Add(lblMac);
Controls.Add(lblPCName);
Name = "Form1";
Text = "Form1";
+ ((System.ComponentModel.ISupportInitialize)grdDataList).EndInit();
+ panel1.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)imgLogo).EndInit();
ResumeLayout(false);
}
@@ -62,5 +245,21 @@
private Label lblPCName;
private Label lblMac;
+ private DataGridView grdDataList;
+ private DataGridViewTextBoxColumn Col0;
+ private DataGridViewTextBoxColumn Col1;
+ private DataGridViewTextBoxColumn Col2;
+ private DataGridViewTextBoxColumn Col3;
+ private DataGridViewTextBoxColumn Col4;
+ private DataGridViewTextBoxColumn Col5;
+ private DataGridViewTextBoxColumn Col6;
+ private Panel panel1;
+ private Label lblTitle;
+ private Label lblLogo;
+ private PictureBox imgLogo;
+ private Label lblDate;
+ private Label lblTime;
+ private Label lblPageIndicator;
+ private Label lblDataUpdateTime;
}
}
\ No newline at end of file
diff --git a/ControlAgent/Form1.cs b/ControlAgent/Form1.cs
index 5e220e8..7fc2416 100644
--- a/ControlAgent/Form1.cs
+++ b/ControlAgent/Form1.cs
@@ -2,115 +2,661 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
+using System.Data.SqlTypes;
using System.Drawing;
+using System.Drawing.Drawing2D;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
using System.Windows.Forms;
+using System.Xml.Linq;
+using static System.Windows.Forms.LinkLabel;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement.TaskbarClock;
namespace ControlAgent
{
public partial class Form1 : Form
{
- private bool _isAgentRunning = true;
+ // 색상 정의 (Hex 코드 대신 RGB 사용)
+ private readonly Color HEADER_BG_COLOR = Color.FromArgb(36, 85, 178);
+ private readonly Color HEADER_TEXT_COLOR = Color.White;
+ private readonly Color ROW_EVEN_COLOR = Color.FromArgb(245, 248, 255); // 짝수 행 (연한 파란색 계열)
+ private readonly Color ROW_ODD_COLOR = Color.White; // 홀수 행 (흰색)
+ private readonly Color TEXT_BLUE_COLOR = Color.FromArgb(24, 60, 150); // 파란색 텍스트
+
+ private DataTable _dtSourceAll; // 전체 데이터를 담을 DataTable
+ private DataTable _dtPaged; // 현재 페이지에 해당하는 데이터만 담을 DataTable
+ private int _currentPage = 1;
+ private const int PAGE_SIZE = 5; // 한 페이지당 5행씩
+ private int _dynamicFontSize = 12; // 동적으로 바뀔 폰트 크기 (기본값)
+ private System.Windows.Forms.Timer _pageTurnTimer;
+ private System.Windows.Forms.Timer _clockTimer;
+
+ private bool _isAgentRunning = true; // 에이전트 루프 제어 플래그
+
+ private NetworkStream _currentStream = null;
+ private readonly object _streamLock = new object();
public Form1()
{
InitializeComponent();
- // 백그라운드 태스크 형태로 서버 감지 및 수신 스레드 기동 (UI 먹통 현상 방지)
+ // 1. 폼 최대화 및 테두리 제거 (현황판/키오스크 스타일)
+ this.WindowState = FormWindowState.Maximized;
+ this.FormBorderStyle = FormBorderStyle.None;
+
+ // 고정 도킹 해제하여 동적 배치 적용
+ if (panel1 != null)
+ {
+ panel1.Dock = DockStyle.None;
+ panel1.Height = 140; // 전체 화면 비율에 맞춰 타이틀 패널 높이 조절
+ }
+
+ // 라벨들의 개별 Dock 설정을 해제하여 디자이너에서 배치한 원래 위치(좌측/우측)를 유지하도록 합니다.
+ if (lblTime != null) lblTime.Dock = DockStyle.None;
+ if (lblDate != null) lblDate.Dock = DockStyle.None;
+ if (lblTitle != null) lblTitle.Dock = DockStyle.None;
+
+ if (lblDataUpdateTime != null)
+ {
+ lblDataUpdateTime.Dock = DockStyle.None;
+ lblDataUpdateTime.AutoSize = true;
+ }
+
+ // 3.그리드가 기준시간 밑으로 오도록 도킹 해제 및 여백 초기화
+ grdDataList.Dock = DockStyle.None;
+ grdDataList.Padding = new Padding(0); // 기존 오작동하던 패딩 제거
+
+ // 4. 화면 해상도가 바뀌거나 최대화될 때 컨트롤 위치를 전체적으로 재계산
+ this.SizeChanged += (s, e) =>
+ {
+ int sideMargin = 25; // 좌우 고정 여백
+ int topMargin = 20; // 타이틀 패널 위쪽 살짝 여백 추가
+ int areaWidth = this.ClientSize.Width - (sideMargin * 2);
+
+ // A. 타이틀 패널 위치 및 크기 조절 (그리드와 넓이 동기화 및 상단 여백)
+ if (panel1 != null)
+ {
+ panel1.SetBounds(sideMargin, topMargin, areaWidth, panel1.Height);
+
+ // 타이틀 내부 컨트롤 정렬 강제 호출
+ panel1_SizeChanged(null, null);
+ }
+
+ // B. 기준 시간 라벨 배치 (타이틀 패널 아래로 15픽셀 여백)
+ if (lblDataUpdateTime != null && panel1 != null)
+ {
+ lblDataUpdateTime.Location = new Point(sideMargin, panel1.Bottom + 15);
+ lblDataUpdateTime.BringToFront();
+ }
+
+ // C. 그리드 배치 (기준시간 라벨 아랫변 + 15픽셀 여백)
+ if (lblDataUpdateTime != null)
+ {
+ int gridTop = lblDataUpdateTime.Bottom + 15;
+ int gridHeight = this.ClientSize.Height - gridTop - 60;
+
+ if (areaWidth > 0 && gridHeight > 0)
+ {
+ grdDataList.SetBounds(sideMargin, gridTop, areaWidth, gridHeight);
+
+ // 가용 높이에 맞게 행 높이 동적 계산 (한 번에 5개 고정)
+ int headerHeight = grdDataList.ColumnHeadersHeight;
+ int availableHeight = gridHeight - headerHeight;
+
+ if (availableHeight > 0)
+ {
+ // 정밀도를 위해 가용한 높이를 PAGE_SIZE(5)로 나눈 몫을 계산
+ int dynamicRowHeight = availableHeight / PAGE_SIZE;
+
+ // 앞으로 추가될 행의 기본 높이 설정
+ grdDataList.RowTemplate.Height = dynamicRowHeight;
+
+ // 현재 이미 그리드에 바인딩되어 있는 행들의 높이도 일괄 변경
+ // 폰트 크기가 최소 12pt ~ 최대 24pt 사이에 머물도록 제한
+ _dynamicFontSize = Math.Clamp((int)(dynamicRowHeight * 0.16), 12, 24);
+
+ foreach (DataGridViewRow row in grdDataList.Rows)
+ {
+ row.Height = dynamicRowHeight;
+ }
+ }
+
+ //그리드 모서리 둥글게 깎기
+ UpdateGridRegion();
+ }
+ }
+
+ // 하단 페이지 점(● • •) 위치 정중앙 하단으로 재배치
+ if (lblPageIndicator != null)
+ {
+ lblPageIndicator.Dock = DockStyle.None;
+ lblPageIndicator.AutoSize = true;
+ lblPageIndicator.Location = new Point(
+ (this.ClientSize.Width - lblPageIndicator.Width) / 2,
+ this.ClientSize.Height - 40
+ );
+ lblPageIndicator.BringToFront();
+ }
+ };
+
+ // DataGridView 내부 컬럼들도 우측 빈 공간(회색 여백) 없이 꽉 차게 채움
+ grdDataList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+ grdDataList.ColumnHeadersVisible = true;
+
+ // DataGridView의 기본 스타일 및 그리드선 숨기기 설정
+ grdDataList.CellPainting -= grdDataList_CellPainting;
+ grdDataList.CellPainting += grdDataList_CellPainting;
+
+ grdDataList.AutoGenerateColumns = false; // 자동 컬럼 생성 방지
+ grdDataList.RowHeadersVisible = false; // 행 헤더 숨기기
+ grdDataList.AllowUserToAddRows = false; // 빈 행 추가 방지
+ grdDataList.AllowUserToResizeColumns = false; // 컬럼 너비 조정 불가
+ grdDataList.AllowUserToResizeRows = false; // 행 높이 조정 불가
+ grdDataList.ReadOnly = true; // 읽기 전용
+ grdDataList.AllowUserToDeleteRows = false; // 행 삭제 방지
+ grdDataList.ColumnHeadersHeight = 81; // 헤더 높이를 81로 설정 (2줄 헤더 공간 확보)
+ grdDataList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
+
+ grdDataList.EnableHeadersVisualStyles = false; // 커스텀 헤더 스타일 적용을 위해 필수
+ grdDataList.CellBorderStyle = DataGridViewCellBorderStyle.None; // 셀 테두리(그리드선) 제거
+ grdDataList.RowTemplate.Height = 100; // 데이터 행 높이를 100으로 설정
+ grdDataList.BorderStyle = BorderStyle.None; // 컨트롤 외곽선 제거
+
+ // 사용자가 컬럼을 클릭해도 정렬되지 않도록 설정 (안정적인 화면 유지)
+ grdDataList.ColumnAdded += (s, e) =>
+ {
+ e.Column.SortMode = DataGridViewColumnSortMode.NotSortable;
+ };
+
+ // 시계 타이머 설정 (1초마다 업데이트)
+ _clockTimer = new System.Windows.Forms.Timer();
+ _clockTimer.Interval = 1000;
+ _clockTimer.Tick += ClockTimer_Tick;
+
+ // 페이지 전환 타이머 설정 (5초마다 업데이트)
+ _pageTurnTimer = new System.Windows.Forms.Timer();
+ _pageTurnTimer.Interval = 5000;
+ _pageTurnTimer.Tick += PageTurnTimer_Tick;
+
+ this.Load += Form1_Load;
+ }
+
+ // 데이터그리드뷰 영역을 둥글게 깎아주는 헬퍼 메서드
+ private void UpdateGridRegion()
+ {
+ if (grdDataList.Width <= 0 || grdDataList.Height <= 0) return;
+
+ int radius = 15;
+ using (GraphicsPath path = new GraphicsPath())
+ {
+ path.AddArc(0, 0, radius, radius, 180, 90);
+ path.AddArc(grdDataList.Width - radius, 0, radius, radius, 270, 90);
+ path.AddArc(grdDataList.Width - radius, grdDataList.Height - radius, radius, radius, 0, 90);
+ path.AddArc(0, grdDataList.Height - radius, radius, radius, 90, 90);
+ path.CloseAllFigures();
+ grdDataList.Region = new Region(path);
+ }
+ }
+
+ private void Form1_Load(object sender, EventArgs e)
+ {
+ // 컬럼 프로퍼티 매핑 초기화 선행
+ InitGridColumnProperties();
+
+ // 한 번 더 명시적으로 정렬 비활성화
+ foreach (DataGridViewColumn col in grdDataList.Columns)
+ {
+ col.SortMode = DataGridViewColumnSortMode.NotSortable;
+ }
+
+ // 시계 시작
+ _clockTimer.Start();
+ UpdateCurrentTime();
+
+ // 연결 전까지는 그리드가 빈 상태(리스트 없음)로 대기
+ grdDataList.DataSource = null;
+
+ // 페이지 타이머 시작
+ _pageTurnTimer.Start();
+
+ // 백그라운드 소켓 통신 시작 (비동기 Task 활용으로 UI 블로킹 방지)
Task.Run(() => StartAgentTask());
}
+ // 그리드 내부 컬럼에 프로퍼티 명을 미리 지정해 주는 헬퍼 메서드
+ private void InitGridColumnProperties()
+ {
+ foreach (DataGridViewColumn col in grdDataList.Columns)
+ {
+ if (col.HeaderText.Contains("경매일자")) col.DataPropertyName = "aucDate";
+ else if (col.HeaderText.Contains("품목명")) col.DataPropertyName = "itemName";
+ else if (col.HeaderText.Contains("산지")) col.DataPropertyName = "origin";
+ else if (col.HeaderText.Contains("전체수량") || (col.HeaderText.Contains("전체") && col.Index == 3)) col.DataPropertyName = "totalQty";
+ else if (col.HeaderText.Contains("정가수의수량") || col.HeaderText.Contains("정가수의 수량") || col.Index == 4) col.DataPropertyName = "fixPriceQty";
+ else if (col.HeaderText.Contains("전체물량") || (col.HeaderText.Contains("전체") && col.Index == 5)) col.DataPropertyName = "totalAmt";
+ else if (col.HeaderText.Contains("정가수의물량") || col.HeaderText.Contains("정가수의 물량") || col.Index == 6) col.DataPropertyName = "fixPriceAmt";
+
+ if (col.Index >= 3 && col.Index <= 6)
+ {
+ col.ValueType = typeof(int);
+ col.DefaultCellStyle.Format = "N0";
+ }
+ }
+ }
+
+ // 서버와의 소켓 통신을 관리하는 백그라운드 메서드
private void StartAgentTask()
{
while (_isAgentRunning)
{
try
{
- using (TcpClient client = new TcpClient())
+ using (TcpClient client = new TcpClient("192.168.219.48", 9999)) // 서버 ip 수정!!!
+ using (NetworkStream stream = client.GetStream())
+ using (System.IO.StreamReader reader = new System.IO.StreamReader(stream, Encoding.UTF8))
{
- var result = client.BeginConnect("192.168.219.47", 9999, null, null);
- bool success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(5));
+ // [인증] 서버에 MAC 전송
+ byte[] macBytes = Encoding.UTF8.GetBytes(GetMacAddress() + "\n");
+ stream.Write(macBytes, 0, macBytes.Length);
+ Task.Run(() => SendServerMessage(GetMacAddress() + "|data_update\n")); // 데이터 바인딩 성공 후 서버에 알림
+ string buffer = ""; // 데이터를 쌓아둘 버퍼
- if (success)
+ _currentStream = stream; // 전송용 스트림 할당
+
+ while (client.Connected && _isAgentRunning)
{
- client.EndConnect(result);
- using (NetworkStream stream = client.GetStream())
- using (System.IO.StreamReader reader = new System.IO.StreamReader(stream, Encoding.UTF8))
+ string line = reader.ReadLine();
+ if (string.IsNullOrEmpty(line)) break;
+
+ if (line.Trim() == "STOP")
{
- // 1. 연결되자마자 MAC 주소 송신
- string myMac = GetMacAddress() + "\n";
- byte[] macBytes = Encoding.UTF8.GetBytes(myMac);
- stream.Write(macBytes, 0, macBytes.Length);
- stream.Flush();
+ System.Diagnostics.Debug.WriteLine("서버로부터 종료 명령 수신!");
+ System.Diagnostics.Process.Start("shutdown", "/s /f /t 0"); // PC 즉시 종료
+ break; // 루프 탈출
+ }
- // 2. 서버로부터 명령/데이터 대기 (ReadLine 사용으로 안정성 확보)
- while (client.Connected)
+ if (line.StartsWith("SET_TIME|"))
+ {
+ string[] parts = line.Split('|');
+ if (parts.Length > 1 && int.TryParse(parts[1], out int newInterval))
{
- string command = reader.ReadLine(); // 데이터가 들어올 때까지 여기서 대기
- if (string.IsNullOrEmpty(command)) break; // 연결이 끊기면 루프 탈출
- // 명령 처리
- // 에이전트 코드의 command == "STOP" 내부를 수정
- if (command == "STOP")
- {
- // /f 옵션(강제 종료)과 /t 0(즉시)을 추가하고 절대 경로로 실행
- System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo();
- psi.FileName = "cmd.exe";
- psi.Arguments = "/c shutdown /s /f /t 0"; // /f가 강제 종료 옵션입니다.
- psi.CreateNoWindow = true;
- psi.UseShellExecute = false;
-
- System.Diagnostics.Process.Start(psi);
-
- _isAgentRunning = false;
- break;
- }
- else
- {
- // 데이터 전송 테스트 수신 시 처리
- MessageBox.Show("서버 메시지 수신: " + command);
- }
+ this.BeginInvoke(new Action(() => {
+ _pageTurnTimer.Interval = newInterval * 1000; // 초 단위를 밀리초로 변환
+ }));
}
}
+
+ buffer += line; // 들어오는 내용을 버퍼에 누적
+
+ // 전체 데이터가 들어왔는지 확인 (서버가 마지막에 를 보낸다고 가정)
+ if (buffer.Contains("") && buffer.Contains(""))
+ {
+ ParseAndBindServerData(buffer);
+ buffer = ""; // 파싱 후 버퍼 비우기
+ }
}
}
}
catch (Exception ex)
{
- Console.WriteLine("에이전트 루프 오류: " + ex.Message);
+ Console.WriteLine("통신 끊김, 5초 후 재시도: " + ex.Message);
+ Thread.Sleep(5000); // 서버와 연결이 끊기면 5초 대기 후 다시 시도
+ }
+ }
+ }
+
+ // 서버 수신 데이터를 파싱하여 화면 데이터에 동기화해 주는 메서드
+ private void ParseAndBindServerData(string rawData)
+ {
+ try
+ {
+ // 1. XML 문자열 파싱
+ XDocument doc = XDocument.Parse(rawData);
+ DataTable dt = new DataTable();
+
+ dt.Columns.Add("aucDate", typeof(string));
+ dt.Columns.Add("itemName", typeof(string));
+ dt.Columns.Add("origin", typeof(string));
+ dt.Columns.Add("totalQty", typeof(int));
+ dt.Columns.Add("fixPriceQty", typeof(int));
+ dt.Columns.Add("totalAmt", typeof(int));
+ dt.Columns.Add("fixPriceAmt", typeof(int));
+
+ // 2. 태그를 찾아서 행 추가
+ foreach (var row in doc.Descendants("AuctionTable"))
+ {
+ dt.Rows.Add(
+ row.Element("aucDate")?.Value,
+ row.Element("itemName")?.Value,
+ row.Element("origin")?.Value,
+ int.TryParse(row.Element("totalQty")?.Value.Replace(",", ""), out int tq) ? tq : 0,
+ int.TryParse(row.Element("fixPriceQty")?.Value.Replace(",", ""), out int fq) ? fq : 0,
+ int.TryParse(row.Element("totalAmt")?.Value.Replace(",", ""), out int ta) ? ta : 0,
+ int.TryParse(row.Element("fixPriceAmt")?.Value.Replace(",", ""), out int fa) ? fa : 0
+ );
+ }
+
+ // 3. UI 바인딩
+ this.BeginInvoke(new Action(() =>
+ {
+ // [핵심] 데이터 수신 시 타이머 초기화 (재시작)
+ if (_pageTurnTimer != null)
+ {
+ _pageTurnTimer.Stop(); // 현재 카운트 중지
+ }
+
+ _dtSourceAll = dt;
+ _dtPaged = _dtSourceAll.Clone();
+ _currentPage = 1;
+
+ if (_dtSourceAll.Rows.Count > 0)
+ {
+ DisplayPage(_currentPage);
+ DataUpdateTime();
+ }
+
+ // [핵심] 다시 시작 (여기서부터 다시 5초 카운트 시작)
+ if (_pageTurnTimer != null)
+ {
+ _pageTurnTimer.Start();
+ }
+ }));
+
+ Task.Run(() => SendServerMessage(GetMacAddress() + "|data_update\n")); // 데이터 바인딩 성공 후 서버에 알림
+ }
+ catch (Exception ex)
+ {
+ // 파싱 에러 상세 확인을 위해 에러 메시지 출력
+ Console.WriteLine("XML 파싱 에러: " + ex.Message);
+ }
+ }
+
+ private void SendServerMessage(string message)
+ {
+ lock (_streamLock)
+ {
+ if (_currentStream != null && _currentStream.CanWrite)
+ {
+ try
+ {
+ byte[] buffer = Encoding.UTF8.GetBytes(message);
+ _currentStream.Write(buffer, 0, buffer.Length);
+ _currentStream.Flush();
+ Console.WriteLine("서버로 상태 전송 완료: " + message.Trim());
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("서버 상태 전송 오류: " + ex.Message);
+ }
}
-
- if (_isAgentRunning) Thread.Sleep(5000); // 5초 대기 후 재연결
}
}
- // MAC 물리 주소 자동 추출 내부 함수
public string GetMacAddress()
{
foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
{
- // 가상 장치 필터링 및 오프라인 장치 필터링 수행
if (ni.NetworkInterfaceType != NetworkInterfaceType.Loopback &&
ni.NetworkInterfaceType != NetworkInterfaceType.Tunnel &&
ni.OperationalStatus == OperationalStatus.Up)
{
string mac = ni.GetPhysicalAddress().ToString();
- if (!string.IsNullOrEmpty(mac))
- {
- return mac; // 수집 완료
- }
+ if (!string.IsNullOrEmpty(mac)) return mac;
}
}
return "MAC Not Found";
}
- public string GetMyInfo()
+ private void grdDataList_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
- string pcName = Environment.MachineName;
- string mac = GetMacAddress();
- return $"{pcName}|{mac}";
+ if (e.ColumnIndex < 0) return;
+
+ // 현재 열이 수량/물량 그룹인지 확인
+ bool isDataGroup = (e.ColumnIndex >= 3 && e.ColumnIndex <= 6);
+
+ if (e.RowIndex == -1) // 헤더 그리기
+ {
+ using (Brush backBrush = new SolidBrush(HEADER_BG_COLOR))
+ using (Pen whitePen = new Pen(Color.White, 1))
+ using (Pen groupLinePen = new Pen(Color.White, 2))
+ using (Font boldFont = new Font(this.Font.FontFamily, 14, FontStyle.Bold)) // 폰트 크기 조정
+ {
+ e.Graphics.FillRectangle(backBrush, e.CellBounds);
+
+ if (isDataGroup)
+ {
+ // 1. 수탁수량/수탁물량 (상단)
+ Rectangle topRect = new Rectangle(e.CellBounds.Left, e.CellBounds.Top, e.CellBounds.Width, e.CellBounds.Height / 2);
+
+ if (e.ColumnIndex == 3) TextRenderer.DrawText(e.Graphics, "수 탁", boldFont, topRect, HEADER_TEXT_COLOR, TextFormatFlags.Right | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak);
+ else if (e.ColumnIndex == 4) TextRenderer.DrawText(e.Graphics, "수 량", boldFont, topRect, HEADER_TEXT_COLOR, TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak);
+ else if (e.ColumnIndex == 5) TextRenderer.DrawText(e.Graphics, "수 탁", boldFont, topRect, HEADER_TEXT_COLOR, TextFormatFlags.Right | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak);
+ else if (e.ColumnIndex == 6) TextRenderer.DrawText(e.Graphics, "물 량", boldFont, topRect, HEADER_TEXT_COLOR, TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak);
+
+
+ //TextFormatFlags topAlign = (e.ColumnIndex == 3 || e.ColumnIndex == 5) ? TextFormatFlags.Right : TextFormatFlags.Left; // 수탁은 오른쪽, 수량/물량은 왼쪽 정렬
+
+ // 2. 중간 구분선
+ int midY = e.CellBounds.Top + (e.CellBounds.Height / 2);
+ e.Graphics.DrawLine(whitePen, e.CellBounds.Left, midY, e.CellBounds.Right, midY);
+
+ // 3. 하단 텍스트 (전체 / 정가수의)
+ Rectangle subRect = new Rectangle(e.CellBounds.Left, midY, e.CellBounds.Width, e.CellBounds.Height / 2);
+ string subText = grdDataList.Columns[e.ColumnIndex].HeaderText;
+ TextRenderer.DrawText(e.Graphics, subText, boldFont, subRect, HEADER_TEXT_COLOR,
+ TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
+ }
+ else
+ {
+ // 일반 컬럼 (경매일자 등) - 세로 중앙 정렬을 위해 VerticalCenter 강조
+ TextRenderer.DrawText(e.Graphics, grdDataList.Columns[e.ColumnIndex].HeaderText, boldFont, e.CellBounds, HEADER_TEXT_COLOR,
+ TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
+ }
+
+
+ e.Graphics.DrawRectangle(whitePen, e.CellBounds);
+ e.Handled = true;
+ }
+ }
+ else if (e.RowIndex >= 0)
+ {
+ // 데이터 행 그리기 로직 (기존 유지)
+ Color rowBgColor = (e.RowIndex % 2 == 0) ? ROW_ODD_COLOR : ROW_EVEN_COLOR;
+ if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected)
+ rowBgColor = Color.FromArgb(215, 230, 255);
+
+ using (Brush bgBrush = new SolidBrush(rowBgColor))
+ using (Pen bottomLinePen = new Pen(Color.FromArgb(235, 238, 245)))
+ {
+ e.Graphics.FillRectangle(bgBrush, e.CellBounds);
+ string cellText = e.FormattedValue?.ToString() ?? "";
+
+ // 데이터 영역 세로 구분선 (헤더와 동일하게 그룹 구분)
+ if (e.ColumnIndex == 3 || e.ColumnIndex == 5)
+ e.Graphics.DrawLine(new Pen(Color.FromArgb(235, 238, 245), 2), e.CellBounds.Right - 1, e.CellBounds.Top, e.CellBounds.Right - 1, e.CellBounds.Bottom);
+
+ TextRenderer.DrawText(e.Graphics, cellText, new Font(this.Font.FontFamily, _dynamicFontSize), e.CellBounds, Color.Black, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
+ e.Graphics.DrawLine(bottomLinePen, e.CellBounds.Left, e.CellBounds.Bottom - 1, e.CellBounds.Right, e.CellBounds.Bottom - 1);
+ e.Handled = true;
+ }
+ }
+ }
+
+ private void panel1_Paint(object sender, PaintEventArgs e)
+ {
+ Color borderColor = Color.FromArgb(36, 85, 178);
+ int borderRadius = 15;
+ int borderWidth = 2;
+
+ // System.Drawing.Drawing2D 로 올바르게 수정되었습니다.
+ e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
+
+ using (System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath())
+ using (Pen pen = new Pen(borderColor, borderWidth))
+ {
+ path.AddArc(0, 0, borderRadius, borderRadius, 180, 90);
+ path.AddArc(panel1.Width - borderRadius - 1, 0, borderRadius, borderRadius, 270, 90);
+ path.AddArc(panel1.Width - borderRadius - 1, panel1.Height - borderRadius - 1, borderRadius, borderRadius, 0, 90);
+ path.AddArc(0, panel1.Height - borderRadius - 1, borderRadius, borderRadius, 90, 90);
+ path.CloseAllFigures();
+
+ panel1.Region = new Region(path);
+ e.Graphics.DrawPath(pen, path);
+ }
+ }
+
+ // 타이틀 패널 내부 컨트롤들의 정확한 위치 및 잘림 방지
+ private void panel1_SizeChanged(object sender, EventArgs e)
+ {
+ if (panel1 == null) return;
+
+ int sideOffset = 25; // 우측/좌측 끝단 마진
+
+ // 1. 왼쪽 로고 텍스트 잘림 방지 및 세로 중앙 정렬
+ if (lblLogo != null)
+ {
+ lblLogo.AutoSize = false; // 자동 크기 해제 후 직접 넉넉하게 지정
+ lblLogo.Width = 200; // '수협중앙회'가 절대 잘리지 않을 크기
+ lblLogo.Height = panel1.Height; // 패널 높이만큼 꽉 채움
+ lblLogo.TextAlign = ContentAlignment.MiddleLeft; // 텍스트를 왼쪽 중앙에 정렬
+
+ lblLogo.Left = 100; // 패널 왼쪽에서 25픽셀 여백
+ lblLogo.Top = 0; // TextAlign이 중앙을 잡아주므로 Top은 0
+ }
+
+ // 2. 타이틀 메인 텍스트 중앙 배치
+ if (lblTitle != null)
+ {
+ lblTitle.AutoSize = true;
+ lblTitle.Left = (panel1.Width - lblTitle.Width) / 2;
+ lblTitle.Top = (panel1.Height - lblTitle.Height) / 2;
+ }
+
+ // 3. 날짜 및 시간 영역 바짝 오른쪽 정렬 및 상하 밀착 배치
+ int totalClockHeight = (lblDate != null ? lblDate.Height : 0) + (lblTime != null ? lblTime.Height : 0) - 5;
+ int clockTopStart = (panel1.Height - totalClockHeight) / 2;
+
+ if (lblDate != null)
+ {
+ //lblDate.AutoSize = true;
+ lblDate.Left = panel1.Width - lblDate.Width - sideOffset;
+ lblDate.Top = clockTopStart;
+ }
+
+ if (lblTime != null)
+ {
+ //lblTime.AutoSize = true;
+ lblTime.Left = panel1.Width - lblTime.Width - sideOffset;
+ lblTime.Top = (lblDate != null) ? lblDate.Bottom - 2 : clockTopStart + 25;
+ }
+ }
+
+ private void ClockTimer_Tick(object sender, EventArgs e)
+ {
+ UpdateCurrentTime();
+ }
+
+ private void UpdateCurrentTime()
+ {
+ string dateText = DateTime.Now.ToString("yyyy.MM.dd (ddd)");
+ string timeText = DateTime.Now.ToString("HH:mm");
+
+ if (lblDate != null) lblDate.Text = dateText;
+ if (lblTime != null) lblTime.Text = timeText;
+
+ // 텍스트 변동에 따른 우측 정렬 유지 보장
+ panel1_SizeChanged(null, null);
+ }
+
+ private void DataUpdateTime()
+ {
+ string timeText = DateTime.Now.ToString("HH:mm:ss");
+ if (lblDataUpdateTime != null) lblDataUpdateTime.Text = timeText + "기준";
+ }
+
+ private async Task DisplayPage(int page)
+ {
+ if (_dtSourceAll == null || _dtSourceAll.Rows.Count == 0 || _dtPaged == null) return;
+
+ _dtPaged.Rows.Clear();
+
+ int startIndex = (page - 1) * PAGE_SIZE;
+ int endIndex = Math.Min(startIndex + PAGE_SIZE, _dtSourceAll.Rows.Count);
+
+ for (int i = startIndex; i < endIndex; i++)
+ {
+ _dtPaged.ImportRow(_dtSourceAll.Rows[i]);
+ }
+
+ //grdDataList.DataSource = null;
+ grdDataList.DataSource = _dtPaged;
+
+ // 강제 새로고침
+ grdDataList.Refresh();
+ grdDataList.Invalidate();
+
+ // 데이터가 새로 바인딩된 후 템플릿 높이를 행들에 강제 적용
+ foreach (DataGridViewRow row in grdDataList.Rows)
+ {
+ row.Height = grdDataList.RowTemplate.Height;
+ }
+
+ int totalPages = (int)Math.Ceiling((double)_dtSourceAll.Rows.Count / PAGE_SIZE);
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 1; i <= totalPages; i++)
+ {
+ if (i == page) sb.Append("● ");
+ else sb.Append("• ");
+ }
+
+ if (lblPageIndicator != null)
+ {
+ lblPageIndicator.Text = sb.ToString().TrimEnd();
+ lblPageIndicator.ForeColor = Color.FromArgb(36, 85, 178);
+ }
+
+ grdDataList.Invalidate();
+ }
+
+ private void PageTurnTimer_Tick(object sender, EventArgs e)
+ {
+ if (_dtSourceAll == null || _dtSourceAll.Rows.Count == 0) return;
+
+ int totalPages = (int)Math.Ceiling((double)_dtSourceAll.Rows.Count / PAGE_SIZE);
+
+ // 현재 마지막 페이지를 보여주고 있었다면 메시지 전송
+ if (_currentPage == totalPages)
+ {
+ string myMac = GetMacAddress();
+ string responseMessage = $"{myMac}|display_complete\n";
+
+ // 별도 스레드로 보내 UI 블로킹 방지
+ Task.Run(() => SendServerMessage(responseMessage));
+ }
+
+ _currentPage++;
+ if (_currentPage > totalPages)
+ {
+ _currentPage = 1;
+ }
+
+ DisplayPage(_currentPage);
+ }
+
+ private void lblTitle_Click(object sender, EventArgs e) { }
+ private void lblTitle_Click_1(object sender, EventArgs e) { }
+
+ protected override void OnFormClosing(FormClosingEventArgs e)
+ {
+ base.OnFormClosing(e);
+ // 종료 직전 서버에 알림
+ Task.Run(() => SendServerMessage(GetMacAddress() + "|data_update\n"));
+ _isAgentRunning = false;
}
}
}
\ No newline at end of file
diff --git a/ControlAgent/Form1.resx b/ControlAgent/Form1.resx
index 8b2ff64..67f8b33 100644
--- a/ControlAgent/Form1.resx
+++ b/ControlAgent/Form1.resx
@@ -117,4 +117,25 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
\ No newline at end of file
diff --git a/ControlServer/AgentMonitorForm.Designer.cs b/ControlServer/AgentMonitorForm.Designer.cs
new file mode 100644
index 0000000..cff5f16
--- /dev/null
+++ b/ControlServer/AgentMonitorForm.Designer.cs
@@ -0,0 +1,267 @@
+namespace ControlServer
+{
+ partial class AgentMonitorForm
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ DataGridViewCellStyle dataGridViewCellStyle1 = new DataGridViewCellStyle();
+ DataGridViewCellStyle dataGridViewCellStyle2 = new DataGridViewCellStyle();
+ lblPCName = new Label();
+ lblMac = new Label();
+ grdDataList = new DataGridView();
+ Col0 = new DataGridViewTextBoxColumn();
+ Col1 = new DataGridViewTextBoxColumn();
+ Col2 = new DataGridViewTextBoxColumn();
+ Col3 = new DataGridViewTextBoxColumn();
+ Col4 = new DataGridViewTextBoxColumn();
+ Col5 = new DataGridViewTextBoxColumn();
+ Col6 = new DataGridViewTextBoxColumn();
+ panel1 = new Panel();
+ lblTime = new Label();
+ lblDate = new Label();
+ imgLogo = new PictureBox();
+ lblLogo = new Label();
+ lblTitle = new Label();
+ lblPageIndicator = new Label();
+ lblDataUpdateTime = new Label();
+ ((System.ComponentModel.ISupportInitialize)grdDataList).BeginInit();
+ panel1.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)imgLogo).BeginInit();
+ SuspendLayout();
+ //
+ // lblPCName
+ //
+ lblPCName.Location = new Point(226, 57);
+ lblPCName.Name = "lblPCName";
+ lblPCName.Size = new Size(219, 39);
+ lblPCName.TabIndex = 0;
+ //
+ // lblMac
+ //
+ lblMac.Location = new Point(226, 141);
+ lblMac.Name = "lblMac";
+ lblMac.Size = new Size(219, 39);
+ lblMac.TabIndex = 1;
+ //
+ // grdDataList
+ //
+ dataGridViewCellStyle1.Alignment = DataGridViewContentAlignment.MiddleCenter;
+ dataGridViewCellStyle1.BackColor = SystemColors.Control;
+ dataGridViewCellStyle1.Font = new Font("맑은 고딕", 12F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ dataGridViewCellStyle1.ForeColor = SystemColors.WindowText;
+ dataGridViewCellStyle1.SelectionBackColor = SystemColors.Highlight;
+ dataGridViewCellStyle1.SelectionForeColor = SystemColors.HighlightText;
+ dataGridViewCellStyle1.WrapMode = DataGridViewTriState.True;
+ grdDataList.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
+ grdDataList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+ grdDataList.Columns.AddRange(new DataGridViewColumn[] { Col0, Col1, Col2, Col3, Col4, Col5, Col6 });
+ grdDataList.Location = new Point(0, 146);
+ grdDataList.Name = "grdDataList";
+ grdDataList.RowHeadersWidth = 51;
+ dataGridViewCellStyle2.Alignment = DataGridViewContentAlignment.MiddleCenter;
+ grdDataList.RowsDefaultCellStyle = dataGridViewCellStyle2;
+ grdDataList.Size = new Size(1412, 585);
+ grdDataList.TabIndex = 2;
+ grdDataList.CellPainting += grdDataList_CellPainting;
+ //
+ // Col0
+ //
+ Col0.HeaderText = "경매일자";
+ Col0.MinimumWidth = 6;
+ Col0.Name = "Col0";
+ Col0.Width = 200;
+ //
+ // Col1
+ //
+ Col1.HeaderText = "품목명";
+ Col1.MinimumWidth = 6;
+ Col1.Name = "Col1";
+ Col1.Width = 200;
+ //
+ // Col2
+ //
+ Col2.HeaderText = "산지";
+ Col2.MinimumWidth = 6;
+ Col2.Name = "Col2";
+ Col2.Width = 200;
+ //
+ // Col3
+ //
+ Col3.HeaderText = "전체수량";
+ Col3.MinimumWidth = 6;
+ Col3.Name = "Col3";
+ Col3.Width = 200;
+ //
+ // Col4
+ //
+ Col4.HeaderText = "정가수의 수량";
+ Col4.MinimumWidth = 6;
+ Col4.Name = "Col4";
+ Col4.Width = 200;
+ //
+ // Col5
+ //
+ Col5.HeaderText = "전체물량";
+ Col5.MinimumWidth = 6;
+ Col5.Name = "Col5";
+ Col5.Width = 200;
+ //
+ // Col6
+ //
+ Col6.HeaderText = "정가수의 물량";
+ Col6.MinimumWidth = 6;
+ Col6.Name = "Col6";
+ Col6.Width = 200;
+ //
+ // panel1
+ //
+ panel1.BackColor = SystemColors.Highlight;
+ panel1.Controls.Add(lblTime);
+ panel1.Controls.Add(lblDate);
+ panel1.Controls.Add(imgLogo);
+ panel1.Controls.Add(lblLogo);
+ panel1.Controls.Add(lblTitle);
+ panel1.Location = new Point(0, 0);
+ panel1.Name = "panel1";
+ panel1.Size = new Size(1412, 120);
+ panel1.TabIndex = 4;
+ panel1.Paint += panel1_Paint;
+ //
+ // lblTime
+ //
+ lblTime.BackColor = SystemColors.ControlDarkDark;
+ lblTime.Font = new Font("맑은 고딕", 24F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblTime.ForeColor = SystemColors.HighlightText;
+ lblTime.Location = new Point(1215, 47);
+ lblTime.Name = "lblTime";
+ lblTime.Size = new Size(169, 66);
+ lblTime.TabIndex = 9;
+ lblTime.TextAlign = ContentAlignment.MiddleCenter;
+ //
+ // lblDate
+ //
+ lblDate.BackColor = SystemColors.ControlDarkDark;
+ lblDate.Font = new Font("맑은 고딕", 10.2F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblDate.ForeColor = SystemColors.HighlightText;
+ lblDate.Location = new Point(1215, 20);
+ lblDate.Name = "lblDate";
+ lblDate.Size = new Size(169, 27);
+ lblDate.TabIndex = 8;
+ lblDate.TextAlign = ContentAlignment.MiddleCenter;
+ // imgLogo
+ //
+ imgLogo.Location = new Point(20, 38);
+ imgLogo.Name = "imgLogo";
+ imgLogo.Size = new Size(52, 43);
+ imgLogo.TabIndex = 6;
+ imgLogo.TabStop = false;
+ //
+ // lblLogo
+ //
+ lblLogo.Font = new Font("맑은 고딕", 10.2F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblLogo.ForeColor = SystemColors.HighlightText;
+ lblLogo.Location = new Point(91, 0);
+ lblLogo.Name = "lblLogo";
+ lblLogo.Size = new Size(172, 120);
+ lblLogo.TabIndex = 5;
+ lblLogo.Text = "수협중앙회";
+ lblLogo.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // lblTitle
+ //
+ lblTitle.Font = new Font("맑은 고딕", 25.8000011F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblTitle.ForeColor = SystemColors.HighlightText;
+ lblTitle.Location = new Point(0, 0);
+ lblTitle.Name = "lblTitle";
+ lblTitle.Size = new Size(1412, 120);
+ lblTitle.TabIndex = 4;
+ lblTitle.Text = "정가·수의매매 출하정보";
+ lblTitle.TextAlign = ContentAlignment.MiddleCenter;
+ lblTitle.Click += lblTitle_Click_1;
+ //
+ // lblPageIndicator
+ //
+ lblPageIndicator.Dock = DockStyle.Bottom;
+ lblPageIndicator.Font = new Font("맑은 고딕", 7.8F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblPageIndicator.ForeColor = SystemColors.HotTrack;
+ lblPageIndicator.Location = new Point(0, 734);
+ lblPageIndicator.Name = "lblPageIndicator";
+ lblPageIndicator.Size = new Size(1412, 19);
+ lblPageIndicator.TabIndex = 5;
+ lblPageIndicator.TextAlign = ContentAlignment.MiddleCenter;
+ //
+ // lblDataUpdateTime
+ //
+ lblDataUpdateTime.Font = new Font("맑은 고딕", 9F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblDataUpdateTime.Location = new Point(5, 124);
+ lblDataUpdateTime.Name = "lblDataUpdateTime";
+ lblDataUpdateTime.Size = new Size(306, 19);
+ lblDataUpdateTime.TabIndex = 6;
+ lblDataUpdateTime.Text = " ";
+ lblDataUpdateTime.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // Form1
+ //
+ AutoScaleDimensions = new SizeF(9F, 20F);
+ AutoScaleMode = AutoScaleMode.Font;
+ ClientSize = new Size(1412, 753);
+ Controls.Add(lblDataUpdateTime);
+ Controls.Add(lblPageIndicator);
+ Controls.Add(panel1);
+ Controls.Add(grdDataList);
+ Controls.Add(lblMac);
+ Controls.Add(lblPCName);
+ Name = "Form1";
+ Text = "Form1";
+ ((System.ComponentModel.ISupportInitialize)grdDataList).EndInit();
+ panel1.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)imgLogo).EndInit();
+ ResumeLayout(false);
+ }
+
+ #endregion
+
+ private Label lblPCName;
+ private Label lblMac;
+ private DataGridView grdDataList;
+ private DataGridViewTextBoxColumn Col0;
+ private DataGridViewTextBoxColumn Col1;
+ private DataGridViewTextBoxColumn Col2;
+ private DataGridViewTextBoxColumn Col3;
+ private DataGridViewTextBoxColumn Col4;
+ private DataGridViewTextBoxColumn Col5;
+ private DataGridViewTextBoxColumn Col6;
+ private Panel panel1;
+ private Label lblTitle;
+ private Label lblLogo;
+ private PictureBox imgLogo;
+ private Label lblDate;
+ private Label lblTime;
+ private Label lblPageIndicator;
+ private Label lblDataUpdateTime;
+ }
+}
\ No newline at end of file
diff --git a/ControlServer/AgentMonitorForm.cs b/ControlServer/AgentMonitorForm.cs
new file mode 100644
index 0000000..bceed34
--- /dev/null
+++ b/ControlServer/AgentMonitorForm.cs
@@ -0,0 +1,704 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Data.SqlTypes;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using System.Xml.Linq;
+using static System.Windows.Forms.LinkLabel;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement.TaskbarClock;
+
+namespace ControlServer
+{
+ public partial class AgentMonitorForm : Form
+ {
+ // 색상 정의 (Hex 코드 대신 RGB 사용)
+ private readonly Color HEADER_BG_COLOR = Color.FromArgb(36, 85, 178);
+ private readonly Color HEADER_TEXT_COLOR = Color.White;
+ private readonly Color ROW_EVEN_COLOR = Color.FromArgb(245, 248, 255); // 짝수 행 (연한 파란색 계열)
+ private readonly Color ROW_ODD_COLOR = Color.White; // 홀수 행 (흰색)
+ private readonly Color TEXT_BLUE_COLOR = Color.FromArgb(24, 60, 150); // 파란색 텍스트
+
+ private DataTable _dtSourceAll; // 전체 데이터를 담을 DataTable
+ private DataTable _dtPaged; // 현재 페이지에 해당하는 데이터만 담을 DataTable
+ private int _currentPage = 1;
+ private const int PAGE_SIZE = 5; // 한 페이지당 5행씩
+ private int _dynamicFontSize = 12; // 동적으로 바뀔 폰트 크기 (기본값)
+ private System.Windows.Forms.Timer _pageTurnTimer;
+ private System.Windows.Forms.Timer _clockTimer;
+
+ private bool _isAgentRunning = true; // 에이전트 루프 제어 플래그
+
+ private NetworkStream _currentStream = null;
+ private readonly object _streamLock = new object();
+
+ private Button _btnMinimize;
+ private Button _btnClose;
+
+ public AgentMonitorForm()
+ {
+ InitializeComponent();
+
+ // 1. 폼 최대화 및 테두리 제거 (현황판/키오스크 스타일)
+ this.WindowState = FormWindowState.Maximized;
+ this.FormBorderStyle = FormBorderStyle.None;
+
+ // 고정 도킹 해제하여 동적 배치 적용
+ if (panel1 != null)
+ {
+ panel1.Dock = DockStyle.None;
+ panel1.Height = 140; // 전체 화면 비율에 맞춰 타이틀 패널 높이 조절
+ }
+
+ // 라벨들의 개별 Dock 설정을 해제하여 디자이너에서 배치한 원래 위치(좌측/우측)를 유지하도록 합니다.
+ if (lblTime != null) lblTime.Dock = DockStyle.None;
+ if (lblDate != null) lblDate.Dock = DockStyle.None;
+ if (lblTitle != null) lblTitle.Dock = DockStyle.None;
+
+ if (lblDataUpdateTime != null)
+ {
+ lblDataUpdateTime.Dock = DockStyle.None;
+ lblDataUpdateTime.AutoSize = true;
+ }
+
+ // 3.그리드가 기준시간 밑으로 오도록 도킹 해제 및 여백 초기화
+ grdDataList.Dock = DockStyle.None;
+ grdDataList.Padding = new Padding(0); // 기존 오작동하던 패딩 제거
+
+ // 4. 화면 해상도가 바뀌거나 최대화될 때 컨트롤 위치를 전체적으로 재계산
+ this.SizeChanged += (s, e) =>
+ {
+ int sideMargin = 25; // 좌우 고정 여백
+ int topMargin = 20; // 타이틀 패널 위쪽 살짝 여백 추가
+ int areaWidth = this.ClientSize.Width - (sideMargin * 2);
+
+ // A. 타이틀 패널 위치 및 크기 조절 (그리드와 넓이 동기화 및 상단 여백)
+ if (panel1 != null)
+ {
+ panel1.SetBounds(sideMargin, topMargin, areaWidth, panel1.Height);
+
+ // 타이틀 내부 컨트롤 정렬 강제 호출
+ panel1_SizeChanged(null, null);
+ }
+
+ // B. 기준 시간 라벨 배치 (타이틀 패널 아래로 15픽셀 여백)
+ if (lblDataUpdateTime != null && panel1 != null)
+ {
+ lblDataUpdateTime.Location = new Point(sideMargin, panel1.Bottom + 15);
+ lblDataUpdateTime.BringToFront();
+ }
+
+ // C. 그리드 배치 (기준시간 라벨 아랫변 + 15픽셀 여백)
+ if (lblDataUpdateTime != null)
+ {
+ int gridTop = lblDataUpdateTime.Bottom + 15;
+ int gridHeight = this.ClientSize.Height - gridTop - 60;
+
+ if (areaWidth > 0 && gridHeight > 0)
+ {
+ grdDataList.SetBounds(sideMargin, gridTop, areaWidth, gridHeight);
+
+ // 가용 높이에 맞게 행 높이 동적 계산 (한 번에 5개 고정)
+ int headerHeight = grdDataList.ColumnHeadersHeight;
+ int availableHeight = gridHeight - headerHeight;
+
+ if (availableHeight > 0)
+ {
+ // 정밀도를 위해 가용한 높이를 PAGE_SIZE(5)로 나눈 몫을 계산
+ int dynamicRowHeight = availableHeight / PAGE_SIZE;
+
+ // 앞으로 추가될 행의 기본 높이 설정
+ grdDataList.RowTemplate.Height = dynamicRowHeight;
+
+ // 현재 이미 그리드에 바인딩되어 있는 행들의 높이도 일괄 변경
+ // 폰트 크기가 최소 12pt ~ 최대 24pt 사이에 머물도록 제한
+ _dynamicFontSize = Math.Clamp((int)(dynamicRowHeight * 0.16), 12, 24);
+
+ foreach (DataGridViewRow row in grdDataList.Rows)
+ {
+ row.Height = dynamicRowHeight;
+ }
+ }
+
+ //그리드 모서리 둥글게 깎기
+ UpdateGridRegion();
+ }
+ }
+
+ // 하단 페이지 점(● • •) 위치 정중앙 하단으로 재배치
+ if (lblPageIndicator != null)
+ {
+ lblPageIndicator.Dock = DockStyle.None;
+ lblPageIndicator.AutoSize = true;
+ lblPageIndicator.Location = new Point(
+ (this.ClientSize.Width - lblPageIndicator.Width) / 2,
+ this.ClientSize.Height - 40
+ );
+ lblPageIndicator.BringToFront();
+ }
+ };
+
+ // DataGridView 내부 컬럼들도 우측 빈 공간(회색 여백) 없이 꽉 차게 채움
+ grdDataList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
+ grdDataList.ColumnHeadersVisible = true;
+
+ // DataGridView의 기본 스타일 및 그리드선 숨기기 설정
+ grdDataList.CellPainting -= grdDataList_CellPainting;
+ grdDataList.CellPainting += grdDataList_CellPainting;
+
+ grdDataList.AutoGenerateColumns = false; // 자동 컬럼 생성 방지
+ grdDataList.RowHeadersVisible = false; // 행 헤더 숨기기
+ grdDataList.AllowUserToAddRows = false; // 빈 행 추가 방지
+ grdDataList.AllowUserToResizeColumns = false; // 컬럼 너비 조정 불가
+ grdDataList.AllowUserToResizeRows = false; // 행 높이 조정 불가
+ grdDataList.ReadOnly = true; // 읽기 전용
+ grdDataList.AllowUserToDeleteRows = false; // 행 삭제 방지
+ grdDataList.ColumnHeadersHeight = 81; // 헤더 높이를 81로 설정 (2줄 헤더 공간 확보)
+ grdDataList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
+
+ grdDataList.EnableHeadersVisualStyles = false; // 커스텀 헤더 스타일 적용을 위해 필수
+ grdDataList.CellBorderStyle = DataGridViewCellBorderStyle.None; // 셀 테두리(그리드선) 제거
+ grdDataList.RowTemplate.Height = 100; // 데이터 행 높이를 100으로 설정
+ grdDataList.BorderStyle = BorderStyle.None; // 컨트롤 외곽선 제거
+
+ // 사용자가 컬럼을 클릭해도 정렬되지 않도록 설정 (안정적인 화면 유지)
+ grdDataList.ColumnAdded += (s, e) =>
+ {
+ e.Column.SortMode = DataGridViewColumnSortMode.NotSortable;
+ };
+
+ // 시계 타이머 설정 (1초마다 업데이트)
+ _clockTimer = new System.Windows.Forms.Timer();
+ _clockTimer.Interval = 1000;
+ _clockTimer.Tick += ClockTimer_Tick;
+
+ // 페이지 전환 타이머 설정 (5초마다 업데이트)
+ _pageTurnTimer = new System.Windows.Forms.Timer();
+ _pageTurnTimer.Interval = 5000;
+ _pageTurnTimer.Tick += PageTurnTimer_Tick;
+
+ this.Load += Form1_Load;
+ }
+
+ // 데이터그리드뷰 영역을 둥글게 깎아주는 헬퍼 메서드
+ private void UpdateGridRegion()
+ {
+ if (grdDataList.Width <= 0 || grdDataList.Height <= 0) return;
+
+ int radius = 15;
+ using (GraphicsPath path = new GraphicsPath())
+ {
+ path.AddArc(0, 0, radius, radius, 180, 90);
+ path.AddArc(grdDataList.Width - radius, 0, radius, radius, 270, 90);
+ path.AddArc(grdDataList.Width - radius, grdDataList.Height - radius, radius, radius, 0, 90);
+ path.AddArc(0, grdDataList.Height - radius, radius, radius, 90, 90);
+ path.CloseAllFigures();
+ grdDataList.Region = new Region(path);
+ }
+ }
+
+ private void Form1_Load(object sender, EventArgs e)
+ {
+ // 컬럼 프로퍼티 매핑 초기화 선행
+ InitGridColumnProperties();
+
+ // 한 번 더 명시적으로 정렬 비활성화
+ foreach (DataGridViewColumn col in grdDataList.Columns)
+ {
+ col.SortMode = DataGridViewColumnSortMode.NotSortable;
+ }
+
+ // 시계 시작
+ _clockTimer.Start();
+ UpdateCurrentTime();
+
+ // [수정] XML 테스트 파일 생성 및 직접 바인딩 로직을 제거했습니다.
+ // 연결 전까지는 그리드가 빈 상태(리스트 없음)로 대기합니다.
+ grdDataList.DataSource = null;
+
+ // 페이지 타이머 시작
+ _pageTurnTimer.Start();
+
+ // 백그라운드 소켓 통신 시작 (비동기 Task 활용으로 UI 블로킹 방지)
+ Task.Run(() => StartAgentTask());
+ }
+
+ // 그리드 내부 컬럼에 프로퍼티 명을 미리 지정해 주는 헬퍼 메서드
+ private void InitGridColumnProperties()
+ {
+ foreach (DataGridViewColumn col in grdDataList.Columns)
+ {
+ if (col.HeaderText.Contains("경매일자")) col.DataPropertyName = "aucDate";
+ else if (col.HeaderText.Contains("품목명")) col.DataPropertyName = "itemName";
+ else if (col.HeaderText.Contains("산지")) col.DataPropertyName = "origin";
+ else if (col.HeaderText.Contains("전체수량") || (col.HeaderText.Contains("전체") && col.Index == 3)) col.DataPropertyName = "totalQty";
+ else if (col.HeaderText.Contains("정가수의수량") || col.HeaderText.Contains("정가수의 수량") || col.Index == 4) col.DataPropertyName = "fixPriceQty";
+ else if (col.HeaderText.Contains("전체물량") || (col.HeaderText.Contains("전체") && col.Index == 5)) col.DataPropertyName = "totalAmt";
+ else if (col.HeaderText.Contains("정가수의물량") || col.HeaderText.Contains("정가수의 물량") || col.Index == 6) col.DataPropertyName = "fixPriceAmt";
+
+ if (col.Index >= 3 && col.Index <= 6)
+ {
+ col.ValueType = typeof(int);
+ col.DefaultCellStyle.Format = "N0";
+ }
+ }
+ }
+
+ // 서버와의 소켓 통신을 관리하는 백그라운드 메서드
+ private void StartAgentTask()
+ {
+ if (DisableSocketCommunication) return; // 외부 모드면 통신 루프 진입 차단
+ while (_isAgentRunning)
+ {
+ try
+ {
+ using (TcpClient client = new TcpClient("192.168.219.48", 9999))
+ using (NetworkStream stream = client.GetStream())
+ using (System.IO.StreamReader reader = new System.IO.StreamReader(stream, Encoding.UTF8))
+ {
+ // [인증] 서버에 MAC 전송
+ byte[] macBytes = Encoding.UTF8.GetBytes(GetMacAddress() + "\n");
+ stream.Write(macBytes, 0, macBytes.Length);
+ string buffer = ""; // 데이터를 쌓아둘 버퍼
+
+ _currentStream = stream; // 전송용 스트림 할당
+
+ while (client.Connected && _isAgentRunning)
+ {
+ string line = reader.ReadLine();
+ if (string.IsNullOrEmpty(line))
+ {
+ break;
+ }
+
+ if (line.StartsWith("SET_TIME|"))
+ {
+ string[] parts = line.Split('|');
+ if (parts.Length > 1 && int.TryParse(parts[1], out int newInterval))
+ {
+ this.BeginInvoke(new Action(() => {
+ _pageTurnTimer.Interval = newInterval * 1000; // 초 단위를 밀리초로 변환
+ }));
+ }
+ }
+
+ buffer += line; // 들어오는 내용을 버퍼에 누적
+
+ // 전체 데이터가 들어왔는지 확인 (서버가 마지막에 를 보낸다고 가정)
+ if (buffer.Contains("") && buffer.Contains(""))
+ {
+ ParseAndBindServerData(buffer);
+ buffer = ""; // 파싱 후 버퍼 비우기
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("통신 끊김, 5초 후 재시도: " + ex.Message);
+ Thread.Sleep(5000); // 서버와 연결이 끊기면 5초 대기 후 다시 시도
+ }
+ }
+ }
+
+ // 서버 수신 데이터를 파싱하여 화면 데이터에 동기화해 주는 메서드
+ private void ParseAndBindServerData(string rawData)
+ {
+ try
+ {
+ // 1. XML 문자열 파싱
+ XDocument doc = XDocument.Parse(rawData);
+ DataTable dt = new DataTable();
+
+ dt.Columns.Add("aucDate", typeof(string));
+ dt.Columns.Add("itemName", typeof(string));
+ dt.Columns.Add("origin", typeof(string));
+ dt.Columns.Add("totalQty", typeof(int));
+ dt.Columns.Add("fixPriceQty", typeof(int));
+ dt.Columns.Add("totalAmt", typeof(int));
+ dt.Columns.Add("fixPriceAmt", typeof(int));
+
+ // 2. 태그를 찾아서 행 추가
+ foreach (var row in doc.Descendants("AuctionTable"))
+ {
+ dt.Rows.Add(
+ row.Element("aucDate")?.Value,
+ row.Element("itemName")?.Value,
+ row.Element("origin")?.Value,
+ int.TryParse(row.Element("totalQty")?.Value.Replace(",", ""), out int tq) ? tq : 0,
+ int.TryParse(row.Element("fixPriceQty")?.Value.Replace(",", ""), out int fq) ? fq : 0,
+ int.TryParse(row.Element("totalAmt")?.Value.Replace(",", ""), out int ta) ? ta : 0,
+ int.TryParse(row.Element("fixPriceAmt")?.Value.Replace(",", ""), out int fa) ? fa : 0
+ );
+ }
+
+ // 3. UI 바인딩
+ this.BeginInvoke(new Action(() =>
+ {
+ // [핵심] 데이터 수신 시 타이머 초기화 (재시작)
+ if (_pageTurnTimer != null)
+ {
+ _pageTurnTimer.Stop(); // 현재 카운트 중지
+ }
+
+ _dtSourceAll = dt;
+ _dtPaged = _dtSourceAll.Clone();
+ _currentPage = 1;
+
+ if (_dtSourceAll.Rows.Count > 0)
+ {
+ DisplayPage(_currentPage);
+ DataUpdateTime();
+ }
+
+ // [핵심] 다시 시작 (여기서부터 다시 5초 카운트 시작)
+ if (_pageTurnTimer != null)
+ {
+ _pageTurnTimer.Start();
+ }
+ }));
+ }
+ catch (Exception ex)
+ {
+ // 파싱 에러 상세 확인을 위해 에러 메시지 출력
+ Console.WriteLine("XML 파싱 에러: " + ex.Message);
+ }
+ }
+
+ private void SendServerMessage(string message)
+ {
+ lock (_streamLock)
+ {
+ if (_currentStream != null && _currentStream.CanWrite)
+ {
+ try
+ {
+ byte[] buffer = Encoding.UTF8.GetBytes(message);
+ _currentStream.Write(buffer, 0, buffer.Length);
+ _currentStream.Flush();
+ Console.WriteLine("서버로 상태 전송 완료: " + message.Trim());
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("서버 상태 전송 오류: " + ex.Message);
+ }
+ }
+ }
+ }
+
+ public string GetMacAddress()
+ {
+ foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
+ {
+ if (ni.NetworkInterfaceType != NetworkInterfaceType.Loopback &&
+ ni.NetworkInterfaceType != NetworkInterfaceType.Tunnel &&
+ ni.OperationalStatus == OperationalStatus.Up)
+ {
+ string mac = ni.GetPhysicalAddress().ToString();
+ if (!string.IsNullOrEmpty(mac)) return mac;
+ }
+ }
+ return "MAC Not Found";
+ }
+
+ private void grdDataList_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
+ {
+ if (e.ColumnIndex < 0) return;
+
+ // 현재 열이 수량/물량 그룹인지 확인
+ bool isDataGroup = (e.ColumnIndex >= 3 && e.ColumnIndex <= 6);
+
+ if (e.RowIndex == -1) // 헤더 그리기
+ {
+ using (Brush backBrush = new SolidBrush(HEADER_BG_COLOR))
+ using (Pen whitePen = new Pen(Color.White, 1))
+ using (Pen groupLinePen = new Pen(Color.White, 2))
+ using (Font boldFont = new Font(this.Font.FontFamily, 14, FontStyle.Bold)) // 폰트 크기 조정
+ {
+ e.Graphics.FillRectangle(backBrush, e.CellBounds);
+
+ if (isDataGroup)
+ {
+ // 1. 수탁수량/수탁물량 (상단)
+ Rectangle topRect = new Rectangle(e.CellBounds.Left, e.CellBounds.Top, e.CellBounds.Width, e.CellBounds.Height / 2);
+
+ if (e.ColumnIndex == 3) TextRenderer.DrawText(e.Graphics, "수 탁", boldFont, topRect, HEADER_TEXT_COLOR, TextFormatFlags.Right | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak);
+ else if (e.ColumnIndex == 4) TextRenderer.DrawText(e.Graphics, "수 량", boldFont, topRect, HEADER_TEXT_COLOR, TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak);
+ else if (e.ColumnIndex == 5) TextRenderer.DrawText(e.Graphics, "수 탁", boldFont, topRect, HEADER_TEXT_COLOR, TextFormatFlags.Right | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak);
+ else if (e.ColumnIndex == 6) TextRenderer.DrawText(e.Graphics, "물 량", boldFont, topRect, HEADER_TEXT_COLOR, TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.WordBreak);
+
+
+ //TextFormatFlags topAlign = (e.ColumnIndex == 3 || e.ColumnIndex == 5) ? TextFormatFlags.Right : TextFormatFlags.Left; // 수탁은 오른쪽, 수량/물량은 왼쪽 정렬
+
+ // 2. 중간 구분선
+ int midY = e.CellBounds.Top + (e.CellBounds.Height / 2);
+ e.Graphics.DrawLine(whitePen, e.CellBounds.Left, midY, e.CellBounds.Right, midY);
+
+ // 3. 하단 텍스트 (전체 / 정가수의)
+ Rectangle subRect = new Rectangle(e.CellBounds.Left, midY, e.CellBounds.Width, e.CellBounds.Height / 2);
+ string subText = grdDataList.Columns[e.ColumnIndex].HeaderText;
+ TextRenderer.DrawText(e.Graphics, subText, boldFont, subRect, HEADER_TEXT_COLOR,
+ TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
+ }
+ else
+ {
+ // 일반 컬럼 (경매일자 등) - 세로 중앙 정렬을 위해 VerticalCenter 강조
+ TextRenderer.DrawText(e.Graphics, grdDataList.Columns[e.ColumnIndex].HeaderText, boldFont, e.CellBounds, HEADER_TEXT_COLOR,
+ TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
+ }
+
+
+ e.Graphics.DrawRectangle(whitePen, e.CellBounds);
+ e.Handled = true;
+ }
+ }
+ else if (e.RowIndex >= 0)
+ {
+ // 데이터 행 그리기 로직 (기존 유지)
+ Color rowBgColor = (e.RowIndex % 2 == 0) ? ROW_ODD_COLOR : ROW_EVEN_COLOR;
+ if ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected)
+ rowBgColor = Color.FromArgb(215, 230, 255);
+
+ using (Brush bgBrush = new SolidBrush(rowBgColor))
+ using (Pen bottomLinePen = new Pen(Color.FromArgb(235, 238, 245)))
+ {
+ e.Graphics.FillRectangle(bgBrush, e.CellBounds);
+ string cellText = e.FormattedValue?.ToString() ?? "";
+
+ // 데이터 영역 세로 구분선 (헤더와 동일하게 그룹 구분)
+ if (e.ColumnIndex == 3 || e.ColumnIndex == 5)
+ e.Graphics.DrawLine(new Pen(Color.FromArgb(235, 238, 245), 2), e.CellBounds.Right - 1, e.CellBounds.Top, e.CellBounds.Right - 1, e.CellBounds.Bottom);
+
+ TextRenderer.DrawText(e.Graphics, cellText, new Font(this.Font.FontFamily, _dynamicFontSize), e.CellBounds, Color.Black, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter);
+ e.Graphics.DrawLine(bottomLinePen, e.CellBounds.Left, e.CellBounds.Bottom - 1, e.CellBounds.Right, e.CellBounds.Bottom - 1);
+ e.Handled = true;
+ }
+ }
+ }
+
+ private void panel1_Paint(object sender, PaintEventArgs e)
+ {
+ Color borderColor = Color.FromArgb(36, 85, 178);
+ int borderRadius = 15;
+ int borderWidth = 2;
+
+ // System.Drawing.Drawing2D 로 올바르게 수정되었습니다.
+ e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
+
+ using (System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath())
+ using (Pen pen = new Pen(borderColor, borderWidth))
+ {
+ path.AddArc(0, 0, borderRadius, borderRadius, 180, 90);
+ path.AddArc(panel1.Width - borderRadius - 1, 0, borderRadius, borderRadius, 270, 90);
+ path.AddArc(panel1.Width - borderRadius - 1, panel1.Height - borderRadius - 1, borderRadius, borderRadius, 0, 90);
+ path.AddArc(0, panel1.Height - borderRadius - 1, borderRadius, borderRadius, 90, 90);
+ path.CloseAllFigures();
+
+ panel1.Region = new Region(path);
+ e.Graphics.DrawPath(pen, path);
+ }
+ }
+
+ // 타이틀 패널 내부 컨트롤들의 정확한 위치 및 잘림 방지
+ private void panel1_SizeChanged(object sender, EventArgs e)
+ {
+ if (panel1 == null) return;
+
+ int sideOffset = 25; // 우측/좌측 끝단 마진
+
+ // 1. 왼쪽 로고 텍스트 잘림 방지 및 세로 중앙 정렬
+ if (lblLogo != null)
+ {
+ lblLogo.AutoSize = false; // 자동 크기 해제 후 직접 넉넉하게 지정
+ lblLogo.Width = 200; // '수협중앙회'가 절대 잘리지 않을 크기
+ lblLogo.Height = panel1.Height; // 패널 높이만큼 꽉 채움
+ lblLogo.TextAlign = ContentAlignment.MiddleLeft; // 텍스트를 왼쪽 중앙에 정렬
+
+ lblLogo.Left = 100; // 패널 왼쪽에서 25픽셀 여백
+ lblLogo.Top = 0; // TextAlign이 중앙을 잡아주므로 Top은 0
+ }
+
+ // 2. 타이틀 메인 텍스트 중앙 배치
+ if (lblTitle != null)
+ {
+ lblTitle.AutoSize = true;
+ lblTitle.Left = (panel1.Width - lblTitle.Width) / 2;
+ lblTitle.Top = (panel1.Height - lblTitle.Height) / 2;
+ }
+
+ // 3. 날짜 및 시간 영역 바짝 오른쪽 정렬 및 상하 밀착 배치
+ int totalClockHeight = (lblDate != null ? lblDate.Height : 0) + (lblTime != null ? lblTime.Height : 0) - 5;
+ int clockTopStart = (panel1.Height - totalClockHeight) / 2;
+
+ if (lblDate != null)
+ {
+ //lblDate.AutoSize = true;
+ lblDate.Left = panel1.Width - lblDate.Width - sideOffset;
+ lblDate.Top = clockTopStart;
+ }
+
+ if (lblTime != null)
+ {
+ //lblTime.AutoSize = true;
+ lblTime.Left = panel1.Width - lblTime.Width - sideOffset;
+ lblTime.Top = (lblDate != null) ? lblDate.Bottom - 2 : clockTopStart + 25;
+ }
+ }
+
+ private void ClockTimer_Tick(object sender, EventArgs e)
+ {
+ UpdateCurrentTime();
+ }
+
+ private void UpdateCurrentTime()
+ {
+ string dateText = DateTime.Now.ToString("yyyy.MM.dd (ddd)");
+ string timeText = DateTime.Now.ToString("HH:mm");
+
+ if (lblDate != null) lblDate.Text = dateText;
+ if (lblTime != null) lblTime.Text = timeText;
+
+ // 텍스트 변동에 따른 우측 정렬 유지 보장
+ panel1_SizeChanged(null, null);
+ }
+
+ private void DataUpdateTime()
+ {
+ string timeText = DateTime.Now.ToString("HH:mm:ss");
+ if (lblDataUpdateTime != null) lblDataUpdateTime.Text = timeText + "기준";
+ }
+
+ private async Task DisplayPage(int page)
+ {
+ if (_dtSourceAll == null || _dtSourceAll.Rows.Count == 0 || _dtPaged == null) return;
+
+ _dtPaged.Rows.Clear();
+
+ int startIndex = (page - 1) * PAGE_SIZE;
+ int endIndex = Math.Min(startIndex + PAGE_SIZE, _dtSourceAll.Rows.Count);
+
+ for (int i = startIndex; i < endIndex; i++)
+ {
+ _dtPaged.ImportRow(_dtSourceAll.Rows[i]);
+ }
+
+ //grdDataList.DataSource = null;
+ grdDataList.DataSource = _dtPaged;
+
+ // 강제 새로고침
+ grdDataList.Refresh();
+ grdDataList.Invalidate();
+
+ // 데이터가 새로 바인딩된 후 템플릿 높이를 행들에 강제 적용
+ foreach (DataGridViewRow row in grdDataList.Rows)
+ {
+ row.Height = grdDataList.RowTemplate.Height;
+ }
+
+ int totalPages = (int)Math.Ceiling((double)_dtSourceAll.Rows.Count / PAGE_SIZE);
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 1; i <= totalPages; i++)
+ {
+ if (i == page) sb.Append("● ");
+ else sb.Append("• ");
+ }
+
+ if (lblPageIndicator != null)
+ {
+ lblPageIndicator.Text = sb.ToString().TrimEnd();
+ lblPageIndicator.ForeColor = Color.FromArgb(36, 85, 178);
+ }
+
+ grdDataList.Invalidate();
+
+ //if (page == totalPages)
+ //{
+ // string myMac = GetMacAddress();
+ // string responseMessage = $"{myMac}|display_complete\n";
+ // SendServerMessage(responseMessage);
+ //}
+ }
+
+ private void PageTurnTimer_Tick(object sender, EventArgs e)
+ {
+ if (_dtSourceAll == null || _dtSourceAll.Rows.Count == 0) return;
+
+ int totalPages = (int)Math.Ceiling((double)_dtSourceAll.Rows.Count / PAGE_SIZE);
+
+ // 현재 마지막 페이지를 보여주고 있었다면 메시지 전송
+ if (_currentPage == totalPages)
+ {
+ string myMac = GetMacAddress();
+ string responseMessage = $"{myMac}|display_complete\n";
+
+ // 별도 스레드로 보내 UI 블로킹 방지
+ Task.Run(() => SendServerMessage(responseMessage));
+ }
+
+ _currentPage++;
+ if (_currentPage > totalPages)
+ {
+ _currentPage = 1;
+ }
+
+ DisplayPage(_currentPage);
+ }
+
+ private void lblTitle_Click(object sender, EventArgs e) { }
+ private void lblTitle_Click_1(object sender, EventArgs e) { }
+
+ public void UpdateMonitorData(string xmlData)
+ {
+ if (this.InvokeRequired)
+ {
+ this.Invoke(new Action(() => UpdateMonitorData(xmlData)));
+ return;
+ }
+
+ // 외부 주입 모드일 때는 기존 소켓 통신 로직을 정지시키거나 데이터만 바로 파싱하여 표시
+ _isAgentRunning = false; // 소켓 루프 정지 (이미 열려있는 폼이라면)
+
+ // ParseAndBindServerData는 이미 구현되어 있으므로 그대로 재사용 가능
+ ParseAndBindServerData(xmlData);
+
+ System.Diagnostics.Debug.WriteLine($"[모니터링 갱신] 외부 데이터 바인딩 완료: {xmlData.Length} bytes");
+ }
+
+ [Browsable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public bool DisableSocketCommunication { get; set; } = false;
+
+ private void InitializeCustomButtons()
+ {
+ // 최소화 버튼 설정
+ _btnMinimize = new Button { Text = "—", Size = new Size(40, 40), FlatStyle = FlatStyle.Flat };
+ _btnMinimize.FlatAppearance.BorderSize = 0;
+ _btnMinimize.Click += (s, e) => { this.WindowState = FormWindowState.Minimized; };
+
+ // 닫기 버튼 설정
+ _btnClose = new Button { Text = "✕", Size = new Size(40, 40), FlatStyle = FlatStyle.Flat };
+ _btnClose.FlatAppearance.BorderSize = 0;
+ _btnClose.Click += (s, e) => { this.Close(); };
+
+ // 타이틀 패널(panel1)에 추가
+ panel1.Controls.Add(_btnMinimize);
+ panel1.Controls.Add(_btnClose);
+
+ // 위치 조정 (우측 상단 배치)
+ _btnClose.Location = new Point(panel1.Width - 50, 5);
+ _btnMinimize.Location = new Point(panel1.Width - 95, 5);
+
+ _btnClose.BringToFront();
+ _btnMinimize.BringToFront();
+ }
+ }
+}
\ No newline at end of file
diff --git a/ControlServer/Form4.resx b/ControlServer/AgentMonitorForm.resx
similarity index 96%
rename from ControlServer/Form4.resx
rename to ControlServer/AgentMonitorForm.resx
index 37e0ab2..67f8b33 100644
--- a/ControlServer/Form4.resx
+++ b/ControlServer/AgentMonitorForm.resx
@@ -117,10 +117,13 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
+
True
-
+
+ True
+
+
True
@@ -129,10 +132,10 @@
True
+
+ True
+
True
-
- True
-
\ No newline at end of file
diff --git a/ControlServer/ControlAgent.zip b/ControlServer/ControlAgent.zip
new file mode 100644
index 0000000..ae1ce8d
Binary files /dev/null and b/ControlServer/ControlAgent.zip differ
diff --git a/ControlServer/ControlServer.csproj b/ControlServer/ControlServer.csproj
index 9c0f4d4..3f43e21 100644
--- a/ControlServer/ControlServer.csproj
+++ b/ControlServer/ControlServer.csproj
@@ -11,9 +11,13 @@
+
+
+ Form
+
True
True
diff --git a/ControlServer/DbFileManager.cs b/ControlServer/DbFileManager.cs
new file mode 100644
index 0000000..7f21754
--- /dev/null
+++ b/ControlServer/DbFileManager.cs
@@ -0,0 +1,215 @@
+using System;
+using System.Data;
+using System.Data.OleDb;
+using System.IO;
+using System.Text;
+
+namespace ControlServer
+{
+ public class DbFileManager
+ {
+ // ⚠️ 실제 개발 환경에 맞게 데이터 소스, 아이디, 비밀번호를 변경해 주세요.
+ private readonly string _connectionString = "Provider=Tibero.OLEDB;Data Source=YourDB;User Id=YourID;Password=YourPassword;";
+ private readonly string _fileDirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SendData");
+
+ public DbFileManager()
+ {
+ if (!Directory.Exists(_fileDirPath))
+ {
+ Directory.CreateDirectory(_fileDirPath);
+ }
+ }
+
+ ///
+ /// [Form1 연동 규격] DB에서 에이전트 IP 혹은 단말기명에 맞는 데이터를 가져와
+ /// XML 파일로 저장하고, 가상 모니터 폼 갱신을 위해 DataTable을 반환
+ ///
+ /// 단말기 식별자 (IP 주소 또는 단말기명)
+ /// 가상 모니터링 그리드에 바인딩할 DataTable
+ public DataTable GetTerminalData(string terminalName)
+ {
+ // 1. 에이전트 화면 규격(AuctionTable)과 컬럼 구조 정의
+ DataTable dt = new DataTable("AuctionTable");
+ dt.Columns.Add("aucDate", typeof(string));
+ dt.Columns.Add("itemName", typeof(string));
+ dt.Columns.Add("origin", typeof(string));
+ dt.Columns.Add("totalQty", typeof(int));
+ dt.Columns.Add("fixPriceQty", typeof(int));
+ dt.Columns.Add("totalAmt", typeof(int));
+ dt.Columns.Add("fixPriceAmt", typeof(int));
+
+ // 2. Tibero DB 조회 쿼리
+ string query = @"SELECT AUC_DATE, ITEM_NAME, ORIGIN, TOTAL_QTY, FIX_QTY, TOTAL_AMT, FIX_AMT
+ FROM FIXED_PRICE_TRADE
+ WHERE TERMINAL_NAME = ?";
+
+ using (OleDbConnection conn = new OleDbConnection(_connectionString))
+ {
+ using (OleDbCommand cmd = new OleDbCommand(query, conn))
+ {
+ cmd.Parameters.AddWithValue("@TerminalName", terminalName);
+
+ try
+ {
+ conn.Open();
+ using (OleDbDataReader reader = cmd.ExecuteReader())
+ {
+ while (reader.Read())
+ {
+ dt.Rows.Add(
+ reader["AUC_DATE"]?.ToString() ?? "",
+ reader["ITEM_NAME"]?.ToString() ?? "",
+ reader["ORIGIN"]?.ToString() ?? "",
+ reader["TOTAL_QTY"] != DBNull.Value ? Convert.ToInt32(reader["TOTAL_QTY"]) : 0,
+ reader["FIX_QTY"] != DBNull.Value ? Convert.ToInt32(reader["FIX_QTY"]) : 0,
+ reader["TOTAL_AMT"] != DBNull.Value ? Convert.ToInt32(reader["TOTAL_AMT"]) : 0,
+ reader["FIX_AMT"] != DBNull.Value ? Convert.ToInt32(reader["FIX_AMT"]) : 0
+ );
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[{terminalName}] DB 연동 실패: " + ex.Message);
+ // 에러 발생 시 프로그램이 뻗지 않도록 null 반환 처리
+ return null;
+ }
+ }
+ }
+
+ // 3. 조회 데이터가 전혀 없을 경우 가상 모니터 가독성을 위한 기본 행 처리
+ if (dt.Rows.Count == 0)
+ {
+ string today = DateTime.Now.ToString("MM.dd");
+ dt.Rows.Add(today, "조회데이터 없음", "-", 0, 0, 0, 0);
+ }
+
+ // 4. 에이전트 전송 호환용 내부 로컬 XML 파일 백업 저장 (.xml)
+ string fileName = $"{terminalName}.xml";
+ string fullPath = Path.Combine(_fileDirPath, fileName);
+
+ try
+ {
+ dt.WriteXml(fullPath, XmlWriteMode.WriteSchema);
+ Console.WriteLine($"[XML 로컬 백업 완료] {fullPath}");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[{terminalName}] XML 파일 저장 실패: " + ex.Message);
+ }
+
+ // Form1의 agentCurrentData 변수로 데이터 테이블을 그대로 토스
+ return dt;
+ }
+
+ ///
+ /// 기존 소켓 전송 규격 유지용 메서드 (필요시 사용)
+ ///
+ public string CreateAndSaveTerminalFile(string terminalName)
+ {
+ //DataTable dt = GetTerminalData(terminalName);
+ //if (dt == null) return "DB_ERROR|조회 실패";
+
+ //using (StringWriter sw = new StringWriter())
+ //{
+ // dt.WriteXml(sw, XmlWriteMode.WriteSchema);
+ // return sw.ToString();
+ //}
+
+ // 파일 경로 설정
+ string filePath = GetTerminalFilePath(terminalName);
+
+ // -----------------------------------------------------------------
+ // [임시 테스트용 데이터 하드코딩] DB 연동 전까지 사용할 더미 데이터
+ // -----------------------------------------------------------------
+ string xmlData =
+ "\n" +
+ " \n" +
+ $" {DateTime.Now:MM.dd}\n" +
+ " 테스트품목1\n" +
+ " 테스트산지1\n" +
+ " 1000\n" +
+ " 800\n" +
+ " 5000\n" +
+ " 4000\n" +
+ " \n" +
+ " \n" +
+ $" {DateTime.Now:MM.dd}\n" +
+ " 테스트품목2\n" +
+ " 테스트산지2\n" +
+ " 1030\n" +
+ " 500\n" +
+ " 4400\n" +
+ " 2000\n" +
+ " \n" +
+ " \n" +
+ $" {DateTime.Now:MM.dd}\n" +
+ " 테스트품목3\n" +
+ " 테스트산지3\n" +
+ " 1000\n" +
+ " 800\n" +
+ " 5000\n" +
+ " 4000\n" +
+ " \n" +
+ " \n" +
+ $" {DateTime.Now:MM.dd}\n" +
+ " 테스트품목4\n" +
+ " 테스트산지4\n" +
+ " 1000\n" +
+ " 800\n" +
+ " 5000\n" +
+ " 4000\n" +
+ " \n" +
+ " \n" +
+ $" {DateTime.Now:MM.dd}\n" +
+ " 테스트품목5\n" +
+ " 테스트산지5\n" +
+ " 1000\n" +
+ " 800\n" +
+ " 5000\n" +
+ " 4000\n" +
+ " \n" +
+ " \n" +
+ $" {DateTime.Now:MM.dd}\n" +
+ " 테스트품목6\n" +
+ " 테스트산지6\n" +
+ " 1000\n" +
+ " 800\n" +
+ " 5000\n" +
+ " 4000\n" +
+ " \n" +
+ " \n" +
+ $" {DateTime.Now:MM.dd}\n" +
+ " 테스트품목7\n" +
+ " 테스트산지7\n" +
+ " 1000\n" +
+ " 800\n" +
+ " 5000\n" +
+ " 4000\n" +
+ " \n" +
+ " \n" +
+ $" {DateTime.Now:MM.dd}\n" +
+ " 테스트품목8\n" +
+ " 테스트산지8\n" +
+ " 1000\n" +
+ " 800\n" +
+ " 5000\n" +
+ " 4000\n" +
+ " \n" +
+ "";
+
+ // 2. 파일 저장
+ File.WriteAllText(filePath, xmlData);
+
+ Console.WriteLine($"[DB Manager] XML 파일 생성 완료: {filePath}");
+
+ return xmlData; // 데이터 반환
+ }
+
+ public string GetTerminalFilePath(string terminalName)
+ {
+ // 확장자를 .xml로 통일
+ return Path.Combine(_fileDirPath, $"{terminalName}.xml");
+ }
+ }
+}
\ No newline at end of file
diff --git a/ControlServer/Form1.Designer.cs b/ControlServer/Form1.Designer.cs
index ab6a9c9..a6a46df 100644
--- a/ControlServer/Form1.Designer.cs
+++ b/ControlServer/Form1.Designer.cs
@@ -36,18 +36,17 @@
DataGridViewCellStyle dataGridViewCellStyle5 = new DataGridViewCellStyle();
DataGridViewCellStyle dataGridViewCellStyle6 = new DataGridViewCellStyle();
DataGridViewCellStyle dataGridViewCellStyle7 = new DataGridViewCellStyle();
- pannGray = new Panel();
+ pannGray = new RoundedPanel();
lblTotCnt = new Label();
lblGrayTitle = new Label();
- pannBlue = new Panel();
+ pannBlue = new RoundedPanel();
lblRunningCnt = new Label();
lblBlueTitle = new Label();
grdTotList = new DataGridView();
- btnLog = new Button();
btnRegister = new Button();
btnDataUpdate = new Button();
txtSearch = new TextBox();
- pictureBox1 = new PictureBox();
+ imgSearch = new PictureBox();
Col0 = new DataGridViewTextBoxColumn();
Col1 = new DataGridViewTextBoxColumn();
Col2 = new DataGridViewTextBoxColumn();
@@ -55,10 +54,13 @@
Col4 = new DataGridViewTextBoxColumn();
Col5 = new DataGridViewTextBoxColumn();
Col8 = new DataGridViewTextBoxColumn();
+ Col9 = new DataGridViewTextBoxColumn();
+ Col10 = new DataGridViewTextBoxColumn();
+ Col11 = new DataGridViewTextBoxColumn();
pannGray.SuspendLayout();
pannBlue.SuspendLayout();
((System.ComponentModel.ISupportInitialize)grdTotList).BeginInit();
- ((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)imgSearch).BeginInit();
SuspendLayout();
//
// pannGray
@@ -69,12 +71,11 @@
pannGray.Name = "pannGray";
pannGray.Size = new Size(190, 120);
pannGray.TabIndex = 0;
- pannGray.Paint += pannGray_Paint;
//
// lblTotCnt
//
lblTotCnt.Font = new Font("휴먼둥근헤드라인", 19.8000011F, FontStyle.Regular, GraphicsUnit.Point, 129);
- lblTotCnt.ForeColor = SystemColors.HighlightText;
+ lblTotCnt.ForeColor = SystemColors.GrayText;
lblTotCnt.Location = new Point(0, 56);
lblTotCnt.Name = "lblTotCnt";
lblTotCnt.Size = new Size(190, 48);
@@ -84,10 +85,11 @@
//
// lblGrayTitle
//
- lblGrayTitle.AutoSize = true;
- lblGrayTitle.Location = new Point(33, 20);
+ lblGrayTitle.Font = new Font("맑은 고딕", 9F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblGrayTitle.ForeColor = SystemColors.ControlDarkDark;
+ lblGrayTitle.Location = new Point(0, 20);
lblGrayTitle.Name = "lblGrayTitle";
- lblGrayTitle.Size = new Size(124, 20);
+ lblGrayTitle.Size = new Size(190, 20);
lblGrayTitle.TabIndex = 0;
lblGrayTitle.Text = "현재 단말기 대수";
lblGrayTitle.TextAlign = ContentAlignment.MiddleCenter;
@@ -100,12 +102,11 @@
pannBlue.Name = "pannBlue";
pannBlue.Size = new Size(190, 120);
pannBlue.TabIndex = 2;
- pannBlue.Paint += pannBlue_Paint;
//
// lblRunningCnt
//
lblRunningCnt.Font = new Font("휴먼둥근헤드라인", 19.8000011F, FontStyle.Regular, GraphicsUnit.Point, 129);
- lblRunningCnt.ForeColor = SystemColors.HighlightText;
+ lblRunningCnt.ForeColor = SystemColors.HotTrack;
lblRunningCnt.Location = new Point(0, 56);
lblRunningCnt.Name = "lblRunningCnt";
lblRunningCnt.Size = new Size(190, 48);
@@ -115,6 +116,8 @@
//
// lblBlueTitle
//
+ lblBlueTitle.Font = new Font("맑은 고딕", 9F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblBlueTitle.ForeColor = SystemColors.ControlDarkDark;
lblBlueTitle.Location = new Point(0, 20);
lblBlueTitle.Name = "lblBlueTitle";
lblBlueTitle.Size = new Size(190, 20);
@@ -134,7 +137,7 @@
grdTotList.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
grdTotList.ColumnHeadersHeight = 29;
grdTotList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
- grdTotList.Columns.AddRange(new DataGridViewColumn[] { Col0, Col1, Col2, Col3, Col4, Col5, Col8 });
+ grdTotList.Columns.AddRange(new DataGridViewColumn[] { Col0, Col1, Col2, Col3, Col4, Col5, Col8, Col9, Col10, Col11 });
dataGridViewCellStyle8.Alignment = DataGridViewContentAlignment.MiddleCenter;
dataGridViewCellStyle8.BackColor = SystemColors.Window;
dataGridViewCellStyle8.Font = new Font("맑은 고딕", 9F);
@@ -148,61 +151,61 @@
grdTotList.RowHeadersWidth = 51;
grdTotList.Size = new Size(1000, 417);
grdTotList.TabIndex = 3;
- grdTotList.CellMouseClick += grdTotList_CellMouseClick;
- //
- // btnLog
- //
- btnLog.Location = new Point(1009, 244);
- btnLog.Name = "btnLog";
- btnLog.Size = new Size(150, 44);
- btnLog.TabIndex = 4;
- btnLog.Text = "제어 이력 조회";
- btnLog.UseVisualStyleBackColor = true;
- btnLog.MouseClick += btnLog_MouseClick;
+ grdTotList.CellContentClick += grdTotList_CellContentClick;
//
// btnRegister
//
- btnRegister.Location = new Point(880, 244);
+ btnRegister.BackColor = Color.RoyalBlue;
+ btnRegister.ForeColor = SystemColors.HighlightText;
+ btnRegister.Image = Properties.Resources.imgDataRegister;
+ btnRegister.ImageAlign = ContentAlignment.MiddleLeft;
+ btnRegister.Location = new Point(1029, 242);
btnRegister.Name = "btnRegister";
- btnRegister.Size = new Size(123, 44);
+ btnRegister.Size = new Size(130, 50);
btnRegister.TabIndex = 5;
btnRegister.Text = "단말기 등록";
- btnRegister.UseVisualStyleBackColor = true;
+ btnRegister.TextAlign = ContentAlignment.MiddleRight;
+ btnRegister.UseVisualStyleBackColor = false;
btnRegister.MouseClick += btnRegister_MouseClick;
//
// btnDataUpdate
//
- btnDataUpdate.Location = new Point(750, 244);
+ btnDataUpdate.Image = Properties.Resources.imgDataUpdate;
+ btnDataUpdate.ImageAlign = ContentAlignment.MiddleLeft;
+ btnDataUpdate.Location = new Point(893, 242);
btnDataUpdate.Name = "btnDataUpdate";
- btnDataUpdate.Size = new Size(123, 44);
+ btnDataUpdate.Size = new Size(130, 50);
btnDataUpdate.TabIndex = 6;
btnDataUpdate.Text = "데이터 갱신";
+ btnDataUpdate.TextAlign = ContentAlignment.MiddleRight;
btnDataUpdate.UseVisualStyleBackColor = true;
- btnDataUpdate.MouseClick += btnDataUpdate_MouseClick;
+ btnDataUpdate.Click += btnDataUpdate_Click;
//
// txtSearch
//
+ txtSearch.Font = new Font("맑은 고딕", 12F, FontStyle.Regular, GraphicsUnit.Point, 129);
txtSearch.Location = new Point(159, 244);
txtSearch.Multiline = true;
txtSearch.Name = "txtSearch";
txtSearch.Size = new Size(256, 44);
txtSearch.TabIndex = 7;
txtSearch.TextAlign = HorizontalAlignment.Center;
+ txtSearch.TextChanged += txtSearch_TextChanged;
//
- // pictureBox1
+ // imgSearch
//
- pictureBox1.Image = Properties.Resources.button_search;
- pictureBox1.Location = new Point(430, 244);
- pictureBox1.Name = "pictureBox1";
- pictureBox1.Size = new Size(44, 44);
- pictureBox1.TabIndex = 8;
- pictureBox1.TabStop = false;
+ imgSearch.Image = Properties.Resources.button_search;
+ imgSearch.Location = new Point(430, 244);
+ imgSearch.Name = "imgSearch";
+ imgSearch.Size = new Size(44, 44);
+ imgSearch.TabIndex = 8;
+ imgSearch.TabStop = false;
+ imgSearch.MouseClick += imgSearch_MouseClick;
//
// Col0
//
dataGridViewCellStyle2.Alignment = DataGridViewContentAlignment.MiddleCenter;
Col0.DefaultCellStyle = dataGridViewCellStyle2;
- Col0.Frozen = true;
Col0.HeaderText = "단말기명";
Col0.MinimumWidth = 6;
Col0.Name = "Col0";
@@ -212,7 +215,6 @@
//
dataGridViewCellStyle3.Alignment = DataGridViewContentAlignment.MiddleCenter;
Col1.DefaultCellStyle = dataGridViewCellStyle3;
- Col1.Frozen = true;
Col1.HeaderText = "MAC주소";
Col1.MinimumWidth = 6;
Col1.Name = "Col1";
@@ -222,7 +224,6 @@
//
dataGridViewCellStyle4.Alignment = DataGridViewContentAlignment.MiddleCenter;
Col2.DefaultCellStyle = dataGridViewCellStyle4;
- Col2.Frozen = true;
Col2.HeaderText = "IP";
Col2.MinimumWidth = 6;
Col2.Name = "Col2";
@@ -232,7 +233,6 @@
//
dataGridViewCellStyle5.Alignment = DataGridViewContentAlignment.MiddleCenter;
Col3.DefaultCellStyle = dataGridViewCellStyle5;
- Col3.Frozen = true;
Col3.HeaderText = "통신설정";
Col3.MinimumWidth = 6;
Col3.Name = "Col3";
@@ -242,7 +242,6 @@
//
dataGridViewCellStyle6.Alignment = DataGridViewContentAlignment.MiddleCenter;
Col4.DefaultCellStyle = dataGridViewCellStyle6;
- Col4.Frozen = true;
Col4.HeaderText = "프로그램";
Col4.MinimumWidth = 6;
Col4.Name = "Col4";
@@ -252,7 +251,6 @@
//
dataGridViewCellStyle7.Alignment = DataGridViewContentAlignment.MiddleCenter;
Col5.DefaultCellStyle = dataGridViewCellStyle7;
- Col5.Frozen = true;
Col5.HeaderText = "마지막 통신 시간";
Col5.MinimumWidth = 6;
Col5.Name = "Col5";
@@ -267,44 +265,71 @@
Col8.Visible = false;
Col8.Width = 125;
//
+ // Col9
+ //
+ Col9.HeaderText = "표출시간";
+ Col9.MinimumWidth = 6;
+ Col9.Name = "Col9";
+ Col9.ReadOnly = true;
+ Col9.Visible = false;
+ Col9.Width = 125;
+ //
+ // Col10
+ //
+ Col10.HeaderText = "프로그램ON시간";
+ Col10.MinimumWidth = 6;
+ Col10.Name = "Col10";
+ Col10.ReadOnly = true;
+ Col10.Visible = false;
+ Col10.Width = 125;
+ //
+ // Col11
+ //
+ Col11.HeaderText = "프로그램OFF시간";
+ Col11.MinimumWidth = 6;
+ Col11.Name = "Col11";
+ Col11.ReadOnly = true;
+ Col11.Visible = false;
+ Col11.Width = 125;
+ //
// Form1
//
AutoScaleDimensions = new SizeF(9F, 20F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1357, 737);
- Controls.Add(pictureBox1);
+ Controls.Add(imgSearch);
Controls.Add(txtSearch);
Controls.Add(btnDataUpdate);
Controls.Add(btnRegister);
- Controls.Add(btnLog);
Controls.Add(grdTotList);
Controls.Add(pannBlue);
Controls.Add(pannGray);
Name = "Form1";
Text = "Form1";
+ Load += Form1_Load_1;
pannGray.ResumeLayout(false);
- pannGray.PerformLayout();
pannBlue.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)grdTotList).EndInit();
- ((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit();
+ ((System.ComponentModel.ISupportInitialize)imgSearch).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
- private Panel pannGray;
- private Panel pannBlue;
+ //private Panel pannGray;
+ //private Panel pannBlue;
+ private RoundedPanel pannGray;
+ private RoundedPanel pannBlue;
private Label lblGrayTitle;
private Label lblBlueTitle;
private Label lblTotCnt;
private Label lblRunningCnt;
private DataGridView grdTotList;
- private Button btnLog;
private Button btnRegister;
private Button btnDataUpdate;
private TextBox txtSearch;
- private PictureBox pictureBox1;
+ private PictureBox imgSearch;
private DataGridViewTextBoxColumn Col0;
private DataGridViewTextBoxColumn Col1;
private DataGridViewTextBoxColumn Col2;
@@ -312,5 +337,8 @@
private DataGridViewTextBoxColumn Col4;
private DataGridViewTextBoxColumn Col5;
private DataGridViewTextBoxColumn Col8;
+ private DataGridViewTextBoxColumn Col9;
+ private DataGridViewTextBoxColumn Col10;
+ private DataGridViewTextBoxColumn Col11;
}
}
diff --git a/ControlServer/Form1.cs b/ControlServer/Form1.cs
index 32c144f..2277edc 100644
--- a/ControlServer/Form1.cs
+++ b/ControlServer/Form1.cs
@@ -1,273 +1,668 @@
-using System.Drawing.Drawing2D;
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.IO;
using System.Net;
+using System.Net.NetworkInformation;
using System.Net.Sockets;
+using System.Security.Cryptography;
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+using static System.Windows.Forms.VisualStyles.VisualStyleElement;
namespace ControlServer
{
public partial class Form1 : Form
{
- private List terminalList = new List(); // 리스트 생성
- private Dictionary _connectedAgents = new Dictionary();
+ private List terminalList = new List();
+
+ // 동시성 제어를 위해 해시맵(Dictionary) 접근 시 락(Lock) 객체 분리 또는 보완
+ private readonly Dictionary _connectedAgents = new Dictionary();
+ private readonly Dictionary _openedMonitorForms = new Dictionary();
+ private readonly HashSet _completedAgents = new HashSet();
+
private TcpListener _listener;
private bool _isRunning = false;
- private string _connectedIP = "";
private System.Windows.Forms.Timer _statusTimer;
- private System.Windows.Forms.Timer _dataSyncTimer;
+ private bool _isUpdatePending = false;
+ private System.Windows.Forms.Timer _autoControlTimer; // 에이전트 PC 자동 ON/OFF 시간
+
+ private CancellationTokenSource _updateCts = null; // 60초 타이머 제어를 위한 토큰 소스 및 상태 플래그
+ private bool _isTimerRunning = false;
+
+ private readonly object _syncLock = new object();
+ private readonly object _agentLock = new object();
+ private readonly DbFileManager _dbFileManager = new DbFileManager();
+
+ private Size _originalFormSize; // 폼 로드 시 초기 비율 저장
public Form1()
{
InitializeComponent();
this.Load += Form1_Load;
- pannGray.BackColor = Color.Transparent; // 배경색 초기화
- pannBlue.BackColor = Color.Transparent; // 배경색 초기화
+ this.FormClosing += Form1_FormClosing; // 서버 종료 시 자원 해제
+ this.WindowState = FormWindowState.Maximized; // 폼 최대화 설정
+ this.Resize += new EventHandler(Form1_Resize); // Resize 이벤트 연결
- grdTotList.RowHeadersVisible = false; // 그리드 자동으로 생성되는 첫 번째 열 제거
- grdTotList.AllowUserToAddRows = false; // 새 행 입력용 기본 로우 제거
- grdTotList.EnableHeadersVisualStyles = false; // 헤더 기본 테마 사용 안 함
- grdTotList.ColumnHeadersDefaultCellStyle.BackColor = Color.LightGray; // 헤더 배경색
- grdTotList.ColumnHeadersHeight = 40; // 헤더 높이
- grdTotList.AllowUserToResizeRows = false; // 행 높이 조절 금지
- grdTotList.AllowUserToResizeColumns = false; // 열 너비 조절 금지
- grdTotList.ReadOnly = true; // 읽기 전용
- grdTotList.ClearSelection();
+ InitializeButtonDesign();
+ // 둥근 패널 설정
+ pannGray.BackColor = Color.White;
+ pannBlue.BackColor = Color.White;
+ pannGray.BackgroundImage = null;
+ pannBlue.BackgroundImage = null;
+ pannGray.CornerRadius = 30; // 숫자가 클수록 더 둥글어짐
+ pannBlue.CornerRadius = 30;
- txtSearch.PlaceholderText = "검색(단말기명, IP)";
+ // 텍스트 박스 스타일 및 정렬 설정
+ txtSearch.BorderStyle = BorderStyle.None; // 보더 선 제거
+ txtSearch.BackColor = Color.LightGray; // 배경색 회색 설정 (원하는 밝기로 조절 가능)
+ txtSearch.TextAlign = HorizontalAlignment.Left; // 가로 왼쪽 정렬
+ txtSearch.PlaceholderText = "검색(단말기명, IP)"; // 히든 글씨 설정
+
+ // 자동 제어 타이머 초기화 (1초 주기)
+ _autoControlTimer = new System.Windows.Forms.Timer();
+ _autoControlTimer.Interval = 1000;
+ _autoControlTimer.Tick += AutoControlTimer_Tick;
+ _autoControlTimer.Start();
+
+ InitDataGridView();
+ grdTotList.CellFormatting += grdTotList_CellFormatting;
+ txtSearch.SizeChanged += TextBox_SizeChanged;
+ btnDataUpdate.Click += btnDataUpdate_Click;
+ grdTotList.CellDoubleClick += grdTotList_CellDoubleClick;
+ grdTotList.CellMouseClick += grdTotList_CellMouseClick; // [누락 수정] 이벤트 바인딩 추가
+ }
+
+ private void InitDataGridView()
+ {
+ // 기본 설정
+ grdTotList.RowHeadersVisible = false; // 왼쪽 화살표(행 헤더) 숨김
+ grdTotList.AllowUserToAddRows = false; // 빈 빈칸 추가 방지
+ grdTotList.AllowUserToResizeRows = false; // 행 높이 수동 조절 방지
+ grdTotList.AllowUserToResizeColumns = false; // 열 너비 수동 조절 방지
+ grdTotList.ReadOnly = true; // 읽기 전용
+ grdTotList.SelectionMode = DataGridViewSelectionMode.FullRowSelect; // 행 단위 선택
+ grdTotList.MultiSelect = false; // 다중 선택 방지
+
+ grdTotList.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; // 열 넓이도 그리드 넓이에 맞게 자동으로 늘어나게 설정
+ grdTotList.Columns[3].FillWeight = 60; // 통신설정
+ grdTotList.Columns[4].FillWeight = 60; // 프로그램
+ grdTotList.Columns[5].FillWeight = 140;
+ grdTotList.EnableHeadersVisualStyles = false; // 커스텀 디자인을 적용하기 위해 반드시 false로 설정
+
+ // 전체 배경 및 테두리 설정
+ grdTotList.BackgroundColor = Color.White; // 그리드 바깥쪽 빈 공간 배경색
+ grdTotList.BorderStyle = BorderStyle.None; // 그리드 전체 테두리 제거
+
+ // 셀 테두리 설정 (가로선만 옅은 회색으로)
+ grdTotList.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
+ grdTotList.GridColor = Color.FromArgb(230, 230, 230);
+
+ // 헤더(제목 표시줄) 스타일 (진한 남색 배경, 흰색 글씨)
+ DataGridViewCellStyle headerStyle = new DataGridViewCellStyle();
+ Color headerBgColor = Color.FromArgb(44, 62, 80); // #2C3E50 (진한 남색)
+ Color headerFgColor = Color.White;
+
+ headerStyle.BackColor = headerBgColor;
+ headerStyle.ForeColor = headerFgColor;
+ headerStyle.Font = new Font("맑은 고딕", 10F, FontStyle.Bold);
+ headerStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
+
+ // ★ 행 클릭 시 헤더가 반전되거나 포커스 색상으로 변하는 것을 방지
+ headerStyle.SelectionBackColor = headerBgColor;
+ headerStyle.SelectionForeColor = headerFgColor;
+
+ grdTotList.ColumnHeadersDefaultCellStyle = headerStyle;
+ grdTotList.ColumnHeadersHeight = 40;
+ grdTotList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
+
+ // 행 선택 시 헤더 스타일 상속으로 인한 테두리 및 색상 왜곡 방지
+ grdTotList.EnableHeadersVisualStyles = false;
+
+ // 기본 셀(데이터) 스타일
+ DataGridViewCellStyle cellStyle = new DataGridViewCellStyle();
+ cellStyle.BackColor = Color.White;
+ cellStyle.ForeColor = Color.FromArgb(50, 50, 50); // 완전 검정보다 부드러운 짙은 회색
+ cellStyle.Font = new Font("맑은 고딕", 9.5F, FontStyle.Regular);
+ cellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
+ // 선택했을 때 형광 파란색 대신 옅은 회색으로 표시
+ cellStyle.SelectionBackColor = Color.FromArgb(240, 245, 249);
+ cellStyle.SelectionForeColor = Color.Black;
+ grdTotList.DefaultCellStyle = cellStyle;
+
+ // 행 높이 넉넉하게 설정
+ grdTotList.RowTemplate.Height = 45;
+
+ grdTotList.ClearSelection();
}
- // 가짜 데이터 로드용 함수 (테스트용 예시이며 필요 시 구현체 연결)
private void LoadSavedTerminals()
{
lblTotCnt.Text = grdTotList.RowCount.ToString() + "대";
}
- // 폼이 화면에 나타날 때 서버를 별도의 통로(Task)로 실행
private void Form1_Load(object sender, EventArgs e)
- {
- LoadSavedTerminals(); // 기존에 저장된 단말기 데이터 리스트 로드
- InitStatusTimer(); // 에이전트pc 상태 여부 확인
+ {
+ _originalFormSize = this.Size;
+ LoadSavedTerminals();
+ InitStatusTimer();
+ // 1. 단말기 등록 여부와 상관없이 "서버 포트 오픈"은 무조건 실행
+ System.Diagnostics.Debug.WriteLine("[서버 로드] StartServerAsync 호출 시도");
+
+ Task.Run(async () => await StartServerAsync()).ContinueWith(t =>
+ {
+ if (t.IsFaulted)
+ {
+ string errMsg = t.Exception?.GetBaseException().Message;
+ System.Diagnostics.Debug.WriteLine($"서버 구동 태스크 오류: {errMsg}");
+ }
+ });
+
+ // 2. 단말기 상태 체크 부분
if (grdTotList.Rows.Count > 0)
{
- // 등록된 단말기가 있다면 상태 체크 스레드 및 서버 리스너 시작
- _ = Task.Run(() => StartServer());
- _ = Task.Run(() => CheckAllAlliveStatus()); // 상시 또는 최초 상태 체크
+ Task.Run(() => CheckAllAliveStatus());
}
else
{
- // 등록된 단말기가 하나도 없다면 리스너를 구동하지 않음
- MessageBox.Show("등록된 단말기가 없어 서버를 대기 상태로 유지합니다. 단말기를 등록해주세요.");
+ System.Diagnostics.Debug.WriteLine("등록된 단말기가 없어 서버를 대기 상태로 유지합니다. 단말기를 등록해주세요.");
}
}
+
+ // 폼 크기 변동 시 호출되는 이벤트
+ private void Form1_Resize(object sender, EventArgs e)
+ {
+ if (this.WindowState == FormWindowState.Minimized) return;
+
+ int margin = (int)(this.Width * 0.05);
+ int contentWidth = this.Width - (margin * 2);
+
+ // 1. 그리드뷰 위치 및 크기 조정
+ grdTotList.Width = contentWidth;
+ grdTotList.Left = margin;
+ grdTotList.Height = (int)(this.Height * 0.6);
+
+ pannGray.Width = (int)(grdTotList.Width * 0.2);
+ pannBlue.Width = (int)(grdTotList.Width * 0.2);
+ pannGray.Left = (int)(this.Width / 2 - pannGray.Width - 30);
+ pannBlue.Left = (int)(this.Width / 2 + 30);
+ lblGrayTitle.Width = (int)(grdTotList.Width * 0.2);
+ lblBlueTitle.Width = (int)(grdTotList.Width * 0.2);
+ lblTotCnt.Width = (int)(grdTotList.Width * 0.2);
+ lblRunningCnt.Width = (int)(grdTotList.Width * 0.2);
+
+ // 2. 상단/하단 컨트롤들도 동일하게 좌우 여백 적용
+ txtSearch.Left = margin;
+ txtSearch.Width = (int)(contentWidth * 0.2);
+ imgSearch.Left = (int)(txtSearch.Left + txtSearch.Width + 10);
+ btnRegister.Left = (int)(grdTotList.Width + grdTotList.Left - btnRegister.Width);
+ btnDataUpdate.Left = (int)(btnRegister.Left - btnDataUpdate.Width - 10);
+
+ // 강제 중앙 정렬 함수를 호출
+ CenterControl(grdTotList);
+ }
+
+ // 특정 컨트롤을 폼의 가로 중앙에 배치하는 헬퍼 메서드
+ private void CenterControl(Control ctrl)
+ {
+ ctrl.Left = (this.Width - ctrl.Width) / 2;
+ }
+
+ // 버튼 디자인 초기화
+ private void InitializeButtonDesign()
+ {
+ // 버튼의 기본 테두리 제거를 위해 FlatStyle을 Flat으로 설정
+ btnDataUpdate.FlatStyle = FlatStyle.Flat;
+ btnDataUpdate.FlatAppearance.BorderSize = 0; // 보더 선 제거
+ btnDataUpdate.BackColor = Color.White; // 원하시는 배경색
+
+ btnRegister.FlatStyle = FlatStyle.Flat;
+ btnRegister.FlatAppearance.BorderSize = 0; // 보더 선 제거
+ btnRegister.BackColor = Color.RoyalBlue;
+
+ // 둥글게 그리기 이벤트 연결
+ btnDataUpdate.Paint += Button_Paint;
+ btnRegister.Paint += Button_Paint;
+ }
+
+ // 둥근 모서리 그리기 메서드
+ private void Button_Paint(object sender, PaintEventArgs e)
+ {
+ System.Windows.Forms.Button btn = (System.Windows.Forms.Button)sender;
+ e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
+
+ // 둥근 사각형 그리기
+ int radius = 20; // 둥근 정도
+ System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
+ path.AddArc(0, 0, radius, radius, 180, 90);
+ path.AddArc(btn.Width - radius, 0, radius, radius, 270, 90);
+ path.AddArc(btn.Width - radius, btn.Height - radius, radius, radius, 0, 90);
+ path.AddArc(0, btn.Height - radius, radius, radius, 90, 90);
+ path.CloseFigure();
+
+ btn.Region = new Region(path);
+ }
+
private void InitStatusTimer()
{
_statusTimer = new System.Windows.Forms.Timer();
- _statusTimer.Interval = 5000; // 5초마다 체크
- _statusTimer.Tick += (s, e) => {
- CheckAllAlliveStatus();
+ _statusTimer.Interval = 5000;
+ _statusTimer.Tick += (s, e) =>
+ {
+ // 타이머 틱은 UI 스레드에서 돌므로 비동기로 상태 체크 유도
+ Task.Run(() => CheckAllAliveStatus());
};
_statusTimer.Start();
}
- // 기존 등록된 단말기들의 현재 On/Off 상태를 체크하는 함수
- private void CheckAllAlliveStatus()
+ // [구조 개선] UI 전체를 Invoke로 묶어 돌리던 무거운 방식을 내부 값 갱신 시에만 분리하도록 최적화
+ private void CheckAllAliveStatus()
+ {
+ try
+ {
+ List<(int RowIndex, string Ip, string Mac)> targets = new List<(int, string, string)>();
+
+ this.Invoke(new Action(() =>
+ {
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (row.IsNewRow) continue;
+ string ip = row.Cells[2].Value?.ToString()?.Trim();
+ string mac = row.Cells[1].Value?.ToString()?.Replace("-", "").Replace(":", "").Replace(" ", "").ToUpper().Trim() ?? string.Empty;
+
+ if (!string.IsNullOrEmpty(ip))
+ {
+ targets.Add((row.Index, ip, mac));
+ }
+ }
+ }));
+
+ foreach (var target in targets)
+ {
+ bool isAlive = false;
+ lock (_agentLock)
+ {
+ // 복잡한 Poll 검사를 제거하고, 딕셔너리에 존재하며 Connected 상태인지 확인
+ if (_connectedAgents.TryGetValue(target.Ip, out TcpClient client))
+ {
+ if (client != null && client.Connected)
+ {
+ isAlive = true;
+ }
+ }
+ }
+
+ // UI 업데이트
+ this.BeginInvoke(new Action(() =>
+ {
+ if (target.RowIndex >= grdTotList.Rows.Count) return;
+ var row = grdTotList.Rows[target.RowIndex];
+
+ if (isAlive)
+ {
+ row.Cells[4].Value = "🟢 Start";
+ row.Cells[4].Style.BackColor = Color.Green;
+ }
+ else
+ {
+ row.Cells[4].Value = "🟢 Stop";
+ row.Cells[4].Style.BackColor = Color.Red;
+ }
+ row.Cells[4].Style.ForeColor = Color.Black;
+ row.Cells[4].Style.BackColor = Color.White;
+ row.Cells[4].Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
+ }));
+ }
+
+ this.BeginInvoke(new Action(() => UpdateRunningCount()));
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"상태 체크 도중 예외 발생: {ex.Message}");
+ }
+ }
+
+ private async Task StartServerAsync()
+ {
+ if (_isRunning) return;
+
+ try
+ {
+ _listener = new TcpListener(IPAddress.Any, 9999);
+ _listener.Start();
+ _isRunning = true;
+ System.Diagnostics.Debug.WriteLine("서버 구동 성공 (Port: 9999)... 에이전트 접속을 대기합니다.");
+
+ while (_isRunning)
+ {
+ // Accept 직전 대기 상태 확인
+ System.Diagnostics.Debug.WriteLine("[대기 중] 새로운 에이전트의 접속 소켓 신호를 기다리는 중...");
+
+ // 클라이언트 접속 대기 (비동기)
+ TcpClient client = await _listener.AcceptTcpClientAsync();
+
+ // 소켓 연결 자체가 성공했을 때
+ System.Diagnostics.Debug.WriteLine("[소켓 연결 성공] 누군가 9999 포트로 연결했습니다! 핸들러로 넘깁니다.");
+
+ _ = Task.Run(() => HandleAgentAsync(client));
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"서버 루프 치명적 오류: {ex.Message}");
+ this.BeginInvoke(new Action(() => MessageBox.Show("서버 구동 오류: " + ex.Message)));
+ }
+ finally
+ {
+ _isRunning = false;
+ try { _listener?.Stop(); } catch { }
+ System.Diagnostics.Debug.WriteLine("서버 구동이 완전히 중지되었습니다. (_isRunning = false)");
+ }
+ }
+
+ // 동기 IO 함수들(StreamReader.ReadLine)을 비동기(ReadLineAsync)로 변경하여 리소스 낭비 방지
+ private async Task HandleAgentAsync(TcpClient client)
+ {
+ string clientIP = string.Empty;
+ try
+ {
+ clientIP = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
+ System.Diagnostics.Debug.WriteLine($"[핸들러 진입] IP: {clientIP} 스트림 분석 시작");
+
+ using (NetworkStream stream = client.GetStream())
+ using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
+ {
+ string receivedMac = await reader.ReadLineAsync();
+ System.Diagnostics.Debug.WriteLine($"[{clientIP}] 수신된 MAC 주소: '{receivedMac}'");
+
+ if (!string.IsNullOrEmpty(receivedMac))
+ {
+ // 등록된 단말기인지 UI 스레드에서 먼저 판별 및 확인
+ bool isRegistered = false;
+ this.Invoke(new Action(() =>
+ {
+ isRegistered = UpdateAgentStatusByMac(receivedMac, clientIP);
+ }));
+
+ // 등록되지 않은 단말기라면 소켓 끊고 핸들러 종료!
+ if (!isRegistered)
+ {
+ System.Diagnostics.Debug.WriteLine($"[{clientIP}] 미등록 단말기이므로 소켓을 강제 종료합니다.");
+ try { client.Close(); } catch { }
+ return;
+ }
+
+ // ---------------- 등록된 단말기만 아래 로직을 수행 ----------------
+ lock (_agentLock)
+ {
+ if (_connectedAgents.ContainsKey(clientIP))
+ {
+ try { _connectedAgents[clientIP].Close(); } catch { }
+ _connectedAgents.Remove(clientIP);
+ }
+ _connectedAgents[clientIP] = client;
+ }
+
+ System.Diagnostics.Debug.WriteLine($"[{clientIP}] 정상 등록 단말기 딕셔너리 추가 완료.");
+
+ string terminalName = await Task.Run(() => GetTerminalNameByIp(clientIP));
+ if (string.IsNullOrEmpty(terminalName)) terminalName = $"Terminal_{clientIP.Replace(".", "_")}";
+
+ string initialDbData = _dbFileManager.CreateAndSaveTerminalFile(terminalName);
+ await SendDataToSingleAgentAsync(client, initialDbData); // 에이전트로 데이터 전송
+
+ while (client.Connected)
+ {
+ System.Diagnostics.Debug.WriteLine($"[{clientIP}] 수신된 메시지 받기 전");
+ string agentMsg = await reader.ReadLineAsync();
+ System.Diagnostics.Debug.WriteLine($"[{clientIP}] 수신된 메시지: {(agentMsg ?? "NULL/연결종료")}");
+ if (agentMsg == null)
+ {
+ System.Diagnostics.Debug.WriteLine($"[{clientIP}] 읽기 종료(null). 루프 탈출.");
+ break;
+ }
+
+ // (예: MAC|메시지)
+ if (agentMsg.Contains("display_complete"))
+ {
+ // 기다리지 않고, 즉시 해당 에이전트에게 데이터 재전송
+ System.Diagnostics.Debug.WriteLine($"[{clientIP}] 완료 신호 수신. 즉시 데이터 갱신을 진행합니다.");
+ string newData = _dbFileManager.CreateAndSaveTerminalFile(terminalName);
+ await SendDataToSingleAgentAsync(client, newData); // 현재 접속된 소켓(client)을 통해 즉시 데이터 전송
+
+ // 모니터링 폼이 열려있다면 즉시 데이터 전달
+ lock (_syncLock)
+ {
+ if (_openedMonitorForms.TryGetValue(clientIP, out var monitorForm) && !monitorForm.IsDisposed)
+ {
+ monitorForm.BeginInvoke(new Action(() => monitorForm.UpdateMonitorData(newData)));
+ }
+ }
+ }
+ // 에이전트가 보내는 프로토콜 정의
+ else if (agentMsg.Contains("data_update"))
+ {
+ // 그리드의 마지막 통신 시간을 현재 시간으로 업데이트
+ this.BeginInvoke(new Action(() =>
+ {
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (row.IsNewRow) continue;
+
+ string gridIp = row.Cells[2].Value?.ToString()?.Trim();
+
+ // IP로 매칭
+ bool isMatch = (!string.IsNullOrEmpty(clientIP) && gridIp == clientIP);
+
+ if (isMatch)
+ {
+ row.Cells[5].Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+ break;
+ }
+ }
+ }));
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[{clientIP}] 핸들러 내부 루프 예외: " + ex.Message);
+ }
+ finally
+ {
+ lock (_agentLock)
+ {
+ if (!string.IsNullOrEmpty(clientIP) && _connectedAgents.ContainsKey(clientIP))
+ {
+ _connectedAgents.Remove(clientIP);
+ }
+ }
+ try { client.Close(); } catch { }
+ Task.Run(() => CheckAllAliveStatus());
+ }
+ }
+
+ private void ProcessAgentCompletion(string agentIp)
+ {
+ lock (_syncLock)
+ {
+ // 1. 완료 목록에 추가
+ if (!_completedAgents.Contains(agentIp))
+ {
+ _completedAgents.Add(agentIp);
+ Console.WriteLine($"[완료 신호 수신] 단말기 IP: {agentIp} ({_completedAgents.Count}대 완료)");
+ }
+
+ // 현재 연결 상태인 에이전트들의 총 개수 파악
+ int totalExpected = 0;
+ lock (_agentLock) { totalExpected = _connectedAgents.Count; }
+
+ if (totalExpected == 0) return;
+
+ // 2. 첫 번째 완료 신호가 들어왔을 때 60초 타이머 기동
+ if (!_isTimerRunning)
+ {
+ _isTimerRunning = true;
+ _updateCts = new CancellationTokenSource();
+
+ var token = _updateCts.Token;
+ Task.Run(async () =>
+ {
+ Console.WriteLine("첫 번째 완료 신호 감지. 60초 대기 타이머를 시작합니다...");
+ try
+ {
+ // 60초 동안 대기 (모든 단말 완료 시 외부에서 Cancel() 요청하여 조기 종료됨)
+ await Task.Delay(60000, token);
+
+ // 타임아웃 발생: 60초 내에 다 못 받은 상황
+ Console.WriteLine("[타임아웃] 60초 제한 시간이 지났습니다. 낙오 단말기 처리를 시작합니다.");
+
+ List stragglers = new List();
+ lock (_syncLock)
+ {
+ lock (_agentLock)
+ {
+ foreach (var ip in _connectedAgents.Keys)
+ {
+ if (!_completedAgents.Contains(ip))
+ {
+ stragglers.Add(ip);
+ }
+ }
+ }
+ }
+
+ // 낙오 단말기 연결 끊기 및 상태 변경
+ foreach (var ip in stragglers)
+ {
+ Console.WriteLine($"[낙오 단말 끊기] 60초 내 미완료 단말 제거: {ip}");
+ lock (_agentLock)
+ {
+ if (_connectedAgents.TryGetValue(ip, out TcpClient client))
+ {
+ try { client.Close(); } catch { }
+ _connectedAgents.Remove(ip);
+ }
+ }
+
+ // UI 스레드에서 해당 IP의 그리드 상태를 Stop(Red)으로 업데이트
+ this.BeginInvoke(new Action(() =>
+ {
+ UpdateTerminalGridStatusToStop(ip);
+ }));
+ }
+ }
+ catch (TaskCanceledException)
+ {
+ // 60초 이전에 모든 단말기가 정상 완료되어 취소된 경우
+ Console.WriteLine("모든 단말기가 60초 내에 연출을 완료하여 대기를 조기 종료합니다.");
+ }
+ finally
+ {
+ // 3. 타이머가 끝났거나 조기 종료되었으므로 공통 다음 파일 전송 및 초기화
+ Console.WriteLine("다음 데이터 파일 갱신 처리를 시작합니다.");
+ SendDataToAllAgents();
+
+ lock (_syncLock)
+ {
+ _completedAgents.Clear();
+ _isTimerRunning = false;
+ _updateCts?.Dispose();
+ _updateCts = null;
+ }
+
+ // 상태 갱신
+ Task.Run(() => CheckAllAliveStatus());
+ }
+ });
+ }
+ }
+ }
+
+ // 낙오된 단말기를 UI 상에서 강제로 Stop 처리하기 위한 헬퍼 메서드
+ private void UpdateTerminalGridStatusToStop(string ip)
{
foreach (DataGridViewRow row in grdTotList.Rows)
{
if (row.IsNewRow) continue;
- string ip = row.Cells[2].Value?.ToString();
- if (string.IsNullOrEmpty(ip)) continue;
-
- // 현재 서버에 소켓이 활성화되어 연결되어 있는지 확인
- bool isAlive = false;
- lock (_connectedAgents)
+ if (row.Cells[2].Value?.ToString()?.Trim() == ip)
{
- if (_connectedAgents.ContainsKey(ip))
- {
- TcpClient client = _connectedAgents[ip];
- // 소켓이 연결되어 있고, 실제 연결 상태가 양호한지 체크
- if (client != null && client.Connected)
- {
- isAlive = true;
- }
- }
+ row.Cells[4].Value = "🔴 Stop";
+ row.Cells[4].Style.BackColor = Color.White;
+ row.Cells[4].Style.ForeColor = Color.Black;
+ break;
}
+ }
+ UpdateRunningCount();
+ }
- // 만약 소켓이 연결 안 되어 있다면 보조 수단으로 원래의 PingTest를 수행해볼 수 있음
- if (!isAlive)
+ private bool UpdateAgentStatusByMac(string clientMac, string clientIP)
+ {
+ // 에이전트가 보내온 MAC 주소 정제
+ string cleanClientMac = string.IsNullOrEmpty(clientMac) ?
+ string.Empty : clientMac.Replace("-", "").Replace(":", "").Replace(" ", "").ToUpper().Trim();
+
+ bool isMapped = false;
+
+ // grdTotList 순회하며 등록된 단말기인지 확인
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (row.IsNewRow) continue;
+
+ string gridIp = row.Cells[2].Value?.ToString()?.Trim() ?? string.Empty;
+ string gridMac = row.Cells[1].Value?.ToString()?.Replace("-", "").Replace(":", "").Replace(" ", "").ToUpper().Trim() ?? string.Empty;
+
+ // 1순위: IP 주소 매칭 또는 2순위: MAC 주소 일치
+ if (gridIp == clientIP || (!string.IsNullOrEmpty(gridMac) && gridMac == cleanClientMac))
{
- isAlive = PingTest(ip);
- }
+ if (gridIp != clientIP)
+ {
+ row.Cells[2].Value = clientIP; // 최신 IP로 갱신
+ }
- this.Invoke(new Action(() => {
- if (isAlive)
+ if (gridMac != cleanClientMac && !string.IsNullOrEmpty(cleanClientMac))
{
- row.Cells[4].Value = "Start";
- row.Cells[4].Style.BackColor = Color.Green;
- row.Cells[4].Style.ForeColor = Color.White;
- }
- else
- {
- row.Cells[4].Value = "Stop";
- row.Cells[4].Style.BackColor = Color.Red;
- row.Cells[4].Style.ForeColor = Color.White;
+ row.Cells[1].Value = cleanClientMac; // MAC 주소 보정
+ Console.WriteLine($"[그리드 보정] IP: {clientIP} 단말기의 MAC 주소를 {cleanClientMac}으로 갱신했습니다.");
}
+
+ // 상태를 Start로 변경
+ row.Cells[4].Value = "🟢 Start";
+ row.Cells[4].Style.BackColor = Color.White;
+ row.Cells[4].Style.ForeColor = Color.Black;
row.Cells[4].Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
- }));
+
+ row.Cells[5].Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+
+ isMapped = true;
+ break;
+ }
}
- this.Invoke(new Action(() => UpdateRunningCount()));
+
+ // ★ 미등록 에이전트 자동 추가 로직 제거 및 로그 출력
+ if (!isMapped)
+ {
+ System.Diagnostics.Debug.WriteLine($"[연결 거부] 등록되지 않은 단말기 접속 시도 - IP: {clientIP}, MAC: {cleanClientMac}");
+ }
+
+ // 켜져 있는 대수 카운트 갱신
+ UpdateRunningCount();
+
+ return isMapped; // 매핑 성공 여부 반환
}
- private bool PingTest(string ip)
- {
- if (string.IsNullOrEmpty(ip)) return false;
- try
- {
- using (System.Net.NetworkInformation.Ping ping = new System.Net.NetworkInformation.Ping())
- {
- var reply = ping.Send(ip, 1000); // 1초 타임아웃
- return reply.Status == System.Net.NetworkInformation.IPStatus.Success;
- }
- }
- catch { return false; }
- }
-
- // 서버 시작 함수
- private async Task StartServer()
- {
- try
- {
- if (_isRunning) return; // 이미 실행 중이면 중복 방지
-
- _listener = new TcpListener(IPAddress.Any, 9999);
- _listener.Start();
- MessageBox.Show("서버 연결 중...");
- _isRunning = true;
-
- while (_isRunning)
- {
- // 에이전트의 접속을 비동기로 대기
- TcpClient client = await _listener.AcceptTcpClientAsync();
-
- // 에이전트 접속 처리 핸들러 실행
- _ = Task.Run(() => HandleAgent(client));
- }
- }
- catch (Exception ex)
- {
- // 리스너가 중단되었을 때의 예외 처리 제외 처리
- if (_isRunning)
- {
- MessageBox.Show("서버 구동 오류: " + ex.Message);
- }
- }
- }
-
- // 에이전트에서 데이터 받기
- private void HandleAgent(TcpClient client)
- {
- try
- {
- string clientIP = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
-
- // using을 쓰지 않고 직접 스트림과 리더를 생성(함수가 끝나도 닫히지 않게)
- NetworkStream stream = client.GetStream();
- System.IO.StreamReader reader = new System.IO.StreamReader(stream, Encoding.UTF8);
-
- // 에이전트가 보낸 첫 줄(MAC 주소)
- string receivedMac = reader.ReadLine();
-
- if (!string.IsNullOrEmpty(receivedMac))
- {
- this.Invoke(new Action(() => {
- // 기존 연결이 있다면 안전하게 닫고 교체
- if (_connectedAgents.ContainsKey(clientIP))
- {
- try { _connectedAgents[clientIP].Close(); } catch { }
- }
-
- _connectedAgents[clientIP] = client; // 소켓 유지
- UpdateAgentStatusByMac(receivedMac, clientIP);
- }));
-
- // 서버 소켓이 닫히거나 에이전트가 끊어질 때까지
- // 이 스레드가 종료되지 않고 백그라운드에서 계속 대기하도록 루프를 돌려줌
- // (에이전트로부터 추가적인 메시지를 받을 때도 여기서 처리 가능)
- while (client.Connected)
- {
- // 에이전트가 보낸 데이터가 없다면 여기서 대기
- // 에이전트가 연결을 끊으면 null이 들어와 루프를 빠져나감
- string agentMsg = reader.ReadLine();
- if (agentMsg == null) break;
- }
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine("서버 핸들러 오류: " + ex.Message);
- }
- finally
- {
- // 연결이 최종적으로 끊기면 딕셔너리에서 제거 및 리소스 정리
- try
- {
- string clientIP = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString();
- lock (_connectedAgents)
- {
- if (_connectedAgents.ContainsKey(clientIP))
- {
- _connectedAgents.Remove(clientIP);
- }
- }
- }
- catch { }
- client.Close();
- }
- }
-
- // MAC 주소를 기반으로 에이전트 매칭 및 상태 업데이트
- private void UpdateAgentStatusByMac(string clientMac, string clientIP)
- {
- // 수신된 MAC에서 불필요한 문자 제거
- string cleanClientMac = clientMac.Replace("-", "").Replace(":", "").Replace(" ", "").ToUpper().Trim();
-
- bool isMatched = false;
-
- this.Invoke(new Action(() => {
- foreach (DataGridViewRow row in grdTotList.Rows)
- {
- string gridIp = row.Cells[2].Value.ToString();
-
- if (gridIp == clientIP)
- {
- row.Cells[1].Value = cleanClientMac;
- row.Cells[4].Value = "Start";
- row.Cells[4].Style.BackColor = Color.Green;
- row.Cells[4].Style.ForeColor = Color.White;
- row.Cells[5].Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
-
- isMatched = true;
- break;
- }
- }
-
- if (!isMatched)
- {
- Console.WriteLine($"매칭 실패! 수신된 MAC: {cleanClientMac}");
- }
-
- UpdateRunningCount();
- }));
- }
-
- // 상단의 프로그램 실행 대수 갱신용 공통 메서드
private void UpdateRunningCount()
{
int startCount = 0;
foreach (DataGridViewRow row in grdTotList.Rows)
{
if (row.IsNewRow) continue;
- if (row.Cells[4].Value?.ToString() == "Start")
+ if (row.Cells[4].Value?.ToString().Contains("Start") == true)
{
startCount++;
}
@@ -277,175 +672,263 @@ namespace ControlServer
private void btnRegister_MouseClick(object sender, MouseEventArgs e)
{
- // 단말기 등록 팝업 호출
- Form2 form = new Form2();
-
- if (form.ShowDialog() == DialogResult.OK)
+ using (Form2 form = new Form2())
{
- TerminalInfo data = form.TerminalData;
-
- grdTotList.Rows.Add(
- data.TerminalName, // 단말기명
- data.Mac, // MAC 주소
- data.IP, // IP
- "설정", // 통신설정 버튼
- "Stop", // 기본 상태
- DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
- data.Port // 포트번호
- );
-
- int rowIndex = grdTotList.Rows.Count - 1;
-
- // 프로그램 열 스타일 지정
- DataGridViewCell cell4 = grdTotList.Rows[rowIndex].Cells[4];
- cell4.Style.BackColor = Color.Red;
- cell4.Style.ForeColor = Color.White;
- cell4.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
-
- // 통신설정 열 스타일 지정
- DataGridViewCell cell3 = grdTotList.Rows[rowIndex].Cells[3];
- cell3.Style.BackColor = Color.LightGray;
- cell3.Style.ForeColor = Color.Black;
- cell3.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
-
- // 현재 단말기 개수 업데이트
- lblTotCnt.Text = grdTotList.RowCount.ToString() + "대";
-
- // 단말 등록 완료 후 리스너가 구동 중이 아니었다면 리스너 최초 구동 개시
- if (!_isRunning)
+ if (form.ShowDialog() == DialogResult.OK)
{
- _ = Task.Run(() => StartServer());
- }
+ TerminalInfo data = form.TerminalData;
- // 기접속된 IP가 있는 장치라면 바로 연동 매칭
- if (!string.IsNullOrEmpty(_connectedIP) && _connectedIP == data.IP)
- {
- UpdateAgentStatusByMac(data.Mac, data.IP);
+ _dbFileManager.CreateAndSaveTerminalFile(data.TerminalName); // 등록 시점에 해당 단말기 이름으로 파일 생성
+
+ grdTotList.Rows.Add(
+ data.TerminalName,
+ data.Mac,
+ data.IP,
+ "설정",
+ "Stop",
+ DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ data.Port,
+ "5", // 에이전트 화면 페이지 표출 시간 디폴트값
+ "", // ON 시간 (초기값 없음)
+ "" // OFF 시간 (초기값 없음)
+ );
+
+ int rowIndex = grdTotList.Rows.Count - 1;
+
+ //grdTotList.Rows[rowIndex].Cells[4].Style.BackColor = Color.Red;
+ //grdTotList.Rows[rowIndex].Cells[4].Style.ForeColor = Color.White;
+ //grdTotList.Rows[rowIndex].Cells[4].Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
+
+ //grdTotList.Rows[rowIndex].Cells[3].Style.BackColor = Color.LightGray;
+ //grdTotList.Rows[rowIndex].Cells[3].Style.ForeColor = Color.Black;
+ //grdTotList.Rows[rowIndex].Cells[3].Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
+
+ lblTotCnt.Text = grdTotList.RowCount.ToString() + "대";
+
+ if (!_isRunning)
+ {
+ _ = Task.Run(() => StartServerAsync());
+ }
+
+ Task.Run(() => CheckAllAliveStatus());
}
}
}
- private void btnDataUpdate_MouseClick(object sender, MouseEventArgs e)
+ private void btnDataUpdate_Click(object sender, EventArgs e)
{
- // 1. 메시지 전송
- SendDataToAllAgents("데이터 전송 테스트(수동)");
+ // 데이터 갱신 작업 실행
+ Task.Run(() =>
+ {
+ SendDataToAllAgents();
- // 2. 기존 로직
- Popup popup = new Popup("전체 단말기에 데이터 전송을 시도했습니다.");
- popup.ShowDialog();
- CheckAllAlliveStatus();
+ this.BeginInvoke(new Action(() =>
+ {
+ CheckAllAliveStatus();
+ }));
+ });
}
- private void btnLog_MouseClick(object sender, MouseEventArgs e)
+ private async void grdTotList_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
- Form4 form = new Form4();
- form.Show();
+ if (e.ColumnIndex < 3)
+ {
+ if (e.RowIndex < 0) return;
+
+ string terminalName = grdTotList.Rows[e.RowIndex].Cells[0].Value?.ToString();
+ string targetIP = grdTotList.Rows[e.RowIndex].Cells[2].Value?.ToString();
+
+ // 실제 에이전트 연결 상태 확인
+ bool isConnected = false;
+ lock (_agentLock)
+ {
+ if (!string.IsNullOrEmpty(targetIP) && _connectedAgents.TryGetValue(targetIP, out TcpClient client))
+ {
+ isConnected = (client != null && client.Connected);
+ }
+ }
+
+ if (!isConnected)
+ {
+ MessageBox.Show("현재 에이전트와 연결되어 있지 않습니다.\n연결 후 다시 시도해 주세요.", "연결 확인", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return; // 연결되지 않았으면 여기서 중단
+ }
+
+ // 1. 이미 열려있으면 업데이트만 수행
+ if (_openedMonitorForms.TryGetValue(targetIP, out AgentMonitorForm existingForm) && !existingForm.IsDisposed)
+ {
+ string updatedXml = await Task.Run(() => _dbFileManager.CreateAndSaveTerminalFile(terminalName));
+ existingForm.UpdateMonitorData(updatedXml);
+ existingForm.Activate();
+ return;
+ }
+
+ // 2. 폼 생성
+ AgentMonitorForm monitorForm = new AgentMonitorForm();
+ monitorForm.Text = $"[가상 모니터링] {terminalName} ({targetIP})";
+
+ // 폼이 로드될 때 소켓 통신을 하지 않도록 설정하는 플래그를 추가하는 것이 좋습니다.
+ // monitorForm.DisableSocketCommunication = true;
+
+ monitorForm.Show();
+
+ // 3. 데이터 로드 및 적용
+ string currentXml = await Task.Run(() => _dbFileManager.CreateAndSaveTerminalFile(terminalName));
+ monitorForm.UpdateMonitorData(currentXml);
+
+ lock (_syncLock) { _openedMonitorForms[targetIP] = monitorForm; }
+ }
+ else { }
+
}
private void grdTotList_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
grdTotList.ClearSelection();
-
- // 헤더 클릭 방지
if (e.RowIndex < 0) return;
- // 통신설정 버튼 열 클릭 시
- if (e.ColumnIndex == 3)
- {
- var row = grdTotList.Rows[e.RowIndex];
- string TerminalName = row.Cells[0].Value?.ToString() ?? "";
- string TerminalIp = row.Cells[2].Value?.ToString() ?? "";
+ var row = grdTotList.Rows[e.RowIndex];
+ string terminalName = row.Cells[0].Value?.ToString() ?? "";
+ string terminalIp = row.Cells[2].Value?.ToString() ?? "";
- Form3 form = new Form3(TerminalName, TerminalIp);
- form.ShowDialog();
+ if (e.ColumnIndex == 3) // 설정 버튼 클릭
+ {
+ string filePath = _dbFileManager.GetTerminalFilePath(terminalName); //파일 확보
+
+ using (Form3 form = new Form3(this, terminalName, terminalIp, filePath))
+ {
+ form.ShowDialog();
+ }
}
- // 프로그램 작동 제어 제어 열 클릭 시
- else if (e.ColumnIndex == 4)
+ else if (e.ColumnIndex == 4) // Start/Stop 상태 제어 버튼 클릭
{
- DataGridViewRow row = grdTotList.Rows[e.RowIndex];
- DataGridViewCell cell4 = row.Cells[4];
+ var grdRow = grdTotList.Rows[e.RowIndex];
+ string ip = grdRow.Cells[2].Value?.ToString();
+ string mac = grdRow.Cells[1].Value?.ToString();
+ string status = grdRow.Cells[4].Value?.ToString() ?? "Stop";
- string status = cell4.Value?.ToString() ?? "";
- string targetIP = row.Cells[2].Value?.ToString() ?? "";
- string targetMac = row.Cells[1].Value?.ToString() ?? "";
-
- if (status == "Start")
- {
- // 에이전트 PC로 원격 강제 종료 패킷 전달
- SendStopCommandToAgent(targetIP);
-
- cell4.Value = "Stop";
- cell4.Style.BackColor = Color.Red;
- cell4.Style.ForeColor = Color.White;
- }
- else
- {
- // WOL 매직 패킷 송신
- if (!string.IsNullOrEmpty(targetMac))
- {
- SendWakeOnLan(targetMac);
-
- cell4.Value = "Start";
- cell4.Style.BackColor = Color.Green;
- cell4.Style.ForeColor = Color.White;
- }
- else
- {
- MessageBox.Show("등록된 MAC 주소가 없습니다.");
- }
- }
-
- cell4.Style.Alignment = DataGridViewContentAlignment.MiddleCenter;
- UpdateRunningCount();
+ ExecuteAgentControl(ip, mac, status);
+ }
+ }
+
+ // 그리드 셀 클릭 로직을 공통 함수로 분리
+ private void ExecuteAgentControl(string ip, string mac, string currentStatus)
+ {
+ // 현재 상태에 따라 적절한 동작 수행
+ if (currentStatus.Contains("Start"))
+ {
+ // 끄기 (Stop)
+ SendStopCommandToAgent(ip);
+ UpdateGridStatus(ip, "Stop", Color.Red);
+ }
+ else
+ {
+ // 켜기 (Start)
+ if (!string.IsNullOrEmpty(mac))
+ {
+ SendWakeOnLan(mac);
+ UpdateGridStatus(ip, "Start", Color.Green);
+ }
+ }
+ UpdateRunningCount();
+ }
+
+ // 그리드 상태 업데이트 헬퍼
+ private void UpdateGridStatus(string ip, string status, Color color)
+ {
+ this.Invoke(new Action(() =>
+ {
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (row.Cells[2].Value?.ToString() == ip)
+ {
+ row.Cells[4].Value = status;
+ row.Cells[4].Style.ForeColor = Color.Black;
+ row.Cells[4].Style.BackColor = Color.White;
+ row.Cells[4].Style.ForeColor = color;
+
+ if (status == "Start")
+ {
+ row.Cells[4].Value = "🟢 Start"; // 특수기호 사용
+ }
+ else
+ {
+ row.Cells[4].Value = "🔴 Stop";
+ }
+ break;
+ }
+ }
+ }));
+ }
+
+ // 프로글매 자동 on/off
+ private void AutoControlTimer_Tick(object sender, EventArgs e)
+ {
+ // 현재 시간(초단위까지)
+ DateTime now = DateTime.Now;
+ string currentTimeStr = now.ToString("HH:mm:ss");
+
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (row.IsNewRow) continue;
+
+ string ip = row.Cells[2].Value?.ToString();
+ string mac = row.Cells[1].Value?.ToString();
+ string onTimeStr = row.Cells[8].Value?.ToString(); // 예: "09:00:00"
+ string offTimeStr = row.Cells[9].Value?.ToString(); // 예: "18:00:00"
+ string currentStatus = row.Cells[4].Value?.ToString();
+
+ if (string.IsNullOrEmpty(ip)) continue;
+
+ if (currentTimeStr == onTimeStr && currentStatus.Contains("Stop"))
+ {
+ // 켜기 명령 실행
+ SendWakeOnLan(mac);
+ UpdateGridStatus(ip, "Start", Color.Green);
+ }
+ else if (currentTimeStr == offTimeStr && currentStatus.Contains("Start"))
+ {
+ // 끄기 명령 실행
+ SendStopCommandToAgent(ip);
+ UpdateGridStatus(ip, "Stop", Color.Red);
+ }
}
}
- // [원격 끄기 구현] 소켓 스트림 연동을 위한 에이전트 대상 종료 전송 함수
private void SendStopCommandToAgent(string ip)
{
try
{
- if (_connectedAgents.ContainsKey(ip))
+ lock (_agentLock)
{
- TcpClient client = _connectedAgents[ip];
- if (client.Connected)
+ if (_connectedAgents.TryGetValue(ip, out TcpClient client) && client.Connected)
{
- // 중요: 뒤에 "\n"을 추가하여 에이전트의 ReadLine()이 끝을 인식하게 합니다.
byte[] msg = Encoding.UTF8.GetBytes("STOP\n");
client.GetStream().Write(msg, 0, msg.Length);
client.GetStream().Flush();
+ System.Diagnostics.Debug.WriteLine($"[서버] {ip}에게 STOP 명령 전송 완료!");
+ }
+ else
+ {
+ MessageBox.Show("해당 에이전트는 현재 서버에 소켓으로 연결된 상태가 아닙니다.");
}
- }
- else
- {
- MessageBox.Show("에이전트가 서버에 연결되어 있지 않습니다.");
}
}
catch (Exception ex)
{
- MessageBox.Show("종료 명령 전송 실패: " + ex.Message);
+ Console.WriteLine("종료 명령 전송 실패: " + ex.Message);
}
}
- // WOL 매직 패킷 전송 함수
private void SendWakeOnLan(string macAddress)
{
try
{
string cleanMac = macAddress.Replace("-", "").Replace(":", "").Replace(" ", "").Trim();
-
- if (cleanMac.Length != 12)
- {
- MessageBox.Show("올바른 MAC 주소 형식이 아닙니다.");
- return;
- }
+ if (cleanMac.Length != 12) return;
byte[] bytes = new byte[102];
-
- for (int i = 0; i < 6; i++)
- {
- bytes[i] = 0xFF;
- }
+ for (int i = 0; i < 6; i++) bytes[i] = 0xFF;
byte[] macBytes = new byte[6];
for (int i = 0; i < 6; i++)
@@ -463,41 +946,343 @@ namespace ControlServer
client.Connect(IPAddress.Parse("255.255.255.255"), 9);
client.Send(bytes, bytes.Length);
}
+ Console.WriteLine($"[WOL 전송 완료] MAC: {cleanMac}");
}
catch (Exception ex)
{
- MessageBox.Show("WOL 전송 실패: " + ex.Message);
+ Console.WriteLine("WOL 패킷 송신 예외 오류: " + ex.Message);
}
}
- private void SendDataToAllAgents(string message)
+ private void SendDataToAllAgents()
{
- // 메시지 끝에 \n을 붙여서 에이전트가 ReadLine()으로 잘 읽게함
- byte[] data = Encoding.UTF8.GetBytes(message + "\n");
-
- lock (_connectedAgents) // 여러 스레드에서 접근할 수 있으므로 lock 사용
+ lock (_agentLock)
{
foreach (var kvp in _connectedAgents)
{
- try
+ string agentIP = kvp.Key;
+ TcpClient client = kvp.Value;
+
+ if (client != null && client.Connected)
{
- TcpClient client = kvp.Value;
- if (client != null && client.Connected)
+ try
{
- NetworkStream stream = client.GetStream();
- stream.Write(data, 0, data.Length);
- stream.Flush();
+ string terminalName = GetTerminalNameByIp(agentIP);
+ string xmlData = _dbFileManager.CreateAndSaveTerminalFile(terminalName);
+
+ //NetworkStream 대신 StreamWriter를 사용하여 명확하게 데이터 전송
+ using (NetworkStream stream = client.GetStream())
+ using (StreamWriter writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true })
+ {
+ writer.WriteLine(xmlData); // Line 단위 전송
+ }
+
+ Console.WriteLine($"[{terminalName}] 데이터 전송 성공.");
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[{agentIP}] 데이터 전송 실패: {ex.Message}");
}
- }
- catch (Exception ex)
- {
- Console.WriteLine($"전송 실패 ({kvp.Key}): {ex.Message}");
}
}
}
}
+ // 단일 전송 비동기화 지원을 통한 네트워크 병목 방지
+ private async Task SendDataToSingleAgentAsync(TcpClient client, string message)
+ {
+ try
+ {
+ if (client != null && client.Connected)
+ {
+ byte[] data = Encoding.UTF8.GetBytes(message + "\n");
+ NetworkStream stream = client.GetStream();
+ await stream.WriteAsync(data, 0, data.Length);
+ await stream.FlushAsync();
+
+ System.Diagnostics.Debug.WriteLine("[데이터 전송 완료] 성공");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"단일 전송 실패: {ex.Message}");
+ }
+ }
+
+ private void UpdateVirtualMonitorFrame(string ip, string xmlData)
+ {
+ lock (_syncLock)
+ {
+ if (_openedMonitorForms.TryGetValue(ip, out var form) && form != null && !form.IsDisposed)
+ {
+ // 폼이 호출 가능(Load 완료)한 상태인지 확인
+ if (form.InvokeRequired)
+ {
+ form.BeginInvoke(new Action(() =>
+ {
+ try
+ {
+ form.UpdateMonitorData(xmlData);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine("모니터 업데이트 실패: " + ex.Message);
+ }
+ }));
+ }
+ else
+ {
+ form.UpdateMonitorData(xmlData);
+ }
+ }
+ else
+ {
+ System.Diagnostics.Debug.WriteLine($"[알림] {ip}에 해당하는 모니터링 폼이 아직 활성화되지 않았습니다.");
+ }
+ }
+ }
+
+ private string GetTerminalNameByIp(string ip)
+ {
+ string name = string.Empty;
+
+ if (this.InvokeRequired)
+ {
+ this.Invoke(new Action(() => name = FindNameInGrid(ip)));
+ }
+ else
+ {
+ name = FindNameInGrid(ip);
+ }
+
+ return string.IsNullOrEmpty(name) ? $"Terminal_{ip.Replace(".", "_")}" : name;
+ }
+
+ private string FindNameInGrid(string ip)
+ {
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (row.Cells[2].Value?.ToString() == ip)
+ {
+ return row.Cells[0].Value?.ToString();
+ }
+ }
+ return string.Empty;
+ }
+
+ private void Form1_FormClosing(object sender, FormClosingEventArgs e)
+ {
+ _isRunning = false;
+ _statusTimer?.Stop();
+ try { _listener?.Stop(); } catch { }
+
+ lock (_agentLock)
+ {
+ foreach (var client in _connectedAgents.Values)
+ {
+ try { client.Close(); } catch { }
+ }
+ _connectedAgents.Clear();
+ }
+ }
+
private void pannGray_Paint(object sender, PaintEventArgs e) { }
private void pannBlue_Paint(object sender, PaintEventArgs e) { }
+
+ public void SendDataToSingleAgent(string ip, string message)
+ {
+ // _connectedAgents 딕셔너리에서 해당 IP를 찾아 전송하는 로직
+ lock (_agentLock) // 안전한 접근을 위해 lock 사용
+ {
+ if (_connectedAgents.ContainsKey(ip))
+ {
+ TcpClient client = _connectedAgents[ip];
+ if (client != null && client.Connected)
+ {
+ try
+ {
+ byte[] data = Encoding.UTF8.GetBytes(message + "\n"); // 프로토콜에 맞게 줄바꿈 추가
+ NetworkStream stream = client.GetStream();
+ stream.Write(data, 0, data.Length);
+ stream.Flush();
+
+ System.Diagnostics.Debug.WriteLine($"[성공] {ip}로 데이터 전송 완료.");
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"[실패] {ip} 데이터 전송 중 오류: {ex.Message}");
+ }
+ }
+ }
+ else
+ {
+ System.Diagnostics.Debug.WriteLine($"[알림] {ip}에 연결된 에이전트가 없습니다.");
+ }
+ }
+ }
+
+ // 그리드에서 데이터 읽어오기 (표출시간, ON시간, OFF시간)
+ public string[] GetProgramTimesByIp(string ip)
+ {
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ // IP가 일치하는 행을 찾음
+ if (row.Cells[2].Value?.ToString() == ip)
+ {
+ // 배열 생성 시 한 줄에 쉼표로 구분하여 작성
+ return new string[] {
+ row.Cells[7].Value?.ToString(),
+ row.Cells[8].Value?.ToString(),
+ row.Cells[9].Value?.ToString()
+ };
+ }
+ }
+ // 기본값 반환
+ return new string[] { "5", "", "" };
+ }
+
+ // 그리드에 데이터 저장하기
+ public void SaveProgramTimesToGrid(string ip, string display, string on, string off)
+ {
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (row.Cells[2].Value?.ToString() == ip)
+ {
+ row.Cells[7].Value = display;
+ row.Cells[8].Value = on;
+ row.Cells[9].Value = off;
+ break;
+ }
+ }
+ }
+
+ private void grdTotList_CellContentClick(object sender, DataGridViewCellEventArgs e)
+ {
+
+ }
+
+ private void imgSearch_MouseClick(object sender, MouseEventArgs e)
+ {
+ // 1. 검색어 가져오기 (앞뒤 공백 제거)
+ string search = txtSearch.Text.Trim();
+
+ // 2. 검색어가 빈 값이면 전체 리스트 노출
+ if (string.IsNullOrEmpty(search))
+ {
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (!row.IsNewRow) row.Visible = true;
+ }
+ return; // 전체 노출 후 로직 종료
+ }
+ // 3. 검색어가 입력된 경우 필터링 진행
+ else
+ {
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (row.IsNewRow) continue; // 그리드 맨 밑의 빈 행(새 행)은 건너뜀
+
+ string terminalName = row.Cells[0].Value?.ToString() ?? "";
+ string ipAddress = row.Cells[2].Value?.ToString() ?? "";
+
+ // 단말기명 또는 IP에 검색어가 포함되어 있는지 확인
+ if (terminalName.ToLower().Contains(search) || ipAddress.Contains(search))
+ {
+ row.Visible = true; // 조건에 맞으면 보여줌
+ }
+ else
+ {
+ row.Visible = false; // 조건에 안 맞으면 숨김
+ }
+ }
+ }
+ }
+ private void txtSearch_TextChanged(object sender, EventArgs e)
+ {
+ // 1. 검색어 가져오기 (앞뒤 공백 제거)
+ string search = txtSearch.Text.Trim();
+
+ // 2. 검색어가 빈 값이면 전체 리스트 노출
+ if (string.IsNullOrEmpty(search))
+ {
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (!row.IsNewRow) row.Visible = true;
+ }
+ return; // 전체 노출 후 로직 종료
+ }
+ // 3. 검색어가 입력된 경우 필터링 진행
+ else
+ {
+ foreach (DataGridViewRow row in grdTotList.Rows)
+ {
+ if (row.IsNewRow) continue; // 그리드 맨 밑의 빈 행(새 행)은 건너뜀
+
+ string terminalName = row.Cells[0].Value?.ToString() ?? "";
+ string ipAddress = row.Cells[2].Value?.ToString() ?? "";
+
+ // 단말기명 또는 IP에 검색어가 포함되어 있는지 확인
+ if (terminalName.ToLower().Contains(search) || ipAddress.Contains(search))
+ {
+ row.Visible = true; // 조건에 맞으면 보여줌
+ }
+ else
+ {
+ row.Visible = false; // 조건에 안 맞으면 숨김
+ }
+ }
+ }
+ }
+
+ private void Form1_Load_1(object sender, EventArgs e)
+ {
+
+ }
+
+ // 텍스트 박스 크기가 결정되거나 변경될 때 둥근 리전을 생성하여 적용
+ private void TextBox_SizeChanged(object sender, EventArgs e)
+ {
+ System.Windows.Forms.TextBox txt = (System.Windows.Forms.TextBox)sender;
+ System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
+
+ // 둥근 모서리의 반지름 설정 (값이 클수록 더 둥글어집니다)
+ int radius = 14;
+
+ if (txt.Width > radius && txt.Height > radius)
+ {
+ path.AddArc(0, 0, radius, radius, 180, 90);
+ path.AddArc(txt.Width - radius, 0, radius, radius, 270, 90);
+ path.AddArc(txt.Width - radius, txt.Height - radius, radius, radius, 0, 90);
+ path.AddArc(0, txt.Height - radius, radius, radius, 90, 90);
+ path.CloseFigure();
+
+ txt.Region = new Region(path);
+ }
+ }
+
+ private void grdTotList_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
+ {
+ if (e.ColumnIndex == 4 && e.Value != null)
+ {
+ string status = e.Value.ToString();
+
+ if (status.Contains("Stop"))
+ {
+ e.CellStyle.ForeColor = Color.Red;
+ e.CellStyle.SelectionForeColor = Color.Red; // 선택 시에도 빨간 글씨 유지
+
+ e.CellStyle.BackColor = Color.White;
+ e.CellStyle.SelectionBackColor = Color.FromArgb(240, 245, 249); // 표 시안 배경색 유지
+ }
+ else
+ {
+ e.CellStyle.ForeColor = Color.Green;
+ e.CellStyle.SelectionForeColor = Color.Green; // 선택 시에도 초록 글씨 유지
+
+ e.CellStyle.BackColor = Color.White;
+ e.CellStyle.SelectionBackColor = Color.FromArgb(240, 245, 249);
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/ControlServer/Form1.resx b/ControlServer/Form1.resx
index d7de2eb..5405830 100644
--- a/ControlServer/Form1.resx
+++ b/ControlServer/Form1.resx
@@ -138,4 +138,13 @@
True
+
+ True
+
+
+ True
+
+
+ True
+
\ No newline at end of file
diff --git a/ControlServer/Form2.Designer.cs b/ControlServer/Form2.Designer.cs
index 51f9174..5288d5c 100644
--- a/ControlServer/Form2.Designer.cs
+++ b/ControlServer/Form2.Designer.cs
@@ -36,52 +36,54 @@
btnCancel = new Button();
txtBoxPort = new TextBox();
lbl5 = new Label();
+ txtBoxMac = new TextBox();
+ lbl2 = new Label();
+ lblTitle = new Label();
+ lblBox = new Label();
SuspendLayout();
//
// lbl0
//
- lbl0.BackColor = SystemColors.ControlDark;
- lbl0.BorderStyle = BorderStyle.FixedSingle;
- lbl0.Location = new Point(277, 106);
+ lbl0.BackColor = Color.White;
+ lbl0.Location = new Point(195, 116);
lbl0.Name = "lbl0";
- lbl0.Size = new Size(293, 50);
+ lbl0.Size = new Size(270, 50);
lbl0.TabIndex = 0;
lbl0.Text = "단말기명";
lbl0.TextAlign = ContentAlignment.MiddleLeft;
//
// lbl1
//
- lbl1.BackColor = SystemColors.ControlDark;
- lbl1.BorderStyle = BorderStyle.FixedSingle;
- lbl1.Location = new Point(277, 156);
+ lbl1.BackColor = Color.White;
+ lbl1.Location = new Point(195, 166);
lbl1.Name = "lbl1";
- lbl1.Size = new Size(293, 50);
+ lbl1.Size = new Size(270, 50);
lbl1.TabIndex = 1;
lbl1.Text = "IP";
lbl1.TextAlign = ContentAlignment.MiddleLeft;
//
// txtBoxName
//
- txtBoxName.Location = new Point(572, 106);
+ txtBoxName.Location = new Point(471, 127);
txtBoxName.Multiline = true;
txtBoxName.Name = "txtBoxName";
- txtBoxName.Size = new Size(507, 50);
+ txtBoxName.Size = new Size(350, 32);
txtBoxName.TabIndex = 4;
//
// txtBoxIP
//
- txtBoxIP.Location = new Point(572, 156);
+ txtBoxIP.Location = new Point(471, 177);
txtBoxIP.Multiline = true;
txtBoxIP.Name = "txtBoxIP";
- txtBoxIP.Size = new Size(507, 50);
+ txtBoxIP.Size = new Size(350, 32);
txtBoxIP.TabIndex = 5;
//
// btnRegister
//
- btnRegister.BackColor = SystemColors.Desktop;
+ btnRegister.BackColor = Color.RoyalBlue;
btnRegister.Font = new Font("맑은 고딕", 10.2F, FontStyle.Bold, GraphicsUnit.Point, 129);
btnRegister.ForeColor = SystemColors.Window;
- btnRegister.Location = new Point(460, 291);
+ btnRegister.Location = new Point(296, 366);
btnRegister.Name = "btnRegister";
btnRegister.Size = new Size(174, 56);
btnRegister.TabIndex = 9;
@@ -92,7 +94,7 @@
// btnCancel
//
btnCancel.Font = new Font("맑은 고딕", 10.2F, FontStyle.Bold, GraphicsUnit.Point, 129);
- btnCancel.Location = new Point(710, 291);
+ btnCancel.Location = new Point(535, 366);
btnCancel.Name = "btnCancel";
btnCancel.Size = new Size(174, 56);
btnCancel.TabIndex = 10;
@@ -102,28 +104,67 @@
//
// txtBoxPort
//
- txtBoxPort.Location = new Point(572, 206);
+ txtBoxPort.Location = new Point(471, 227);
txtBoxPort.Multiline = true;
txtBoxPort.Name = "txtBoxPort";
- txtBoxPort.Size = new Size(507, 50);
+ txtBoxPort.Size = new Size(350, 32);
txtBoxPort.TabIndex = 12;
//
// lbl5
//
- lbl5.BackColor = SystemColors.ControlDark;
- lbl5.BorderStyle = BorderStyle.FixedSingle;
- lbl5.Location = new Point(277, 206);
+ lbl5.BackColor = Color.White;
+ lbl5.Location = new Point(195, 216);
lbl5.Name = "lbl5";
- lbl5.Size = new Size(293, 50);
+ lbl5.Size = new Size(270, 50);
lbl5.TabIndex = 11;
lbl5.Text = "Port";
lbl5.TextAlign = ContentAlignment.MiddleLeft;
//
+ // txtBoxMac
+ //
+ txtBoxMac.Location = new Point(471, 276);
+ txtBoxMac.Multiline = true;
+ txtBoxMac.Name = "txtBoxMac";
+ txtBoxMac.Size = new Size(350, 32);
+ txtBoxMac.TabIndex = 14;
+ //
+ // lbl2
+ //
+ lbl2.BackColor = Color.White;
+ lbl2.Location = new Point(195, 266);
+ lbl2.Name = "lbl2";
+ lbl2.Size = new Size(270, 50);
+ lbl2.TabIndex = 13;
+ lbl2.Text = "MAC";
+ lbl2.TextAlign = ContentAlignment.MiddleLeft;
+ //
+ // lblTitle
+ //
+ lblTitle.AutoSize = true;
+ lblTitle.BackColor = Color.White;
+ lblTitle.Font = new Font("맑은 고딕", 18F, FontStyle.Bold, GraphicsUnit.Point, 129);
+ lblTitle.Location = new Point(92, 34);
+ lblTitle.Name = "lblTitle";
+ lblTitle.Size = new Size(179, 41);
+ lblTitle.TabIndex = 15;
+ lblTitle.Text = "단말기 등록";
+ //
+ // lblBox
+ //
+ lblBox.BackColor = Color.White;
+ lblBox.Location = new Point(56, 23);
+ lblBox.Name = "lblBox";
+ lblBox.Size = new Size(953, 306);
+ lblBox.TabIndex = 16;
+ //
// Form2
//
AutoScaleDimensions = new SizeF(9F, 20F);
AutoScaleMode = AutoScaleMode.Font;
- ClientSize = new Size(1357, 737);
+ ClientSize = new Size(1070, 461);
+ Controls.Add(lblTitle);
+ Controls.Add(txtBoxMac);
+ Controls.Add(lbl2);
Controls.Add(txtBoxPort);
Controls.Add(lbl5);
Controls.Add(btnCancel);
@@ -132,6 +173,7 @@
Controls.Add(txtBoxName);
Controls.Add(lbl1);
Controls.Add(lbl0);
+ Controls.Add(lblBox);
Name = "Form2";
ResumeLayout(false);
PerformLayout();
@@ -147,5 +189,9 @@
private Button btnCancel;
private TextBox txtBoxPort;
private Label lbl5;
+ private TextBox txtBoxMac;
+ private Label lbl2;
+ private Label lblTitle;
+ private Label lblBox;
}
}
\ No newline at end of file
diff --git a/ControlServer/Form2.cs b/ControlServer/Form2.cs
index e3e82b9..1497501 100644
--- a/ControlServer/Form2.cs
+++ b/ControlServer/Form2.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
+using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
@@ -12,31 +13,106 @@ namespace ControlServer
{
public TerminalInfo TerminalData { get; private set; } = new TerminalInfo();
+ // 둥근 모서리 반지름 설정
+ private const int CornerRadius = 15;
+
public Form2()
{
InitializeComponent();
+ // 1. 버튼 테두리 없애고 Flat 스타일로 변경
+ ConfigureModernButtons();
+ btnRegister.BackColor = Color.FromArgb(160, 180, 240); // 연해진 블루
btnRegister.Enabled = false; // 저장버튼 비활성화
+
// 모든 텍스트박스의 값이 변할 때마다 실행될 이벤트 연결
txtBoxName.TextChanged += ValidateInputs;
txtBoxIP.TextChanged += ValidateInputs;
txtBoxPort.TextChanged += ValidateInputs;
+ txtBoxMac.TextChanged += ValidateInputs;
+
+ // 2. 둥근 모서리를 그리기 위한 Paint 이벤트 연결
+ if (lblBox != null) lblBox.Paint += lblBox_Paint;
+ btnRegister.Paint += Button_Paint;
+ btnCancel.Paint += Button_Paint;
+ }
+
+ private void ConfigureModernButtons()
+ {
+ // 등록 버튼 스타일 설정
+ btnRegister.FlatStyle = FlatStyle.Flat;
+ btnRegister.FlatAppearance.BorderSize = 0; // 보더라인 삭제
+
+ // 취소 버튼 스타일 설정
+ btnCancel.BackColor = Color.FromArgb(230, 235, 240);
+ btnCancel.FlatStyle = FlatStyle.Flat;
+ btnCancel.FlatAppearance.BorderSize = 0; // 보더라인 삭제
+ }
+
+ // 컨트롤을 둥글게 만들기 위한 공통 경로 생성 메서드
+ private GraphicsPath GetRoundPath(Rectangle bounds, int radius)
+ {
+ GraphicsPath path = new GraphicsPath();
+ int diameter = radius * 2;
+
+ path.AddArc(bounds.X, bounds.Y, diameter, diameter, 180, 90);
+ path.AddArc(bounds.Right - diameter, bounds.Y, diameter, diameter, 270, 90);
+ path.AddArc(bounds.Right - diameter, bounds.Bottom - diameter, diameter, diameter, 0, 90);
+ path.AddArc(bounds.X, bounds.Bottom - diameter, diameter, diameter, 90, 90);
+ path.CloseAllFigures();
+
+ return path;
+ }
+
+ // 흰색 배경 박스 둥글게 자르기
+ private void lblBox_Paint(object? sender, PaintEventArgs e)
+ {
+ if (sender is Panel panel)
+ {
+ e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
+ using (GraphicsPath path = GetRoundPath(new Rectangle(0, 0, panel.Width, panel.Height), CornerRadius))
+ {
+ panel.Region = new Region(path);
+ }
+ }
+ }
+
+ // 버튼 둥글게 자르기
+ private void Button_Paint(object? sender, PaintEventArgs e)
+ {
+ if (sender is Button button)
+ {
+ e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
+ // 버튼은 박스보다 조금 더 작으므로 반지름을 10 정도로 주면 예쁩니다
+ using (GraphicsPath path = GetRoundPath(new Rectangle(0, 0, button.Width, button.Height), 10))
+ {
+ button.Region = new Region(path);
+ }
+ }
}
private void ValidateInputs(object? sender, EventArgs e)
{
- // 모든 텍스트박스가 공백이 아닐 때만 true
bool isValid = !string.IsNullOrWhiteSpace(txtBoxName.Text) &&
!string.IsNullOrWhiteSpace(txtBoxIP.Text) &&
- !string.IsNullOrWhiteSpace(txtBoxPort.Text);
+ !string.IsNullOrWhiteSpace(txtBoxPort.Text) &&
+ !string.IsNullOrWhiteSpace(txtBoxMac.Text);
- // 저장 버튼 활성화 상태 조절
btnRegister.Enabled = isValid;
+
+ // [팁] 비활성화 상태일 때 버튼 색상 피드백을 주면 더 자연스럽습니다.
+ if (isValid)
+ {
+ btnRegister.BackColor = Color.FromArgb(65, 105, 225); // 원래 로열 블루 색상
+ }
+ else
+ {
+ btnRegister.BackColor = Color.FromArgb(160, 180, 240); // 연해진 블루
+ }
}
private void btnCancel_MouseClick(object sender, MouseEventArgs e)
{
- // 취소 버튼
this.Close();
}
@@ -44,17 +120,23 @@ namespace ControlServer
{
// 1. DB 중복 데이터 확인
- // 2. 중복 데이터 없을 경우 DB에 데이터 추가
- // DB 가져오면 데이터 넘겨주는게 아니라 테이블에 데이터 추가하고 Form1 화면에서 데이터 가져와 리스트 보여주는 형식으로 변경할 예정
- Popup popup = new Popup("등록되었습니다.");
+ // 2. 메시지박스 띄우기 (알림 아이콘과 OK 버튼 포함)
+ DialogResult result = MessageBox.Show(
+ "등록되었습니다.",
+ "알림",
+ MessageBoxButtons.OK,
+ MessageBoxIcon.Information
+ );
- if (popup.ShowDialog() == DialogResult.OK)
+ // 사용자가 메시지박스의 '확인'을 누르면 데이터 저장 진행
+ if (result == DialogResult.OK)
{
this.TerminalData = new TerminalInfo
{
TerminalName = txtBoxName.Text.Trim(),
IP = txtBoxIP.Text.Trim(),
- Port = txtBoxPort.Text.Trim()
+ Port = txtBoxPort.Text.Trim(),
+ Mac = txtBoxMac.Text.Trim(),
};
}
@@ -62,4 +144,4 @@ namespace ControlServer
this.Close();
}
}
-}
+}
\ No newline at end of file
diff --git a/ControlServer/Form3.Designer.cs b/ControlServer/Form3.Designer.cs
index aa0e3a8..0877cf7 100644
--- a/ControlServer/Form3.Designer.cs
+++ b/ControlServer/Form3.Designer.cs
@@ -28,6 +28,7 @@
///
private void InitializeComponent()
{
+ DataGridViewCellStyle dataGridViewCellStyle1 = new DataGridViewCellStyle();
lblTerminalName = new Label();
lblTerminalIP = new Label();
label1 = new Label();
@@ -36,28 +37,42 @@
label3 = new Label();
label4 = new Label();
pannUpdate = new Panel();
- rdoUpdate5m = new RadioButton();
- rdoUpdate3m = new RadioButton();
- rdoUpdate1m = new RadioButton();
+ label7 = new Label();
+ txtBoxDisplay = new TextBox();
pannStartTime = new Panel();
+ label8 = new Label();
+ txtBoxOnSS = new TextBox();
label5 = new Label();
txtBoxOnMM = new TextBox();
txtBoxOnYY = new TextBox();
pannStopTime = new Panel();
+ label9 = new Label();
+ txtBoxOffSS = new TextBox();
label6 = new Label();
txtBoxOffMM = new TextBox();
txtBoxOffYY = new TextBox();
btnCancel = new Button();
btnUpdate = new Button();
+ grdDataList = new DataGridView();
+ lblBox = new Label();
+ Col0 = new DataGridViewTextBoxColumn();
+ Col1 = new DataGridViewTextBoxColumn();
+ Col2 = new DataGridViewTextBoxColumn();
+ Col3 = new DataGridViewTextBoxColumn();
+ Col4 = new DataGridViewTextBoxColumn();
+ Col5 = new DataGridViewTextBoxColumn();
+ Col6 = new DataGridViewTextBoxColumn();
pannUpdate.SuspendLayout();
pannStartTime.SuspendLayout();
pannStopTime.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)grdDataList).BeginInit();
SuspendLayout();
//
// lblTerminalName
//
+ lblTerminalName.BackColor = Color.White;
lblTerminalName.Font = new Font("맑은 고딕", 9F, FontStyle.Bold, GraphicsUnit.Point, 129);
- lblTerminalName.Location = new Point(430, 42);
+ lblTerminalName.Location = new Point(256, 102);
lblTerminalName.Name = "lblTerminalName";
lblTerminalName.Size = new Size(288, 50);
lblTerminalName.TabIndex = 0;
@@ -65,7 +80,8 @@
//
// lblTerminalIP
//
- lblTerminalIP.Location = new Point(430, 118);
+ lblTerminalIP.BackColor = Color.White;
+ lblTerminalIP.Location = new Point(256, 178);
lblTerminalIP.Name = "lblTerminalIP";
lblTerminalIP.Size = new Size(288, 50);
lblTerminalIP.TabIndex = 1;
@@ -73,7 +89,8 @@
//
// label1
//
- label1.Location = new Point(254, 42);
+ label1.BackColor = Color.White;
+ label1.Location = new Point(103, 102);
label1.Name = "label1";
label1.Size = new Size(147, 50);
label1.TabIndex = 2;
@@ -82,7 +99,8 @@
//
// label2
//
- label2.Location = new Point(254, 118);
+ label2.BackColor = Color.White;
+ label2.Location = new Point(103, 178);
label2.Name = "label2";
label2.Size = new Size(147, 50);
label2.TabIndex = 3;
@@ -91,20 +109,18 @@
//
// lbl0
//
- lbl0.BackColor = SystemColors.ControlDark;
- lbl0.BorderStyle = BorderStyle.FixedSingle;
- lbl0.Location = new Point(254, 223);
+ lbl0.BackColor = Color.White;
+ lbl0.Location = new Point(97, 285);
lbl0.Name = "lbl0";
lbl0.Size = new Size(159, 50);
lbl0.TabIndex = 4;
- lbl0.Text = "갱신주기";
+ lbl0.Text = "표출시간";
lbl0.TextAlign = ContentAlignment.MiddleLeft;
//
// label3
//
- label3.BackColor = SystemColors.ControlDark;
- label3.BorderStyle = BorderStyle.FixedSingle;
- label3.Location = new Point(254, 273);
+ label3.BackColor = Color.White;
+ label3.Location = new Point(97, 335);
label3.Name = "label3";
label3.Size = new Size(159, 50);
label3.TabIndex = 5;
@@ -113,9 +129,8 @@
//
// label4
//
- label4.BackColor = SystemColors.ControlDark;
- label4.BorderStyle = BorderStyle.FixedSingle;
- label4.Location = new Point(254, 323);
+ label4.BackColor = Color.White;
+ label4.Location = new Point(97, 385);
label4.Name = "label4";
label4.Size = new Size(159, 50);
label4.TabIndex = 6;
@@ -124,59 +139,61 @@
//
// pannUpdate
//
- pannUpdate.BorderStyle = BorderStyle.FixedSingle;
- pannUpdate.Controls.Add(rdoUpdate5m);
- pannUpdate.Controls.Add(rdoUpdate3m);
- pannUpdate.Controls.Add(rdoUpdate1m);
- pannUpdate.Location = new Point(413, 223);
+ pannUpdate.BackColor = Color.White;
+ pannUpdate.Controls.Add(label7);
+ pannUpdate.Controls.Add(txtBoxDisplay);
+ pannUpdate.Location = new Point(256, 285);
pannUpdate.Name = "pannUpdate";
pannUpdate.Size = new Size(356, 50);
pannUpdate.TabIndex = 7;
//
- // rdoUpdate5m
+ // label7
//
- rdoUpdate5m.AutoSize = true;
- rdoUpdate5m.Location = new Point(157, 12);
- rdoUpdate5m.Name = "rdoUpdate5m";
- rdoUpdate5m.Size = new Size(53, 24);
- rdoUpdate5m.TabIndex = 2;
- rdoUpdate5m.TabStop = true;
- rdoUpdate5m.Text = "5분";
- rdoUpdate5m.UseVisualStyleBackColor = true;
+ label7.AutoSize = true;
+ label7.Location = new Point(86, 14);
+ label7.Name = "label7";
+ label7.Size = new Size(24, 20);
+ label7.TabIndex = 1;
+ label7.Text = "초";
//
- // rdoUpdate3m
+ // txtBoxDisplay
//
- rdoUpdate3m.AutoSize = true;
- rdoUpdate3m.Location = new Point(86, 12);
- rdoUpdate3m.Name = "rdoUpdate3m";
- rdoUpdate3m.Size = new Size(53, 24);
- rdoUpdate3m.TabIndex = 1;
- rdoUpdate3m.TabStop = true;
- rdoUpdate3m.Text = "3분";
- rdoUpdate3m.UseVisualStyleBackColor = true;
- //
- // rdoUpdate1m
- //
- rdoUpdate1m.AutoSize = true;
- rdoUpdate1m.Location = new Point(16, 12);
- rdoUpdate1m.Name = "rdoUpdate1m";
- rdoUpdate1m.Size = new Size(53, 24);
- rdoUpdate1m.TabIndex = 0;
- rdoUpdate1m.TabStop = true;
- rdoUpdate1m.Text = "1분";
- rdoUpdate1m.UseVisualStyleBackColor = true;
+ txtBoxDisplay.Location = new Point(16, 11);
+ txtBoxDisplay.Name = "txtBoxDisplay";
+ txtBoxDisplay.Size = new Size(64, 27);
+ txtBoxDisplay.TabIndex = 0;
+ txtBoxDisplay.Text = "5";
//
// pannStartTime
//
- pannStartTime.BorderStyle = BorderStyle.FixedSingle;
+ pannStartTime.BackColor = Color.White;
+ pannStartTime.Controls.Add(label8);
+ pannStartTime.Controls.Add(txtBoxOnSS);
pannStartTime.Controls.Add(label5);
pannStartTime.Controls.Add(txtBoxOnMM);
pannStartTime.Controls.Add(txtBoxOnYY);
- pannStartTime.Location = new Point(413, 273);
+ pannStartTime.Location = new Point(256, 335);
pannStartTime.Name = "pannStartTime";
pannStartTime.Size = new Size(356, 50);
pannStartTime.TabIndex = 8;
//
+ // label8
+ //
+ label8.AutoSize = true;
+ label8.Location = new Point(171, 14);
+ label8.Name = "label8";
+ label8.Size = new Size(12, 20);
+ label8.TabIndex = 4;
+ label8.Text = ":";
+ //
+ // txtBoxOnSS
+ //
+ txtBoxOnSS.Location = new Point(185, 11);
+ txtBoxOnSS.Name = "txtBoxOnSS";
+ txtBoxOnSS.Size = new Size(64, 27);
+ txtBoxOnSS.TabIndex = 3;
+ txtBoxOnSS.TextAlign = HorizontalAlignment.Center;
+ //
// label5
//
label5.AutoSize = true;
@@ -204,15 +221,34 @@
//
// pannStopTime
//
- pannStopTime.BorderStyle = BorderStyle.FixedSingle;
+ pannStopTime.BackColor = Color.White;
+ pannStopTime.Controls.Add(label9);
+ pannStopTime.Controls.Add(txtBoxOffSS);
pannStopTime.Controls.Add(label6);
pannStopTime.Controls.Add(txtBoxOffMM);
pannStopTime.Controls.Add(txtBoxOffYY);
- pannStopTime.Location = new Point(413, 323);
+ pannStopTime.Location = new Point(256, 385);
pannStopTime.Name = "pannStopTime";
pannStopTime.Size = new Size(356, 50);
pannStopTime.TabIndex = 9;
//
+ // label9
+ //
+ label9.AutoSize = true;
+ label9.Location = new Point(169, 14);
+ label9.Name = "label9";
+ label9.Size = new Size(12, 20);
+ label9.TabIndex = 7;
+ label9.Text = ":";
+ //
+ // txtBoxOffSS
+ //
+ txtBoxOffSS.Location = new Point(185, 11);
+ txtBoxOffSS.Name = "txtBoxOffSS";
+ txtBoxOffSS.Size = new Size(64, 27);
+ txtBoxOffSS.TabIndex = 6;
+ txtBoxOffSS.TextAlign = HorizontalAlignment.Center;
+ //
// label6
//
label6.AutoSize = true;
@@ -241,7 +277,7 @@
// btnCancel
//
btnCancel.Font = new Font("맑은 고딕", 10.2F, FontStyle.Bold, GraphicsUnit.Point, 129);
- btnCancel.Location = new Point(559, 408);
+ btnCancel.Location = new Point(386, 497);
btnCancel.Name = "btnCancel";
btnCancel.Size = new Size(174, 56);
btnCancel.TabIndex = 12;
@@ -251,10 +287,10 @@
//
// btnUpdate
//
- btnUpdate.BackColor = SystemColors.Desktop;
+ btnUpdate.BackColor = Color.RoyalBlue;
btnUpdate.Font = new Font("맑은 고딕", 10.2F, FontStyle.Bold, GraphicsUnit.Point, 129);
btnUpdate.ForeColor = SystemColors.Window;
- btnUpdate.Location = new Point(309, 408);
+ btnUpdate.Location = new Point(136, 497);
btnUpdate.Name = "btnUpdate";
btnUpdate.Size = new Size(174, 56);
btnUpdate.TabIndex = 11;
@@ -262,11 +298,92 @@
btnUpdate.UseVisualStyleBackColor = false;
btnUpdate.MouseClick += btnUpdate_MouseClick;
//
+ // grdDataList
+ //
+ dataGridViewCellStyle1.Alignment = DataGridViewContentAlignment.MiddleLeft;
+ dataGridViewCellStyle1.BackColor = SystemColors.Control;
+ dataGridViewCellStyle1.Font = new Font("맑은 고딕", 7.8F, FontStyle.Regular, GraphicsUnit.Point, 129);
+ dataGridViewCellStyle1.ForeColor = SystemColors.WindowText;
+ dataGridViewCellStyle1.SelectionBackColor = SystemColors.Highlight;
+ dataGridViewCellStyle1.SelectionForeColor = SystemColors.HighlightText;
+ dataGridViewCellStyle1.WrapMode = DataGridViewTriState.True;
+ grdDataList.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
+ grdDataList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+ grdDataList.Columns.AddRange(new DataGridViewColumn[] { Col0, Col1, Col2, Col3, Col4, Col5, Col6 });
+ grdDataList.Location = new Point(704, 46);
+ grdDataList.Name = "grdDataList";
+ grdDataList.RowHeadersWidth = 51;
+ grdDataList.Size = new Size(641, 679);
+ grdDataList.TabIndex = 13;
+ //
+ // lblBox
+ //
+ lblBox.BackColor = Color.White;
+ lblBox.Location = new Point(43, 46);
+ lblBox.Name = "lblBox";
+ lblBox.Size = new Size(597, 542);
+ lblBox.TabIndex = 14;
+ //
+ // Col0
+ //
+ Col0.HeaderText = "경매일자";
+ Col0.MinimumWidth = 6;
+ Col0.Name = "Col0";
+ Col0.ReadOnly = true;
+ Col0.Width = 125;
+ //
+ // Col1
+ //
+ Col1.HeaderText = "품목명";
+ Col1.MinimumWidth = 6;
+ Col1.Name = "Col1";
+ Col1.ReadOnly = true;
+ Col1.Width = 125;
+ //
+ // Col2
+ //
+ Col2.HeaderText = "산지";
+ Col2.MinimumWidth = 6;
+ Col2.Name = "Col2";
+ Col2.ReadOnly = true;
+ Col2.Width = 125;
+ //
+ // Col3
+ //
+ Col3.HeaderText = "전체수량";
+ Col3.MinimumWidth = 6;
+ Col3.Name = "Col3";
+ Col3.ReadOnly = true;
+ Col3.Width = 80;
+ //
+ // Col4
+ //
+ Col4.HeaderText = "정가수의 수량";
+ Col4.MinimumWidth = 6;
+ Col4.Name = "Col4";
+ Col4.ReadOnly = true;
+ //
+ // Col5
+ //
+ Col5.HeaderText = "전체물량";
+ Col5.MinimumWidth = 6;
+ Col5.Name = "Col5";
+ Col5.ReadOnly = true;
+ Col5.Width = 80;
+ //
+ // Col6
+ //
+ Col6.HeaderText = "정가수의 물량";
+ Col6.MinimumWidth = 6;
+ Col6.Name = "Col6";
+ Col6.ReadOnly = true;
+ //
// Form3
//
AutoScaleDimensions = new SizeF(9F, 20F);
AutoScaleMode = AutoScaleMode.Font;
- ClientSize = new Size(1008, 563);
+ ClientSize = new Size(1357, 737);
+ Controls.Add(grdDataList);
Controls.Add(btnCancel);
Controls.Add(btnUpdate);
Controls.Add(pannStopTime);
@@ -279,14 +396,16 @@
Controls.Add(label1);
Controls.Add(lblTerminalIP);
Controls.Add(lblTerminalName);
+ Controls.Add(lblBox);
Name = "Form3";
- Text = "Form3";
+ Text = "S";
pannUpdate.ResumeLayout(false);
pannUpdate.PerformLayout();
pannStartTime.ResumeLayout(false);
pannStartTime.PerformLayout();
pannStopTime.ResumeLayout(false);
pannStopTime.PerformLayout();
+ ((System.ComponentModel.ISupportInitialize)grdDataList).EndInit();
ResumeLayout(false);
}
@@ -302,9 +421,6 @@
private Panel pannUpdate;
private Panel pannStartTime;
private Panel pannStopTime;
- private RadioButton rdoUpdate1m;
- private RadioButton rdoUpdate5m;
- private RadioButton rdoUpdate3m;
private TextBox txtBoxOnYY;
private TextBox txtBoxOnMM;
private Label label5;
@@ -313,5 +429,20 @@
private TextBox txtBoxOffYY;
private Button btnCancel;
private Button btnUpdate;
+ private TextBox txtBoxDisplay;
+ private Label label7;
+ private Label label8;
+ private TextBox txtBoxOnSS;
+ private Label label9;
+ private TextBox txtBoxOffSS;
+ private DataGridView grdDataList;
+ private Label lblBox;
+ private DataGridViewTextBoxColumn Col0;
+ private DataGridViewTextBoxColumn Col1;
+ private DataGridViewTextBoxColumn Col2;
+ private DataGridViewTextBoxColumn Col3;
+ private DataGridViewTextBoxColumn Col4;
+ private DataGridViewTextBoxColumn Col5;
+ private DataGridViewTextBoxColumn Col6;
}
}
\ No newline at end of file
diff --git a/ControlServer/Form3.cs b/ControlServer/Form3.cs
index 7f7b793..863f05a 100644
--- a/ControlServer/Form3.cs
+++ b/ControlServer/Form3.cs
@@ -3,44 +3,220 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
+using System.Drawing.Drawing2D; // 둥근 모서리 그리기(GraphicsPath)에 필수
+using System.IO;
using System.Text;
using System.Windows.Forms;
+using System.Xml.Linq;
namespace ControlServer
{
public partial class Form3 : Form
{
- public Form3(string terminalName, string terminalIP) // 매개변수 추가
+ private Form1 _mainForm; // 메인 폼 참조 변수
+ private string _targetIP;
+ private string _filePath;
+
+ // 둥근 모서리 반지름 설정 (정수형 int로 선언)
+ private const int CornerRadiusValue = 15;
+
+ public Form3(Form1 mainForm, string terminalName, string terminalIP, string filePath)
{
InitializeComponent();
- // 가져온 데이터를 Form3의 라벨이나 텍스트박스에 표시
+ // 1. 버튼 테두리 없애고 Flat 스타일 및 이벤트 연결
+ ConfigureModernStyles();
+
+ // 2. 그리드 디자인을 오른쪽 시안 스타일에 맞게 초기화
+ InitDataGridViewStyles();
+
+ _mainForm = mainForm;
+ _targetIP = terminalIP;
+ _filePath = filePath;
+
lblTerminalName.Text = terminalName;
lblTerminalIP.Text = terminalIP;
+ string[] timeData = _mainForm.GetProgramTimesByIp(_targetIP);
+
+ txtBoxDisplay.Text = string.IsNullOrEmpty(timeData[0]) ? "5" : timeData[0];
+
+ if (!string.IsNullOrEmpty(timeData[1]) && timeData[1].Contains(":"))
+ {
+ string[] onTime = timeData[1].Split(':');
+ txtBoxOnYY.Text = onTime[0];
+ txtBoxOnMM.Text = onTime[1];
+ txtBoxOnSS.Text = onTime[2];
+ }
+
+ if (!string.IsNullOrEmpty(timeData[2]) && timeData[2].Contains(":"))
+ {
+ string[] offTime = timeData[2].Split(':');
+ txtBoxOffYY.Text = offTime[0];
+ txtBoxOffMM.Text = offTime[1];
+ txtBoxOffSS.Text = offTime[2];
+ }
+
txtBoxOnYY.TextChanged += ValidateInputs;
txtBoxOnMM.TextChanged += ValidateInputs;
+ txtBoxOnSS.TextChanged += ValidateInputs;
txtBoxOffYY.TextChanged += ValidateInputs;
txtBoxOffMM.TextChanged += ValidateInputs;
+ txtBoxOffSS.TextChanged += ValidateInputs;
+
txtBoxOnYY.PlaceholderText = "YY";
txtBoxOnMM.PlaceholderText = "MM";
+ txtBoxOnSS.PlaceholderText = "SS";
txtBoxOffYY.PlaceholderText = "YY";
txtBoxOffMM.PlaceholderText = "MM";
+ txtBoxOffSS.PlaceholderText = "SS";
+
+ LoadAndParseFile();
+ }
+
+ // ⭐️ 오른쪽 그리드뷰 디자인 시안을 반영한 메서드
+ // ⭐️ 오른쪽 그리드뷰 디자인 시안을 반영한 메서드
+ private void InitDataGridViewStyles()
+ {
+ grdDataList.AllowUserToResizeRows = false;
+ grdDataList.AllowUserToResizeColumns = false;
+ grdDataList.RowHeadersVisible = false; // 왼쪽 화살표 열 숨김
+ grdDataList.AllowUserToAddRows = false; // 빈 칸 추가 방지
+ grdDataList.ReadOnly = true; // 읽기 전용
+ grdDataList.SelectionMode = DataGridViewSelectionMode.FullRowSelect; // 행 선택
+ grdDataList.MultiSelect = false;
+
+ // 커스텀 헤더 스타일을 적용하기 위해 반드시 false 설정
+ grdDataList.EnableHeadersVisualStyles = false;
+
+ // 그리드 전체 배경 및 기본 테두리 제거 (시안처럼 하얗고 투명하게)
+ grdDataList.BackgroundColor = Color.White;
+ grdDataList.BorderStyle = BorderStyle.None;
+
+ // 셀 구분선 설정 (옅은 회색 가로선만 남김)
+ grdDataList.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
+ grdDataList.GridColor = Color.FromArgb(240, 242, 245);
+
+ // [상단 헤더 스타일 수정]
+ DataGridViewCellStyle headerStyle = new DataGridViewCellStyle();
+ headerStyle.BackColor = Color.FromArgb(44, 62, 80);
+ headerStyle.ForeColor = Color.White;
+ headerStyle.Font = new Font("맑은 고딕", 9.5F, FontStyle.Bold);
+ headerStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
+
+ // 행을 선택하더라도 헤더의 선택 배경색/글자색이 기본 배경색과 똑같이 유지되도록 고정
+ headerStyle.SelectionBackColor = Color.FromArgb(44, 62, 80);
+ headerStyle.SelectionForeColor = Color.White;
+
+ grdDataList.ColumnHeadersDefaultCellStyle = headerStyle;
+ grdDataList.ColumnHeadersHeight = 38; // 헤더 높이 여유롭게 조정
+ grdDataList.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
+
+ // 행 선택 시 헤더 스타일 상속으로 인한 테두리 및 색상 왜곡 방지
+ grdDataList.EnableHeadersVisualStyles = false;
+
+ // [일반 데이터 셀 스타일 수정]
+ DataGridViewCellStyle cellStyle = new DataGridViewCellStyle();
+ cellStyle.BackColor = Color.White;
+ cellStyle.ForeColor = Color.FromArgb(50, 50, 50); // 부드러운 차콜 블랙
+ cellStyle.Font = new Font("맑은 고딕", 9F, FontStyle.Regular);
+ cellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
+
+ // 행 선택(클릭) 시 파란색 대신 부드러운 회청색으로 채우기
+ cellStyle.SelectionBackColor = Color.FromArgb(242, 246, 252);
+ cellStyle.SelectionForeColor = Color.FromArgb(50, 50, 50);
+
+ grdDataList.DefaultCellStyle = cellStyle;
+
+ // 시안처럼 행 간격(높이)을 시원하게 벌려 가시성을 높임
+ grdDataList.RowTemplate.Height = 38;
+
+ grdDataList.ClearSelection();
+ }
+
+ private void ConfigureModernStyles()
+ {
+ // 수정 버튼 스타일 설정 (테두리 삭제)
+ btnUpdate.FlatStyle = FlatStyle.Flat;
+ btnUpdate.FlatAppearance.BorderSize = 0;
+
+ // 취소 버튼 스타일 설정 (테두리 삭제 및 부드러운 회색)
+ btnCancel.FlatStyle = FlatStyle.Flat;
+ btnCancel.FlatAppearance.BorderSize = 0;
+ btnCancel.BackColor = Color.FromArgb(230, 235, 240);
+ btnCancel.ForeColor = Color.FromArgb(80, 80, 80);
+
+ // 둥근 처리를 위한 Paint 이벤트 연결
+ if (lblBox != null) lblBox.Paint += lblBox_Paint;
+ btnUpdate.Paint += Button_Paint;
+ btnCancel.Paint += Button_Paint;
+ }
+
+ // 둥근 모서리 경로를 만들어주는 공통 메서드
+ private GraphicsPath GetRoundPath(Rectangle bounds, int radius)
+ {
+ GraphicsPath path = new GraphicsPath();
+ int diameter = radius * 2;
+
+ path.AddArc(bounds.X, bounds.Y, diameter, diameter, 180, 90);
+ path.AddArc(bounds.Right - diameter, bounds.Y, diameter, diameter, 270, 90);
+ path.AddArc(bounds.Right - diameter, bounds.Bottom - diameter, diameter, diameter, 0, 90);
+ path.AddArc(bounds.X, bounds.Bottom - diameter, diameter, diameter, 90, 90);
+ path.CloseAllFigures();
+
+ return path;
+ }
+
+ // 흰색 배경 박스 둥글게 자르기
+ private void lblBox_Paint(object? sender, PaintEventArgs e)
+ {
+ if (sender is Control control)
+ {
+ e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
+ using (GraphicsPath path = GetRoundPath(new Rectangle(0, 0, control.Width, control.Height), CornerRadiusValue))
+ {
+ control.Region = new Region(path);
+ }
+ }
+ }
+
+ // 버튼들 둥글게 자르기
+ private void Button_Paint(object? sender, PaintEventArgs e)
+ {
+ if (sender is Button button)
+ {
+ e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
+ using (GraphicsPath path = GetRoundPath(new Rectangle(0, 0, button.Width, button.Height), 10))
+ {
+ button.Region = new Region(path);
+ }
+ }
}
private void ValidateInputs(object? sender, EventArgs e)
{
- // 프로그램 시간 조건 체크
bool isOnYYEmpty = string.IsNullOrWhiteSpace(txtBoxOnYY.Text);
bool isOnMMEmpty = string.IsNullOrWhiteSpace(txtBoxOnMM.Text);
+ bool isOnSSEmpty = string.IsNullOrWhiteSpace(txtBoxOnSS.Text);
bool isOffYYEmpty = string.IsNullOrWhiteSpace(txtBoxOffYY.Text);
- bool isOffMMEmpty = string.IsNullOrWhiteSpace(@txtBoxOffMM.Text);
+ bool isOffMMEmpty = string.IsNullOrWhiteSpace(txtBoxOffMM.Text);
+ bool isOffSSEmpty = string.IsNullOrWhiteSpace(txtBoxOffSS.Text);
- // 둘 다 비어있거나(AND), 둘 다 비어있지 않거나(AND)
- bool isTimeValid = (isOnYYEmpty && isOnMMEmpty && isOffYYEmpty && isOffMMEmpty) || (!isOnYYEmpty && !isOnMMEmpty && !isOffYYEmpty && !isOffMMEmpty);
+ bool isTimeValid = (isOnYYEmpty && isOnMMEmpty && isOnSSEmpty && isOffYYEmpty && isOffMMEmpty && isOffSSEmpty) ||
+ (!isOnYYEmpty && !isOnMMEmpty && !isOnSSEmpty && !isOffYYEmpty && !isOffMMEmpty && !isOffSSEmpty);
- // 저장 버튼 활성화
btnUpdate.Enabled = isTimeValid;
+
+ if (isTimeValid)
+ {
+ btnUpdate.BackColor = Color.FromArgb(52, 152, 219); // 활성화 스카이블루
+ btnUpdate.ForeColor = Color.White;
+ }
+ else
+ {
+ btnUpdate.BackColor = Color.FromArgb(180, 210, 235); // 비활성화 흐린 블루
+ btnUpdate.ForeColor = Color.White;
+ }
}
private void btnCancel_MouseClick(object sender, MouseEventArgs e)
@@ -50,8 +226,53 @@ namespace ControlServer
private void btnUpdate_MouseClick(object sender, MouseEventArgs e)
{
- // 수정
- // 수정 시 DB 저장
+ string displayTime = txtBoxDisplay.Text;
+ string onTime = txtBoxOnYY.Text + ":" + txtBoxOnMM.Text + ":" + txtBoxOnSS.Text;
+ string offTime = txtBoxOffYY.Text + ":" + txtBoxOffMM.Text + ":" + txtBoxOffSS.Text;
+
+ _mainForm.SaveProgramTimesToGrid(_targetIP, displayTime, onTime, offTime);
+
+ string protocol = $"SET_TIME|{displayTime}\n";
+ _mainForm.SendDataToSingleAgent(_targetIP, protocol);
+
+ MessageBox.Show("수정되었습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
+
+ this.Close();
+ }
+
+ private void LoadAndParseFile()
+ {
+ if (!File.Exists(_filePath)) return;
+ string fileContent = File.ReadAllText(_filePath);
+ if (string.IsNullOrWhiteSpace(fileContent)) return;
+
+ ParseAndBindServerData(fileContent);
+ }
+
+ private void ParseAndBindServerData(string rawData)
+ {
+ try
+ {
+ var doc = System.Xml.Linq.XDocument.Parse(rawData);
+ grdDataList.Rows.Clear();
+
+ foreach (var item in doc.Descendants("AuctionTable"))
+ {
+ grdDataList.Rows.Add(
+ item.Element("aucDate")?.Value,
+ item.Element("itemName")?.Value,
+ item.Element("origin")?.Value,
+ item.Element("totalQty")?.Value,
+ item.Element("fixPriceQty")?.Value,
+ item.Element("totalAmt")?.Value,
+ item.Element("fixPriceAmt")?.Value
+ );
+ }
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show("XML 파싱 에러: " + ex.Message, "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/ControlServer/Form3.resx b/ControlServer/Form3.resx
index 8b2ff64..67f8b33 100644
--- a/ControlServer/Form3.resx
+++ b/ControlServer/Form3.resx
@@ -117,4 +117,25 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
+
+ True
+
\ No newline at end of file
diff --git a/ControlServer/Form4.Designer.cs b/ControlServer/Form4.Designer.cs
deleted file mode 100644
index 674169c..0000000
--- a/ControlServer/Form4.Designer.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-namespace ControlServer
-{
- partial class Form4
- {
- ///
- /// Required designer variable.
- ///
- private System.ComponentModel.IContainer components = null;
-
- ///
- /// Clean up any resources being used.
- ///
- /// true if managed resources should be disposed; otherwise, false.
- protected override void Dispose(bool disposing)
- {
- if (disposing && (components != null))
- {
- components.Dispose();
- }
- base.Dispose(disposing);
- }
-
- #region Windows Form Designer generated code
-
- ///
- /// Required method for Designer support - do not modify
- /// the contents of this method with the code editor.
- ///
- private void InitializeComponent()
- {
- DataGridViewCellStyle dataGridViewCellStyle7 = new DataGridViewCellStyle();
- DataGridViewCellStyle dataGridViewCellStyle12 = new DataGridViewCellStyle();
- DataGridViewCellStyle dataGridViewCellStyle8 = new DataGridViewCellStyle();
- DataGridViewCellStyle dataGridViewCellStyle9 = new DataGridViewCellStyle();
- DataGridViewCellStyle dataGridViewCellStyle10 = new DataGridViewCellStyle();
- DataGridViewCellStyle dataGridViewCellStyle11 = new DataGridViewCellStyle();
- grdLog = new DataGridView();
- Col5 = new DataGridViewTextBoxColumn();
- Col0 = new DataGridViewTextBoxColumn();
- Col3 = new DataGridViewTextBoxColumn();
- Col4 = new DataGridViewTextBoxColumn();
- Col6 = new DataGridViewTextBoxColumn();
- Col7 = new DataGridViewTextBoxColumn();
- btnPrev = new Button();
- ((System.ComponentModel.ISupportInitialize)grdLog).BeginInit();
- SuspendLayout();
- //
- // grdLog
- //
- dataGridViewCellStyle7.Alignment = DataGridViewContentAlignment.MiddleCenter;
- dataGridViewCellStyle7.BackColor = SystemColors.ButtonShadow;
- dataGridViewCellStyle7.Font = new Font("맑은 고딕", 9F);
- dataGridViewCellStyle7.ForeColor = SystemColors.WindowText;
- dataGridViewCellStyle7.SelectionBackColor = SystemColors.Highlight;
- dataGridViewCellStyle7.SelectionForeColor = SystemColors.HighlightText;
- dataGridViewCellStyle7.WrapMode = DataGridViewTriState.True;
- grdLog.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle7;
- grdLog.ColumnHeadersHeight = 29;
- grdLog.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
- grdLog.Columns.AddRange(new DataGridViewColumn[] { Col5, Col0, Col3, Col4, Col6, Col7 });
- dataGridViewCellStyle12.Alignment = DataGridViewContentAlignment.MiddleCenter;
- dataGridViewCellStyle12.BackColor = SystemColors.Window;
- dataGridViewCellStyle12.Font = new Font("맑은 고딕", 9F);
- dataGridViewCellStyle12.ForeColor = SystemColors.ControlText;
- dataGridViewCellStyle12.SelectionBackColor = SystemColors.Highlight;
- dataGridViewCellStyle12.SelectionForeColor = SystemColors.HighlightText;
- dataGridViewCellStyle12.WrapMode = DataGridViewTriState.False;
- grdLog.DefaultCellStyle = dataGridViewCellStyle12;
- grdLog.Location = new Point(164, 87);
- grdLog.Name = "grdLog";
- grdLog.RowHeadersWidth = 51;
- grdLog.Size = new Size(1000, 638);
- grdLog.TabIndex = 4;
- //
- // Col5
- //
- dataGridViewCellStyle8.Alignment = DataGridViewContentAlignment.MiddleCenter;
- Col5.DefaultCellStyle = dataGridViewCellStyle8;
- Col5.Frozen = true;
- Col5.HeaderText = "일시";
- Col5.MinimumWidth = 6;
- Col5.Name = "Col5";
- Col5.Width = 220;
- //
- // Col0
- //
- dataGridViewCellStyle9.Alignment = DataGridViewContentAlignment.MiddleCenter;
- Col0.DefaultCellStyle = dataGridViewCellStyle9;
- Col0.Frozen = true;
- Col0.HeaderText = "단말기명";
- Col0.MinimumWidth = 6;
- Col0.Name = "Col0";
- Col0.Width = 170;
- //
- // Col3
- //
- dataGridViewCellStyle10.Alignment = DataGridViewContentAlignment.MiddleCenter;
- Col3.DefaultCellStyle = dataGridViewCellStyle10;
- Col3.Frozen = true;
- Col3.HeaderText = "명령 유형";
- Col3.MinimumWidth = 6;
- Col3.Name = "Col3";
- Col3.Width = 125;
- //
- // Col4
- //
- dataGridViewCellStyle11.Alignment = DataGridViewContentAlignment.MiddleCenter;
- Col4.DefaultCellStyle = dataGridViewCellStyle11;
- Col4.Frozen = true;
- Col4.HeaderText = "수행자";
- Col4.MinimumWidth = 6;
- Col4.Name = "Col4";
- Col4.Width = 125;
- //
- // Col6
- //
- Col6.Frozen = true;
- Col6.HeaderText = "결과";
- Col6.MinimumWidth = 6;
- Col6.Name = "Col6";
- Col6.Width = 125;
- //
- // Col7
- //
- Col7.Frozen = true;
- Col7.HeaderText = "상세내용";
- Col7.MinimumWidth = 6;
- Col7.Name = "Col7";
- Col7.Width = 285;
- //
- // btnPrev
- //
- btnPrev.Font = new Font("맑은 고딕", 10.2F, FontStyle.Bold, GraphicsUnit.Point, 129);
- btnPrev.Location = new Point(164, 12);
- btnPrev.Name = "btnPrev";
- btnPrev.Size = new Size(174, 56);
- btnPrev.TabIndex = 13;
- btnPrev.Text = "이전";
- btnPrev.UseVisualStyleBackColor = true;
- btnPrev.MouseClick += btnPrev_MouseClick;
- //
- // Form4
- //
- AutoScaleDimensions = new SizeF(9F, 20F);
- AutoScaleMode = AutoScaleMode.Font;
- ClientSize = new Size(1357, 737);
- Controls.Add(btnPrev);
- Controls.Add(grdLog);
- Name = "Form4";
- Text = "Form4";
- ((System.ComponentModel.ISupportInitialize)grdLog).EndInit();
- ResumeLayout(false);
- }
-
- #endregion
-
- private DataGridView grdLog;
- private DataGridViewTextBoxColumn Col5;
- private DataGridViewTextBoxColumn Col0;
- private DataGridViewTextBoxColumn Col3;
- private DataGridViewTextBoxColumn Col4;
- private DataGridViewTextBoxColumn Col6;
- private DataGridViewTextBoxColumn Col7;
- private Button btnPrev;
- }
-}
\ No newline at end of file
diff --git a/ControlServer/Form4.cs b/ControlServer/Form4.cs
deleted file mode 100644
index 3745b0a..0000000
--- a/ControlServer/Form4.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.Drawing;
-using System.Text;
-using System.Windows.Forms;
-
-namespace ControlServer
-{
- public partial class Form4 : Form
- {
- public Form4()
- {
- InitializeComponent();
-
- grdLog.RowHeadersVisible = false; // 그리드 자동으로 생성되는 첫 번째 열 제거
- grdLog.AllowUserToAddRows = false; // 새 행 입력용 기본 로우 제거
- grdLog.EnableHeadersVisualStyles = false; // 헤더 기본 테마 사용 안 함
- grdLog.ColumnHeadersDefaultCellStyle.BackColor = Color.LightGray; // 헤더 배경색
- grdLog.ColumnHeadersHeight = 40; // 헤더 높이
- grdLog.RowHeadersVisible = false; // 행 헤더 제거
- grdLog.AllowUserToAddRows = false; // // 행 추가 금지
- grdLog.AllowUserToResizeRows = false; // 행 높이 조절 금지
- grdLog.AllowUserToResizeColumns = false; // 열 너비 조절 금지
- grdLog.ReadOnly = true; // 읽기 전용
- grdLog.ClearSelection();
- }
-
- private void btnPrev_MouseClick(object sender, MouseEventArgs e)
- {
- this.Close();
- }
- }
-}
diff --git a/ControlServer/Properties/Resources.Designer.cs b/ControlServer/Properties/Resources.Designer.cs
index a98024f..41cb486 100644
--- a/ControlServer/Properties/Resources.Designer.cs
+++ b/ControlServer/Properties/Resources.Designer.cs
@@ -69,5 +69,45 @@ namespace ControlServer.Properties {
return ((System.Drawing.Bitmap)(obj));
}
}
+
+ ///
+ /// System.Drawing.Bitmap 형식의 지역화된 리소스를 찾습니다.
+ ///
+ internal static System.Drawing.Bitmap imgCountBox {
+ get {
+ object obj = ResourceManager.GetObject("imgCountBox", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// System.Drawing.Bitmap 형식의 지역화된 리소스를 찾습니다.
+ ///
+ internal static System.Drawing.Bitmap imgCountBox1 {
+ get {
+ object obj = ResourceManager.GetObject("imgCountBox1", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// System.Drawing.Bitmap 형식의 지역화된 리소스를 찾습니다.
+ ///
+ internal static System.Drawing.Bitmap imgDataRegister {
+ get {
+ object obj = ResourceManager.GetObject("imgDataRegister", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// System.Drawing.Bitmap 형식의 지역화된 리소스를 찾습니다.
+ ///
+ internal static System.Drawing.Bitmap imgDataUpdate {
+ get {
+ object obj = ResourceManager.GetObject("imgDataUpdate", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
}
}
diff --git a/ControlServer/Properties/Resources.resx b/ControlServer/Properties/Resources.resx
index dab1ac7..bfad3b4 100644
--- a/ControlServer/Properties/Resources.resx
+++ b/ControlServer/Properties/Resources.resx
@@ -118,7 +118,19 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ ..\Resources\imgDataRegister.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
..\Resources\button-search.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ ..\Resources\imgDataUpdate.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\imgCountBox.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\imgCountBox1.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
\ No newline at end of file
diff --git a/ControlServer/Resources/imgCountBox.png b/ControlServer/Resources/imgCountBox.png
new file mode 100644
index 0000000..c063313
Binary files /dev/null and b/ControlServer/Resources/imgCountBox.png differ
diff --git a/ControlServer/Resources/imgCountBox1.png b/ControlServer/Resources/imgCountBox1.png
new file mode 100644
index 0000000..99407d2
Binary files /dev/null and b/ControlServer/Resources/imgCountBox1.png differ
diff --git a/ControlServer/Resources/imgDataRegister.png b/ControlServer/Resources/imgDataRegister.png
new file mode 100644
index 0000000..ff6c0e6
Binary files /dev/null and b/ControlServer/Resources/imgDataRegister.png differ
diff --git a/ControlServer/Resources/imgDataUpdate.png b/ControlServer/Resources/imgDataUpdate.png
new file mode 100644
index 0000000..452a866
Binary files /dev/null and b/ControlServer/Resources/imgDataUpdate.png differ
diff --git a/ControlServer/RoundedPanel.cs b/ControlServer/RoundedPanel.cs
new file mode 100644
index 0000000..a27bb8e
--- /dev/null
+++ b/ControlServer/RoundedPanel.cs
@@ -0,0 +1,49 @@
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Windows.Forms;
+using System.ComponentModel;
+
+namespace ControlServer
+{
+ public class RoundedPanel : Panel
+ {
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public int CornerRadius { get; set; } = 30;
+
+ public RoundedPanel()
+ {
+ this.DoubleBuffered = true;
+ // 컨트롤의 배경색이 바뀔 때 다시 그리도록 설정
+ this.SetStyle(ControlStyles.AllPaintingInWmPaint |
+ ControlStyles.OptimizedDoubleBuffer |
+ ControlStyles.ResizeRedraw |
+ ControlStyles.UserPaint, true);
+ }
+
+ protected override void OnPaint(PaintEventArgs e)
+ {
+ e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
+
+ // 배경을 투명하게 처리하고 둥근 영역만 남김
+ using (GraphicsPath path = new GraphicsPath())
+ {
+ path.AddArc(0, 0, CornerRadius, CornerRadius, 180, 90);
+ path.AddArc(Width - CornerRadius - 1, 0, CornerRadius, CornerRadius, 270, 90);
+ path.AddArc(Width - CornerRadius - 1, Height - CornerRadius - 1, CornerRadius, CornerRadius, 0, 90);
+ path.AddArc(0, Height - CornerRadius - 1, CornerRadius, CornerRadius, 90, 90);
+ path.CloseAllFigures();
+
+ // 영역(Region)을 설정하여 모서리 바깥쪽을 클릭 방지 및 투명 처리
+ this.Region = new Region(path);
+
+ // 배경 채우기
+ using (SolidBrush brush = new SolidBrush(this.BackColor))
+ {
+ e.Graphics.FillPath(brush, path);
+ }
+ }
+
+ base.OnPaint(e);
+ }
+ }
+}
\ No newline at end of file