using System.Collections.Generic; using System; using System.Drawing; using System.Diagnostics; using System.Data; using System.Collections; using System.Windows.Forms; using System.Linq; using System.Data.Linq; using System.Runtime.InteropServices; using TrendCtrlII; using AR; using System.Windows.Media.Animation; using System.Threading.Tasks; using System.Collections.Concurrent; using System.Diagnostics.Eventing.Reader; using System.Windows.Forms.DataVisualization.Charting; using System.Text.RegularExpressions; using OpenTK.Graphics.ES20; using ScottPlot.WinForms; using ScottPlot.Control; namespace vmsnet { public partial class Frm_trend { ChartListData[] ChartListData = new ChartListData[10]; string SelectedGRPChValue = ""; List totalChlist = new List(); DocumentElement.CHANNELDataTable DTCHLIST; readonly ScottPlot.WinForms.FormsPlot formsPlot1; /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 데이터그리드 행의 전체선택인지 결정하는 변수 선언 */ private bool selALL = true; List LoadFileList = new List(); List LoadCHList = new List(); ScottPlot.Plottables.Crosshair CrossHair; ScottPlot.Plottables.Scatter[] myPlots; int RecordCount = 0; ScottPlot.Plottables.VerticalLine[] CursorLine; ScottPlot.Plottables.AxisLine PlottableBeingDragged = null; List[] myPlotDataX = new List[0]; List[] myPlotDataY = new List[0]; public Frm_trend() { // 디자이너에서 이 호출이 필요합니다. InitializeComponent(); PUB.INIT(out List message); //차트 채널 및 파일데이터 변수 for (int i = 0; i < ChartListData.Length; i++) ChartListData[i] = new ChartListData(); CursorLine = new ScottPlot.Plottables.VerticalLine[2]; formsPlot1 = new ScottPlot.WinForms.FormsPlot() { Dock = DockStyle.Fill }; formsPlot1.MouseDown += FormsPlot1_MouseDown; formsPlot1.MouseUp += FormsPlot1_MouseUp; formsPlot1.MouseMove += FormsPlot1_MouseMove; panel8.Controls.Add(formsPlot1); // 초기 데이터 설정 FormsPlot_Init(formsPlot1); /* 작성자: 이재웅, 작성일: 2024-09-09 */ /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 사용자에 의한 셀수정 차단 */ this.dv_chlist.EditMode = DataGridViewEditMode.EditProgrammatically; } public void Frm_trend_FormClosing(object sender, System.Windows.Forms.FormClosingEventArgs e) { if (this.dv_grp.SelectedRows.Count > 0) { PUB.TREND.tv_selectgroup0 = this.cmb_group.SelectedIndex.ToString(); PUB.TREND.tv_selectgroup = this.bs_grp.Position.ToString(); } } public void Frm_trend_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e) { switch (e.KeyCode) { case Keys.D: if (e.Control) this.formsPlot1.Refresh(); break; case Keys.F10: this.bt_cursor1.PerformClick(); break; case Keys.F11: this.bt_cursor2.PerformClick(); break; } } public void Frm_trend_Load(System.Object sender, System.EventArgs e) { this.Show(); Application.DoEvents(); ////화면좌측의 그룹목록을 갱신 RefreshGroupList(); ////마지막으로 선택한 그룹을 선택해준다. var selidx0 = 0; int.TryParse(PUB.TREND.tv_selectgroup0, out selidx0); if (selidx0 < cmb_group.Items.Count) this.cmb_group.SelectedIndex = selidx0; else this.cmb_group.SelectedIndex = this.cmb_group.Items.Count > 0 ? 0 : -1; var selidx = 0; int.TryParse(PUB.TREND.tv_selectgroup, out selidx); // XMl.Data("trendview", "selectgroup", "0") this.bs_grp.Position = selidx; ////모든채널정보를 가져온다. PUB.smsg("Refresh GroupName"); DTCHLIST = new DocumentElement.CHANNELDataTable();// pub.DS.CHANNEL.Clone(); // DBC.GetTable("Select * from CHANNEL ORDER BY IDX") DTCHLIST.Merge(PUB.DS.CHANNEL); //DTCHLIST.Columns.Add("GRPNAME"); foreach (DocumentElement.CHANNELRow Dr in DTCHLIST.Rows) { Dr.GRPNAME = PUBC.GetGrpName(Dr.GIDX); } DTCHLIST.AcceptChanges(); PUB.smsg(""); toolStripLabel3.Text = $""; //scootplot(); } private ScottPlot.Plottables.AxisLine GetLineUnderMouse(float x, float y) { ScottPlot.CoordinateRect rect = formsPlot1.Plot.GetCoordinateRect(x, y, radius: 10); foreach (var axLine in formsPlot1.Plot.GetPlottables().Reverse()) { if (axLine.IsUnderMouse(rect)) return axLine; } return null; } private void FormsPlot1_MouseDown(object sender, MouseEventArgs e) { var lineUnderMouse = GetLineUnderMouse(e.X, e.Y); if (lineUnderMouse != null) { PlottableBeingDragged = lineUnderMouse; formsPlot1.Interaction.Disable(); // disable panning while dragging } } private void FormsPlot1_MouseUp(object sender, MouseEventArgs e) { PlottableBeingDragged = null; formsPlot1.Interaction.Enable(); // enable panning again formsPlot1.Refresh(); this.UpdateCursorValue(); } private void FormsPlot1_MouseMove(object sender, MouseEventArgs e) { // update cross line ScottPlot.Pixel mousePixel = new ScottPlot.Pixel(e.X, e.Y); ScottPlot.Coordinates mouseCoordinates = formsPlot1.Plot.GetCoordinates(mousePixel); //this.Text = $"X={mouseCoordinates.X:N3}, Y={mouseCoordinates.Y:N3}"; if (CrossHair != null) { CrossHair.Position = mouseCoordinates; var time = DateTime.FromOADate(mouseCoordinates.X); CrossHair.VerticalLine.Text = $"{time:yy-MM-dd\nHH:mm:ss}"; CrossHair.HorizontalLine.Text = $"{mouseCoordinates.Y:N2}v"; formsPlot1.Refresh(); } // this rectangle is the area around the mouse in coordinate units ScottPlot.CoordinateRect rect = formsPlot1.Plot.GetCoordinateRect(e.X, e.Y, radius: 10); // set cursor based on what's beneath the plottable var lineUnderMouse = GetLineUnderMouse(e.X, e.Y); if (PlottableBeingDragged is null) { if (lineUnderMouse is null) Cursor = Cursors.Default; else if (lineUnderMouse.IsDraggable && lineUnderMouse is ScottPlot.Plottables.VerticalLine) Cursor = Cursors.SizeWE; else if (lineUnderMouse.IsDraggable && lineUnderMouse is ScottPlot.Plottables.HorizontalLine) Cursor = Cursors.SizeNS; } else { // update the position of the plottable being dragged if (PlottableBeingDragged is ScottPlot.Plottables.HorizontalLine hl) { hl.Y = rect.VerticalCenter; hl.Text = $"{hl.Y:0.00}v"; } else if (PlottableBeingDragged is ScottPlot.Plottables.VerticalLine vl) { vl.X = rect.HorizontalCenter; var time = DateTime.FromOADate(vl.X); //vl.Text = $"{vl.X:0.00}"; if (lineUnderMouse == CursorLine[0]) vl.Text = $"[C1] {time:yy-MM-dd\nHH:mm:ss}"; else if (lineUnderMouse == CursorLine[1]) vl.Text = $"[C2] {time:yy-MM-dd\nHH:mm:ss}"; else vl.Text = $"{time:yy-MM-dd\nHH:mm:ss}"; } formsPlot1.Refresh(); } } /// /// 그룹목록을 추가합니다. /// /// 표시그룸명(사용자구분추가) /// 표시채널목록(comma) /// 일련번호 /// 그룹명 /// private void AddGroupRow(string name, int idx, string value, string rname) { ////최대idx를 찾아서 그보다 크게한다. var dr = this.dS1.group.NewgroupRow(); // Me.DataSet1.Tables("group").NewRow dr.cname = name; dr.value = value; dr.idx = idx; dr.rname = rname; dS1.group.AddgroupRow(dr); dS1.group.AcceptChanges(); } /// /// 표시그룹목록을 갱신합니다. 좌측하단 /// /// private void RefreshGroupList() { ////상단 그룹목록 //Dim sql As String = "Select * from GRP where USE=1 order by TITLE" var DT = PUB.DS.GRP.Where(t => t.USE == 1).OrderBy(t => t.TITLE).ToList();//.CopyToDataTable();//.Select("use=1", "title") as DocumentElement.GRPRow[]; // DBC.GetTable(sql) this.cmb_group.DataSource = DT; this.cmb_group.DisplayMember = "TITLE"; this.cmb_group.ValueMember = "IDX"; if (DT.Count() > 0) { this.cmb_group.SelectedIndex = 0; } ////현재의 그룹목록을 삭제 dS1.group.Clear(); ////사용자그룹추가 //sql = "select * from VIEWGROUP order by TITLE,IDx" foreach (DocumentElement.VIEWGROUPRow dr in PUB.DS.VIEWGROUP.Select("", "title,idx")) // DBC.GetTable(sql).Rows { string v = dr.VAL.Trim(); if (string.IsNullOrEmpty(v)) { continue; } string newgrpname = "[사용자] " + dr.TITLE; AddGroupRow(newgrpname, dr.IDX, v, dr.GNAME); } ////시스템그룹추가 // sql = "select * from GRP where USE=1" totalChlist.Clear(); foreach (DocumentElement.GRPRow Dr in PUB.DS.GRP.Select("use=1")) // DBC.GetTable(sql).Rows { ////이그룹을 사용하는 채널중 사용채널을 찾는다. string v = ""; foreach (DocumentElement.CHANNELRow Dr2 in PUB.DS.CHANNEL.Select("gidx=" + Dr.IDX.ToString() + " and enable=1", "idx")) // DBC.GetTable("select * from CHANNEL where GIDX=" & Dr("IDX") & " AND ENABLE=1 ORDER BY IDX").Rows { v += System.Convert.ToString((string.IsNullOrEmpty(v) ? "" : ",") + Dr2.IDX.ToString()); if (totalChlist.Contains(Dr2.IDX) == false) { totalChlist.Add(Dr2.IDX); } } if (!string.IsNullOrEmpty(v)) { //AddGroupRow("[시스템]" + Dr.TITLE, Dr.IDX, v, Dr.TITLE); } } short AllIDX = (short)dS1.group.Rows.Count; ////전체보기메뉴의 인덱스 AddGroupRow("모든채널표시", -1, "", "전체"); } #region 우측 채널 목록 ////우측채널목록을 갱신합니다. private void RefreshChannelList() { this.dS1.channel.Rows.Clear(); this.dS1.channel.AcceptChanges(); //파일목록가져온다 GrabFilelist(); foreach (var charr in this.ChartListData) { foreach (var ch in charr.ChannelList) { //전체채널목록에 포함되어있는 대상중 채널목록에 존재하지 않는 경우에만 처리한다. if (DTCHLIST.Where(t => t.IDX == ch).Any() && this.dS1.channel.Where(t => t.idx == ch).Any() == false) { //전체채널정보에서 데이터 수집 var CHDataRow = DTCHLIST.Rows[ch - 1] as DocumentElement.CHANNELRow; if (CHDataRow == null) continue; //채널목록에도 추가한다. var activeCount = this.dS1.channel.Where(t => t.use == true).Count(); var Dr = this.dS1.channel.NewchannelRow(); Dr.use = activeCount < PUB.CONFIG.MaxChCount; Dr.cname = CHDataRow.TITLE; Dr.idx = ch; Dr.cc = CHDataRow.COLOR; Dr.tidx = this.dS1.channel.Rows.Count; //// chinof 의 index번호를 설정해야한다. this.dS1.channel.Rows.Add(Dr); } } } this.dS1.channel.AcceptChanges(); } #endregion void GrabFilelist() { /* 주석날짜: 2024-09-20 | 주석내용: VIEWGROUP.xml 파일내용 | 주석자: 이재웅 * * * 9 * EL2500A * GRP(1...10) * 1,2,3,4,5,6,7,8,9,10 * * ************************************** * * DRGRP.idx = 9 * DRGRP.mame = "EL2500A" * DRGRP.cname = "[사용자] GRP(1...10)" * DRGRP.value = "1,2,3,4,5,6,7,8,9,10" */ var sttdate = PUB.TREND.graph_time_start; var enddate = PUB.TREND.graph_time_end; //각 파일 그룹별 대상 파일을 수집합니다. Parallel.For(0, this.ChartListData.Length - 1, i => { var DataFileName = $"DATAB{i + 1}"; if (ChartListData[i].ChannelList.Any() == true) this.ChartListData[i].FileList = PUB.DB.GetfileS(DataFileName, sttdate, enddate); else this.ChartListData[i].FileList = new List(); Console.WriteLine($"Processing index {i} on thread {Task.CurrentId}"); }); } private void FormsPlot_Init(ScottPlot.WinForms.FormsPlot plot) { plot.Plot.Clear(); // 초기 데이터 설정 plot.Plot.Add.Palette = new ScottPlot.Palettes.Penumbra(); // change figure colors plot.Plot.FigureBackground.Color = ScottPlot.Color.FromHex("#181818"); plot.Plot.DataBackground.Color = ScottPlot.Color.FromHex("#1f1f1f"); // change axis and grid colors plot.Plot.Axes.Color(ScottPlot.Color.FromHex("#d7d7d7")); plot.Plot.Grid.MajorLineColor = ScottPlot.Color.FromHex("#404040"); // change legend colors plot.Plot.Legend.BackgroundColor = ScottPlot.Color.FromHex("#404040"); plot.Plot.Legend.FontColor = ScottPlot.Color.FromHex("#d7d7d7"); plot.Plot.Legend.OutlineColor = ScottPlot.Color.FromHex("#d7d7d7"); } /// /// 데이터를 조회합니다. /// /// 파일목록을 다시 갱신합니다. /// private void ShowData(bool AllClear = true) { //return; Console.WriteLine("Enter : ShowData"); this.Text = $"TrendViewer ({cmb_group.Text})"; this.lb_selgroup.Text = $"[{cmb_group.Text}]"; DateTime SDTotal = DateTime.Now; var chcount2 = this.dS1.channel.Rows.Count; if (chcount2 == 0) { UTIL.MsgE($"선택된 채널목록이 없습니다\n채널그룹이 손상되었거나 다시 선택하세요"); return; } GrabFilelist(); TimeSpan Tsing = DateTime.Now - SDTotal; Console.WriteLine($"[ShowData] Title And Ch : {Tsing.TotalMilliseconds}ms"); DateTime fsd = DateTime.Now; var FileCount = ChartListData.Sum(t => t.FileList.Count); Tsing = DateTime.Now - SDTotal; Console.WriteLine($"[ShowData] {FileCount} Files Checked : {Tsing.TotalMilliseconds}"); TimeSpan fts = DateTime.Now - fsd; lb_filesearchtime.Text = $"[Search : {fts.TotalMilliseconds}ms]"; ////Record Count Check var avdate = PUB.DB.GetAvailableDates(); //변수선언 이동 if (FileCount == 0) { Console.WriteLine("ShowData : FileCount 0 Refresh"); PUB.smsg(""); //var avdate = PUB.DB.GetAvailableDates(); //전위치 if (avdate.Any() == false) { UTIL.MsgE("지정된 폴더내에 저장된 자료가 없습니다"); } else { UTIL.MsgI("기간내 조회된 자료가 없습니다\n\n조회 가능 범위\n\n" + $"{avdate.First().Year}-{avdate.First().Month}~" + $"{avdate.Last().Year}-{avdate.Last().Month}"); } return; } Tsing = DateTime.Now - SDTotal; Console.WriteLine($"[ShowData] Set Date Area : {Tsing.TotalMilliseconds}"); //채널수만큼 데이터버퍼를 확보한다. var maxchno = this.dS1.channel.Max(t => t.idx); //idx는 실제 채널번호가 들어있다(1~ this.myPlotDataX = new List[maxchno]; this.myPlotDataY = new List[maxchno]; // 채널 개수만큼 Scatter 그래프 생성 this.myPlots = new ScottPlot.Plottables.Scatter[maxchno]; //커서삭제 this.CursorLine[0] = null; this.CursorLine[1] = null; ////데이터를 모두 초기화하는경우 if (AllClear) { LoadFileList.Clear(); LoadCHList.Clear(); Tsing = DateTime.Now - SDTotal; Console.WriteLine($"[ShowData] Clear Data : {Tsing.TotalMilliseconds}"); } //커서데이터 삭제 foreach (DS1.channelRow dr in this.dS1.channel) { dr.c1 = ""; dr.c2 = ""; } this.dS1.channel.AcceptChanges(); ////추가된채널목록을 가진다. DateTime STime = DateTime.Now; int totalchcount = this.dS1.channel.Rows.Count; if (totalchcount > PUB.CONFIG.MaxChCount) { UTIL.MsgE($"채널 수({totalchcount})가 {PUB.CONFIG.MaxChCount}을 초과 합니다. [{PUB.CONFIG.MaxChCount}]이하로 사용자 채널을 생성하여 조회 하세요"); return; } /* 주석날짜: 2024-09-20 | 주석자: 이재웅 * * ParseChDataToChart() : 채널데이터를 차트데이터로 변환 */ //대상파일의 자료를 읽어와서 차트데이터를 구축한다. RecordCount = 0; Parallel.For(0, this.ChartListData.Length, i => { ParseChDataToChart(i, ChartListData[i], DTCHLIST); }); Tsing = DateTime.Now - SDTotal; Console.WriteLine($"[ShowData] Pase Data Complete : {Tsing.TotalMilliseconds}"); var sttdate = PUB.TREND.graph_time_start; var enddate = PUB.TREND.graph_time_end; TimeSpan term = DateTime.Now - STime; ////조회영역 및 쿼리시간표시 this.lb_Area.Text = "[ " + sttdate.ToString("yy-MM-dd HH:mm:ss") + "~" + enddate.ToString("yy-MM-dd HH:mm:ss") + " ]"; this.lb_querytime.Text = $"[Loading : {term.TotalMilliseconds:#0.0}ms ]"; ////SET OK STime = DateTime.Now; Console.WriteLine("ShowData : Chart Refresh"); term = DateTime.Now - STime; this.lb_charttime.Text = $"[Chart : {term.TotalMilliseconds:#0.0}ms ]"; Tsing = DateTime.Now - SDTotal; //Console.WriteLine($"[ShowData] Chart Refresh : {Tsing.TotalMilliseconds:#0.0}"); TimeSpan Tstotal = DateTime.Now - SDTotal; this.lb_totaltime.Text = $"[total : {Tstotal.TotalMilliseconds:#0.0}ms]"; Tsing = DateTime.Now - SDTotal; //Console.WriteLine($"[ShowData] Complete : {Tsing.TotalMilliseconds:#0.0}"); // 초기 데이터 생성 //this.formsPlot1.Plot.Clear(); FormsPlot_Init(formsPlot1); #region 이전 코드 주석처리 //formsPlot1.Plot.Add.Palette = new ScottPlot.Palettes.Penumbra(); //// change figure colors //formsPlot1.Plot.FigureBackground.Color = ScottPlot.Color.FromHex("#181818"); //formsPlot1.Plot.DataBackground.Color = ScottPlot.Color.FromHex("#1f1f1f"); //// change axis and grid colors //formsPlot1.Plot.Axes.Color(ScottPlot.Color.FromHex("#d7d7d7")); //formsPlot1.Plot.Grid.MajorLineColor = ScottPlot.Color.FromHex("#404040"); //// change legend colors //formsPlot1.Plot.Legend.BackgroundColor = ScottPlot.Color.FromHex("#404040"); //formsPlot1.Plot.Legend.FontColor = ScottPlot.Color.FromHex("#d7d7d7"); //formsPlot1.Plot.Legend.OutlineColor = ScottPlot.Color.FromHex("#d7d7d7"); #endregion /* 주석날짜: 2024-09-20 | 주석내용: 채널별 데이터 그리기 | 주석자: 이재웅 * * xs : x축 List[] 형식의 데이터 * ys : y축 List[] 형식의 데이터 * myScatter : x,y 좌표를 가지는 ScottPlot.Plottables.Scatter 형식의 데이터 * * 명시적선언時: List[] myPlotDataX * 명시적선언時: List[] myPlotDataY * 명시적선언時: ScottPlot.Plottables.Scatter myScatter */ for (int i = 0; i < this.myPlots.Length; i++) { var xs = myPlotDataX[i]; if (xs == null || xs.Any() == false) continue; var ys = myPlotDataY[i]; var myScatter = formsPlot1.Plot.Add.Scatter(xs, ys); //if (i > 2) break; //채널정보에서 색상을 가져온다. var chinfo = this.dS1.channel.Where(t => t.idx == i + 1).FirstOrDefault(); //idx에 실제 채널정보가 들어있으니 -1 해야함 if (chinfo != null) { var linecolor = Color.FromArgb(chinfo.cc); myScatter.LineColor = new ScottPlot.Color(linecolor.R, linecolor.G, linecolor.B); } else myScatter.LineColor = ScottPlot.Colors.Red; myScatter.Color = myScatter.LineColor; /* 작성자: 이재웅, 작성일: 2024-11-27, 내용: 변경한 셀이름으로 차트범례 표시 */ myScatter.LegendText = chinfo.ItemArray[1].ToString(); //$"CH{i + 1}"; this.myPlots[i] = myScatter; //visible 변경 및 데이터 접근을 위해 변수에 따로 저장해둔다. } formsPlot1.Plot.Axes.DateTimeTicksBottom(); //1번항목을 가져와서 x,y축 range 값 설정 var plot = this.myPlots.Where(t => t != null).FirstOrDefault(); if (plot != null) { // 파일이 있더라도 해당하는 데이터가 없을때 Error 발생 var ymin = plot.Data.GetLimitsY().Min; var ymax = plot.Data.GetLimitsY().Max; var xmin = plot.Data.GetLimitsX().Min; var xmax = plot.Data.GetLimitsX().Max; var maxdate = DateTime.FromOADate(xmin); var mindate = DateTime.FromOADate(xmax); } this.lb_datatcnt.Text = $"[ {FileCount} Files / {RecordCount} Records ]"; CrossHair = formsPlot1.Plot.Add.Crosshair(0, 0); CrossHair.TextColor = ScottPlot.Colors.Red; CrossHair.TextBackgroundColor = CrossHair.HorizontalLine.Color; //축설정 formsPlot1.Plot.Axes.AutoScaleX(); if (PUB.TREND.y_scale_auto) formsPlot1.Plot.Axes.AutoScaleY(); else formsPlot1.Plot.Axes.SetLimitsY(PUB.TREND.graph_y_start, PUB.TREND.graph_y_end); formsPlot1.Plot.YLabel("VOLTAGE"); //X축 바닥 여백 변경 formsPlot1.Plot.Axes.Bottom.MinimumSize = PUB.TREND.graph_bottom_minsize; //십자선추가 CrossHair = formsPlot1.Plot.Add.Crosshair(0, 0); CrossHair.TextColor = ScottPlot.Colors.White; CrossHair.TextBackgroundColor = CrossHair.HorizontalLine.Color; //X축 날짜시간형태로 표시 formsPlot1.Plot.RenderManager.RenderStarting += (s1, e1) => { ScottPlot.Tick[] ticks = formsPlot1.Plot.Axes.Bottom.TickGenerator.Ticks; for (int i = 0; i < ticks.Length; i++) { DateTime dt = DateTime.FromOADate(ticks[i].Position); string label = $"{dt:yy-MM-dd\nHH:mm:ss}"; ticks[i] = new ScottPlot.Tick(ticks[i].Position, label); } }; // the Style object contains helper methods to style many items at once formsPlot1.Plot.Axes.Color(ScottPlot.Color.FromHex("#a0acb5")); formsPlot1.Refresh(); if (RecordCount == 0) { string msg = $"[{sttdate.ToString("yyyy-MM-dd")} ~ {enddate.ToString("yyyy-MM-dd")}] : 데이터가 존재하지 않습니다."; PUB.log.Add(msg); UTIL.MsgE(msg); return; } } private void ParseChDataToChart(int idx, ChartListData chlist, DocumentElement.CHANNELDataTable DTCHList) { // 지정한 날짜구간 내의 파일들을 읽음 (채널데이터 → 차트데이터) //데이터가 없는 경우 빠져 나감 if (chlist.ChannelList.Any() == false || chlist.FileList.Any() == false) { if (chlist.ChannelList.Any() || chlist.FileList.Any()) PUB.log.Add($"Load Skip Ch:{chlist.ChannelList.Count},file:{chlist.FileList.Count}"); return; } //지정된 기간의 데이터를 취해야 하므로 날짜 정보를 가져온다. long SDtime = PUB.TREND.graph_time_start.ToFileTime();// SttDate.ToFileTime(); long EDtime = PUB.TREND.graph_time_end.ToFileTime();// EndDate.ToFileTime(); //속해있는채널정보를 가져온다. //대상 파일 전체를 확인하여, 지정된 기간내라면 자료를 수집힌다. foreach (string file in chlist.FileList) { ////이파일명이 로딩되었다면 불러오지 않는다. if (LoadFileList.Contains(file) == true) continue;// TrendCtrl1.Files.IndexOf(file) == -1) else LoadFileList.Add(file); //불러온기록에 추가 ////해당파일의 지정시간내의 데이터를 모두 읽어온다. using (var FS = new System.IO.FileStream(file, System.IO.FileMode.Open)) using (var SR = new System.IO.StreamReader(FS, System.Text.Encoding.Default)) while (SR.Peek() != 0) { string Line = SR.ReadLine(); if (Line == null) break; //null값이 오는 경우가 있다, 오류 혹은 파일끝에서 발생되었음, 더이상 처리하지 않는다. if (Line.Trim() == "") continue; //빈 줄은 무시한다. string[] buf = Line.Split('\t'); //각 데이터는 탭으로 분리 되어있다. if (buf.Length < 8 || buf[1].ToUpper() == "TIME") continue; //제목줄 처리하지 않는다. if (long.TryParse(buf[1], out long TIme) == false) continue; //1번항목은 시간이고, 그 이후에 채널 데이터가 들어있다. ////속해있는 채널정보를 모두 가져온다. foreach (var c in chlist.ChannelList) ////채널목록을 이용하여 데이터를 가져온다. { //채널정보 확인 string GRP_NAME, GRP_TITLE; Color GRP_COLOR; if (LoadCHList.Contains(c) == false) { var CHDataRow = DTCHList.Where(t => t.IDX == c).FirstOrDefault(); if (CHDataRow == null) continue; //데이터가없다면 처리하지 않는다. else LoadCHList.Add(c); GRP_NAME = CHDataRow.GRPNAME; GRP_TITLE = CHDataRow.TITLE; GRP_COLOR = Color.FromArgb(CHDataRow.COLOR); } else { GRP_NAME = ""; GRP_TITLE = ""; GRP_COLOR = Color.Black; } //각 파일당 160개씩 저장되어있으므로 채널번호를 통해서 파일의 위치를 파악해야한다. var chIndexFromFile = PUB.GetChannelIndexFromFileBuffer(c); //숫자값에 문제가 있다면 처리하지 않는다.(0번버퍼는 항상 blank 이기 때문에 인덱스+1해야 데이터인덱스가 맞다) if ((chIndexFromFile + 1) >= buf.Length || float.TryParse(buf[chIndexFromFile + 1], out float valueS) == false) continue; //Decpos 데이터가 있다면 적용한다 if (PUB.CONFIG.datadiv != 0 && PUB.CONFIG.datadiv != 1) valueS = (valueS / PUB.CONFIG.datadiv); //데이터추가 //TrendCtrl1.AddData(TIme, c, valueS, GRP_NAME, GRP_TITLE, GRP_COLOR); if (c > myPlotDataX.GetUpperBound(0)) { Array.Resize(ref myPlotDataX, c); Array.Resize(ref myPlotDataY, c); } if (myPlotDataX[c - 1] == null) { myPlotDataX[c - 1] = new List(); myPlotDataY[c - 1] = new List(); } myPlotDataX[c - 1].Add(DateTime.FromFileTime(TIme)); myPlotDataY[c - 1].Add(valueS); RecordCount += 1; } } } } public void LinkLabel5_LinkClicked(System.Object sender, System.Windows.Forms.LinkLabelLinkClickedEventArgs e) { addnewGroup(); } private void SaveViewGroup() { string[] BackTables = new string[] { "VIEWGROUP" }; foreach (string tabname in BackTables) { PUB.DS.Tables[tabname].AcceptChanges(); var file = System.IO.Path.Combine(PUB.CONFIG.GetDatabasePath(), "Database", "Config", $"{tabname}.xml"); var fi = new System.IO.FileInfo(file); if (fi.Directory.Exists == false) fi.Directory.Create(); PUB.DS.Tables[tabname].WriteXml(fi.FullName, XmlWriteMode.IgnoreSchema); } } private void addnewGroup() { ////전체채널목록을 생성한다. var group_name = this.cmb_group.Text; var drGRP = PUB.DS.GRP.Where(t => t.TITLE.Equals(group_name)).FirstOrDefault(); if (drGRP == null) return; //그룹에 속한 채널 데이터 var chlist = PUB.DS.CHANNEL.Where(t => t.GIDX == drGRP.IDX).OrderBy(t => t.IDX); //var chlistdata = chlist.Where(t => t.ENABLE == 1).Select(t => t.IDX).ToList(); //var chinfos = chlistdata.Select(t => new CChinfo { Show = false, TITLE = $"#{t:0000}", Idx = (ushort)t }).ToList(); /* 작성자: 이재웅, 작성일: 2024-12-04, 작성내용: 채널선택 화면에 변경된 형식의 채널명 표시를 위해 [ex. #A001] */ var chlistdata = chlist.Where(t => t.ENABLE == 1).Select(t => new { t.IDX, t.TITLE }).ToList(); var chinfos = chlistdata.Select(t => new CChinfo { Show = false, TITLE = $"{t.TITLE}", Idx = (ushort)t.IDX}).ToList(); List chnolist = new List(); //using (var f = new Frm_SelectCH(titles)) using (var f = new Frm_SelectCH(chinfos.ToArray())) if (f.ShowDialog() == DialogResult.OK) { foreach (ListViewItem item in f.CheckedListBox1.CheckedItems) chnolist.Add(int.Parse(item.Tag.ToString())); } //선택된 채널 갯수 확인 if (chnolist.Any() == false || chnolist.Count > PUB.CONFIG.MaxChCount) { UTIL.MsgE("채널 선택은 (최소 1개 부터 {PUB.CONFIG.MaxChCount}개)까지 가능합니다"); return; } //최종저장 그룹명 var name = UTIL.InputBox($"그룹명을 입력하세요\n\n그룹생성({chnolist.Count}건)", $"GRP({chnolist.Min()}...{chnolist.Max()})"); if (name.Item1 == false || name.Item2.isEmpty()) return; group_name = name.Item2.Replace("'", ""); var value = string.Join(",", chnolist); short rlt_maxid = PUBC.AddviewGroup(group_name, value, this.cmb_group.Text); if (rlt_maxid != -1) { AddGroupRow("[사용자] " + group_name, rlt_maxid, value, this.cmb_group.Text); SaveViewGroup(); } } public void LinkLabel4_LinkClicked(System.Object sender, System.Windows.Forms.LinkLabelLinkClickedEventArgs e) { DelteGroup(); } private void DelteGroup() { var drv = this.bs_grp.Current as DataRowView; if (drv == null) return; var dr = drv.Row as DS1.groupRow; if (dr.cname.IndexOf("사용자") == -1) { UTIL.MsgE("사용자 그룹만 삭제가 가능합니다"); return; } if (UTIL.MsgQ($"선택된 사용자 그룹을 삭제하시겠습니까?\n\n{dr.cname}") != DialogResult.Yes) return; if (PUBC.DeleteViewGroup(dr.idx, this.cmb_group.Text)) { UTIL.MsgI("데이터가 삭제되었습니다"); } else { UTIL.MsgI("삭제가 실패되었습니다"); } drv.Delete(); dS1.group.AcceptChanges(); SaveViewGroup(); } /// /// 우측 그리드뷰에 표시되는 커서 값을 표시한다. /// private void UpdateCursorValue() { //미사용채널데이터의 커서값 삭제 this.dS1.channel.Where(t => t.IsuseNull() == false && t.use == false).ToList().ForEach(t => { t.c1 = t.c2 = ""; }); UpdateCursorValueData(0); UpdateCursorValueData(1); dv_chlist.AutoResizeColumns(); } void UpdateCursorValueData(int idx) { var c1 = this.CursorLine[idx]; if (c1 != null) { var time = DateTime.FromOADate(c1.X); foreach (DS1.channelRow dr in this.dS1.channel.Rows) { var chno = dr.idx; //그래프 플롯이 준비되어있지 않는 경우 if (chno > myPlotDataX.Length || this.myPlots[chno - 1] == null) { if (idx == 0) dr.c1 = "ERR"; else dr.c2 = "ERR"; continue; } var plot = this.myPlots[chno - 1]; var times = this.myPlotDataX[chno - 1]; //해당 데이터 포인트에 값이 있다. var samedate = times.Where(t => t == time).FirstOrDefault(); if (samedate != default(DateTime)) { var index = times.IndexOf(samedate); var volt1 = myPlotDataY[chno][index]; if (idx == 0) dr.c1 = $"{volt1:#0.00}"; else dr.c2 = $"{volt1:#0.00}"; ; continue; } //가장자리 데이터 확인 var leftdata = times.Where(t => t < time).LastOrDefault(); var rightdata = times.Where(t => t > time).FirstOrDefault(); if (leftdata == null || rightdata == null) { if (idx == 0) dr.c1 = "ERR"; else dr.c2 = "ERR"; continue; } //시간내의 데이터를 찾는다 var indexL = times.IndexOf(leftdata); var indexR = times.IndexOf(rightdata); if (indexL == -1 || indexR == -1) { if (idx == 0) dr.c1 = $"ERR"; else dr.c2 = $"ERR"; continue; } var voltL = myPlotDataY[chno - 1][indexL]; var voltR = myPlotDataY[chno - 1][indexR]; /* 작성자: 이재웅, 작성일: 2024-09-26, 작성내용: volt 차이값 수식 변경 */ // [이전내용] : var diffvolt = Math.Abs(voltL - voltR); var diffvolt = (voltR - voltL); if (diffvolt == 0) //Y축 변화가 없다면 그 중간의 데이터는 동일하다 { if (idx == 0) dr.c1 = $"{voltL:#0.00}"; else dr.c2 = $"{voltL:#0.00}"; continue; } //X축이 변화량의 어디쯤에 있는지 확인 var XTotal = (rightdata - leftdata).TotalSeconds; var xPosition = (time - leftdata).TotalSeconds / XTotal; var volt = voltL + (diffvolt * xPosition); if (idx == 0) dr.c1 = $"{volt:#0.00}"; else dr.c2 = $"{volt:#0.00}"; } } } private void Ch_AllOrNothing(bool sel) { /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 데이터그리드 데이터 All/Nothing 함수 */ if (this.myPlots == null || this.myPlots.Any() == false) return; //null check PUB.smsg("채널정보를 변경하는중"); this.myPlots.ToList().ForEach(t => t.IsVisible = sel); this.dS1.channel.ToList().ForEach(t => { t.use = sel; if (sel == false) { t.c1 = t.c2 = ""; } }); this.dS1.channel.AcceptChanges(); this.formsPlot1.Refresh(); PUB.smsg(""); } private void dv_chlist_CellContentClick(object sender, DataGridViewCellEventArgs e) { /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 데이터그리드 데이터 행 관련 처리 */ if (e.RowIndex < 0) { // 데이터그리드 Title 행을 클릭 if (e.ColumnIndex == 0) { // checkbox 컬럼을 클릭 selALL = !selALL; Ch_AllOrNothing(selALL); if (selALL) UpdateCursorValue(); // 전체표시 조건일때 실행 return; } else return; } else // 데이터그리드 Data 행의 checkbox 컬럼을 클릭 if (e.ColumnIndex != 0) return; this.dv_chlist.EndEdit(); this.dv_chlist.SuspendLayout(); var Drv = this.bs_Channel.Current as DataRowView; var dr = Drv.Row as DS1.channelRow; if (Drv == null) return; //해당채널의 표시상태를 반전한다. var ch = dr.idx; var plot = this.myPlots[ch - 1]; dr.use = plot.IsVisible = !plot.IsVisible; if (dr.use == false) { dr.c1 = dr.c2 = string.Empty; } dr.EndEdit(); this.formsPlot1.Refresh(); UpdateCursorValue(); this.dv_chlist.ResumeLayout(); } private void dv_chlist_MouseLeave(object sender, EventArgs e) { /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 데이터그리드를 벗어나면 Cursors.Default 처리 */ this.Cursor = Cursors.Default; } private void dv_chlist_MouseMove(object sender, MouseEventArgs e) { /* 작성자: 이재웅, 작성일: 2024-09-26, 내용: 첫번째 checkbox 컬럼일때 Cursors.Hand 처리 */ // 마우스 위치의 HitTest 호출 DataGridView.HitTestInfo hitTestInfo = dv_chlist.HitTest(e.X, e.Y); // 행과 열의 인덱스를 가져옴 //int rowIndex = hitTestInfo.RowIndex; int columnIndex = hitTestInfo.ColumnIndex; if (columnIndex == 0) this.Cursor = Cursors.Hand; else this.Cursor = Cursors.Default; } public void DataGridView1_CellFormatting(object sender, System.Windows.Forms.DataGridViewCellFormattingEventArgs e) { var dr = this.dS1.channel.Rows[e.RowIndex] as DS1.channelRow; if (dr == null) return; this.dv_chlist.Rows[e.RowIndex].Cells[1].Style.BackColor = Color.FromArgb(dr.cc); if (dr.use) this.dv_chlist.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.White; else this.dv_chlist.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.LightGray; } public void ToolStripMenuItem1_Click(System.Object sender, System.EventArgs e) { addnewGroup(); } public void ToolStripMenuItem2_Click(System.Object sender, System.EventArgs e) { DelteGroup(); } public void PrintDocument1_PrintPage(System.Object sender, System.Drawing.Printing.PrintPageEventArgs e) { Graphics G = e.Graphics; var pagesize = new SizeF(PrintDocument1.DefaultPageSettings.PaperSize.Height, PrintDocument1.DefaultPageSettings.PaperSize.Width); ////리포트제목표시 var title = "Historical Trend Display Report"; var titlefont = new Font("나눔고딕", 30, FontStyle.Bold); var fontsize = G.MeasureString(title, titlefont); var Y = 50f; var X = (float)((pagesize.Width - fontsize.Width) / 2); G.DrawString(title, titlefont, Brushes.Black, X, Y); Y = Y + fontsize.Height + 70; int bottommargin = 50; var WindowRect = new RectangleF(30, Y, (float)(pagesize.Width * 0.7), pagesize.Height - Y - bottommargin); var ChartRect = new RectangleF(WindowRect.Left, WindowRect.Top, WindowRect.Width, WindowRect.Height); var 상호명 = PUB.CONFIG.sangho.Trim(); var 상호font = new Font("나눔고딕", 18, FontStyle.Bold); var 상호size = G.MeasureString(상호명, 상호font); G.DrawString(상호명, 상호font, Brushes.Black, WindowRect.Left, WindowRect.Top - 상호size.Height - 10); title = "C1"; fontsize = G.MeasureString(title, this.Font); var valuestr = "00.025"; var valuefontsizew = G.MeasureString(valuestr, this.Font).Width * 1.1F; var valuefontsizeh = G.MeasureString(valuestr, this.Font).Height; var chtitle = new Rectangle(System.Convert.ToInt32(WindowRect.Left + WindowRect.Width + 20), System.Convert.ToInt32(WindowRect.Top), System.Convert.ToInt32(pagesize.Width - WindowRect.Width - 150), System.Convert.ToInt32(fontsize.Height * 2)); ////제목영역 var ChRect = new RectangleF(chtitle.Left, chtitle.Top + chtitle.Height, chtitle.Width, pagesize.Height - (chtitle.Top + chtitle.Height) - bottommargin); ////본문영역 var chinforect = new Rectangle(chtitle.Left, chtitle.Top, chtitle.Width, System.Convert.ToInt32(chtitle.Height + ChRect.Height)); ////총영역 var chtitlewidth = ChRect.Width - (valuefontsizew * 2); ////채널정보를 표시하는 영역의 너비 var chc1valueleft = chinforect.Left + chtitlewidth; var chc2valueleft = chc1valueleft + valuefontsizew; //차트영역에 이미지를 그린다. G.DrawRectangle(Pens.Black, ChartRect.X, ChartRect.Y, ChartRect.Width, ChartRect.Height); using (var img = this.formsPlot1.Plot.GetImage((int)ChartRect.Width, (int)ChartRect.Height)) G.DrawImage(img.GetBitmap(), ChartRect.X, ChartRect.Y, ChartRect.Width, ChartRect.Height); var displayfont = new Font("나눔고딕", 10, FontStyle.Bold); var displayfont2 = new Font("나눔고딕", 10); ////채널정보칸 표시 (바로 이미지를 복제한다?) 제목표시 G.DrawRectangle(Pens.Gray, chtitle); G.DrawString("Cell", displayfont, Brushes.Black, chtitle.Left + 5, (float)(chtitle.Top + (chtitle.Height - valuefontsizeh) / 2)); G.DrawString("C1", displayfont, Brushes.Black, (float)(chc1valueleft + (valuefontsizew - fontsize.Width) / 2), (float)(chtitle.Top + (chtitle.Height - valuefontsizeh) / 2)); G.DrawString("C2", displayfont, Brushes.Black, (float)(chc2valueleft + (valuefontsizew - fontsize.Width) / 2), (float)(chtitle.Top + (chtitle.Height - valuefontsizeh) / 2)); ////세로줄그리기 G.DrawLine(Pens.Gray, chc1valueleft, chinforect.Top, chc1valueleft, chinforect.Top + chinforect.Height); G.DrawLine(Pens.Gray, chc2valueleft, chinforect.Top, chc2valueleft, chinforect.Top + chinforect.Height); //G.DrawRectangle(Pens.Red, chinforect) ////체크된 채널의 목록값을 보여준다. 그리고 c1,c2값을 보여준다. var fsize = G.MeasureString("#11", this.Font); var gridheight = ChRect.Height / 25; var idx = (short)0; Y = ChRect.Top + 10; X = ChRect.Left + 10; var gridcnt = (int)(Math.Floor((decimal)(ChRect.Height / gridheight))); for (int i = 0; i <= gridcnt + 1; i++) { float newy = ChRect.Top + (i * gridheight); G.DrawLine(Pens.Gray, ChRect.Left, newy, ChRect.Left + ChRect.Width, newy); } foreach (var ch in this.dS1.channel.Where(t => t.use)) { fsize = G.MeasureString(ch.cname, displayfont2); Y = (float)(ChRect.Top + (idx * gridheight) + ((gridheight - fsize.Height) / 2)); ////지정역역의 색상을 표시함 RectangleF ColorRect = new RectangleF(X - 5, Y, chc1valueleft - X - 5, fsize.Height); G.FillRectangle(new SolidBrush(Color.FromArgb(ch.cc)), ColorRect); G.DrawString(ch.cname, displayfont2, Brushes.Black, X, Y); G.DrawString(ch.c1, displayfont2, Brushes.Black, chc1valueleft + 5, Y); G.DrawString(ch.c2, displayfont2, Brushes.Black, chc2valueleft + 5, Y); idx++; } ChRect.Height = gridheight * (gridcnt + 0); G.DrawRectangle(Pens.Gray, chinforect.Left, chinforect.Top, chinforect.Width, chinforect.Height); displayfont2.Dispose(); displayfont.Dispose(); G.Dispose(); } public void Button1_Click(System.Object sender, System.EventArgs e) { this.PrintDocument1.DefaultPageSettings.Landscape = true; try { this.PrintPreviewDialog1.ShowDialog(); } catch (Exception ex) { UTIL.MsgE($"인쇄실패\n\n{ex.Message}"); } } public void DataGridView2_CellDoubleClick(object sender, DataGridViewCellEventArgs e) { // 불러오기 if (this.bs_grp.Current == null) { UTIL.MsgE("선택된 그룹이 없습니다"); return; } bt_run.PerformClick(); } public void ToolStripMenuItem3_Click(System.Object sender, System.EventArgs e) { var drv = this.bs_grp.Current as DataRowView; if (drv == null) return; var dr = drv.Row as DS1.groupRow; MessageBox.Show($"Channel List\n\n{dr.value}"); } public void bt_run_Click(System.Object sender, System.EventArgs e) { PUB.smsg("Loading"); var bt = sender as Button; bt.Enabled = false; //GroupName = ""; foreach (var ch in this.ChartListData) ch.Clear(); //그룹선택확인 var DRVGRP = this.bs_grp.Current as DataRowView; if (DRVGRP == null) return; var DRGRP = DRVGRP.Row as DS1.groupRow; /* 주석날짜: 2024-09-20 | 주석내용: VIEWGROUP.xml 파일내용 | 주석자: 이재웅 * * * 9 * EL2500A * GRP(1...10) * 1,2,3,4,5,6,7,8,9,10 * * ************************************** * * DRGRP.idx = 9 * DRGRP.mame = "EL2500A" * DRGRP.cname = "[사용자] GRP(1...10)" * DRGRP.value = "1,2,3,4,5,6,7,8,9,10" */ var CHLIST = DRGRP.value.Trim();// ["value"].ToString().Trim(); ////표시채널목록 var CHArray = CHLIST.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); //채널번호를 채널그룹에 맞게 분리작업을 합니다. //채널데이터는 160개를 기준으로 1~10까지 분리되어 저장됩니다. //채널번호에 맞는 기준의 번호를 찾아야 합니다. foreach (var C in CHArray) { if (C.isEmpty() || C.IsNumeric() == false) continue; var ch = int.Parse(C); var ValueIdx = ch;// int.Parse(C); int quotient = ValueIdx / 160; // 몫 int remainder = ValueIdx % 160; // 나머지 if (remainder == 0) ValueIdx = quotient - 1; else ValueIdx = quotient; if (this.ChartListData[ValueIdx].ChannelList.Contains(ch) == false) this.ChartListData[ValueIdx].ChannelList.Add(ch); } //우측채널목록갱신(한번만 하면되므로 채널정보가 바뀌었을때만 처리한다.) if (CHLIST != SelectedGRPChValue) { bt_cursor1.BackColor = Color.Empty; bt_cursor2.BackColor = Color.Empty; RefreshChannelList(); SelectedGRPChValue = CHLIST; } using (var fd = new Frm_GraphSetup()) if (fd.ShowDialog() == DialogResult.OK) ShowData(); ////UpdateChannelInfo() bt.Enabled = true; PUB.smsg(""); } public void Button2_Click_2(object sender, EventArgs e) { using (var f = new Frm_GraphSetup()) { if (f.ShowDialog() == System.Windows.Forms.DialogResult.OK) ShowData(true); } } public void cmb_group_SelectedIndexChanged(System.Object sender, System.EventArgs e) { if (cmb_group.SelectedIndex < 0) return; //기존에 선택된 채널의 활성화를 모두 해제 한다. this.dS1.channel.ToList().ForEach(t => t.use = false); this.ChartListData.ToList().ForEach(t => t.Clear()); SelectedGRPChValue = ""; RefreshChannelList(); string title = cmb_group.Text; this.bs_grp.Filter = "rname='" + title + "'"; this.formsPlot1.Plot.Clear(); this.formsPlot1.Refresh(); } public void Button1_Click_1(System.Object sender, System.EventArgs e) { using (var sd = new SaveFileDialog()) { sd.Filter = "png file|*.png"; sd.FilterIndex = 0; sd.FileName = $"GraphCapture_{DateTime.Now.ToString("yyyyMMddHHmmssfff")}.png"; sd.RestoreDirectory = true; if (sd.ShowDialog() == DialogResult.OK) { var fn = sd.FileName; this.formsPlot1.Plot.SavePng(fn, formsPlot1.Width, formsPlot1.Height); var dlg = UTIL.MsgQ("생성된 파일을 확인할까요?"); if (dlg == DialogResult.Yes) UTIL.RunExplorer(fn); } } } public void ToolStripButton1_Click_1(object sender, EventArgs e) { string dir = PUB.CONFIG.GetDatabasePath() + "\\DataBase\\volt"; UTIL.RunExplorer(dir);//.Shell("explorer " + dir, AppWinStyle.NormalFocus); } public void bt_cursor_Click(object sender, EventArgs e) { var bt = sender as Button; var idx = int.Parse(bt.Tag.ToString()); CreateCursor(idx); } void CreateCursor(int idx) { if (this.myPlots == null) return; /* 작성자: 이재웅, 작성일: 2024-09-25, 내용: 'myPlots == null' 조건일때 오류발생 */ var plot = this.myPlots.Where(t => t != null).FirstOrDefault(); if (plot == null) return; //화면에 보이는 영역의 데이터를 확인 var viewx = plot.Axes.XAxis; var viewy = plot.Axes.YAxis; var xmin = viewx.Min; var xmax = viewx.Max; var datestep = (xmax - xmin) * 0.33f; //기존에 등록된 것이 있다면 그것을 제거하고 다시 추가한다. if (CursorLine[idx] != null) formsPlot1.Plot.Remove(CursorLine[idx]); var timedata = xmin + datestep * (idx + 1); var timeval = DateTime.FromOADate(timedata); var axLine = formsPlot1.Plot.Add.VerticalLine(timedata); axLine.Text = $"[C{idx + 1}] {timeval:yy-MM-dd\nHH:mm:ss}"; axLine.LabelAlignment = ScottPlot.Alignment.UpperRight; axLine.IsDraggable = true; this.CursorLine[idx] = axLine; this.formsPlot1.Refresh(); UpdateCursorValue(); } // DataGridView의 모든 행의 column 데이터 지우기 private void ClearColumnData(DataGridView dataGridView, int idx) { // DataGridView의 모든 행을 반복 foreach (DataGridViewRow row in dataGridView.Rows) { // 해당 열의 데이터 지우기 if (!row.IsNewRow) // 새 행이 아닌 경우에만 { row.Cells[idx + 2].Value = null; // 또는 string.Empty로 설정 가능 } } } private void bt_Hide_Click(object sender, EventArgs e) { //기존에 등록된 것이 있다면 그것을 제거한다. if (CursorLine[0] != null) { formsPlot1.Plot.Remove(CursorLine[0]); CursorLine[0] = null; ClearColumnData(dv_chlist, 0); } if (CursorLine[1] != null) { formsPlot1.Plot.Remove(CursorLine[1]); CursorLine[1] = null; ClearColumnData(dv_chlist, 1); } this.formsPlot1.Refresh(); } private void btConfig_Click(object sender, EventArgs e) { if (UTIL.ShowPropertyDialog(PUB.TREND) == DialogResult.OK) { PUB.TREND.Save(); formsPlot1.Plot.Axes.Bottom.MinimumSize = PUB.TREND.graph_bottom_minsize; if (PUB.TREND.y_scale_auto) formsPlot1.Plot.Axes.AutoScaleY(); else formsPlot1.Plot.Axes.SetLimitsY(PUB.TREND.graph_y_start, PUB.TREND.graph_y_end); formsPlot1.Refresh(); } } } }