관리자페이지 개발

This commit is contained in:
hassong
2026-07-03 16:43:53 +09:00
parent 5d6ee943f6
commit 5022eba196
26 changed files with 3940 additions and 758 deletions

View File

@@ -28,8 +28,29 @@
/// </summary>
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;
}
}

View File

@@ -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; // 들어오는 내용을 버퍼에 누적
// 전체 데이터가 들어왔는지 확인 (서버가 마지막에 </AuctionRows>를 보낸다고 가정)
if (buffer.Contains("<AuctionRows>") && buffer.Contains("</AuctionRows>"))
{
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. <AuctionTable> 태그를 찾아서 행 추가
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;
}
}
}

View File

@@ -117,4 +117,25 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="Col0.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Col1.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Col2.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Col3.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Col4.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Col5.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="Col6.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>