704 lines
32 KiB
C#
704 lines
32 KiB
C#
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; // 들어오는 내용을 버퍼에 누적
|
|
|
|
// 전체 데이터가 들어왔는지 확인 (서버가 마지막에 </AuctionRows>를 보낸다고 가정)
|
|
if (buffer.Contains("<AuctionRows>") && buffer.Contains("</AuctionRows>"))
|
|
{
|
|
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. <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();
|
|
}
|
|
}));
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
} |