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.Reflection; using System.Runtime.InteropServices; using TrendCtrlII; using AR; using System.Data.SqlTypes; using System.Diagnostics.Eventing.Reader; using System.Data.Linq; namespace vmsnet { public partial class Frm_trend_Real { private string lasttime = ""; private DateTime EndDate = DateTime.Now; private bool init = false; private HMI.DispCtrl DispCtrl1; readonly ScottPlot.WinForms.FormsPlot formsPlot1; ScottPlot.Plottables.Scatter[] Streamer1; List[] dataVolt = null; List[] dataTime = null; short voltlimit = 0; short timelimit = 10; public Frm_trend_Real(HMI.DispCtrl dispctrl) { // 디자이너에서 이 호출이 필요합니다. InitializeComponent(); // InitializeComponent() 호출 뒤에 초기화 코드를 추가하세요. //현재데이터베이스를 복제한다. MakeTempDatabase(); formsPlot1 = new ScottPlot.WinForms.FormsPlot() { Dock = DockStyle.Fill }; formsPlot1.MouseDown += FormsPlot1_MouseDown; formsPlot1.MouseUp += FormsPlot1_MouseUp; formsPlot1.MouseMove += FormsPlot1_MouseMove; CrossHair = formsPlot1.Plot.Add.Crosshair(0, 0); CrossHair.TextColor = ScottPlot.Colors.White; CrossHair.TextBackgroundColor = CrossHair.HorizontalLine.Color; formsPlot1.Plot.YLabel("VOLTAGE"); formsPlot1.Plot.XLabel("COUNT"); formsPlot1.Plot.Axes.Bottom.MinimumSize = PUB.TREND.graph_bottom_minsize; this.formsPlot1.Plot.ShowLegend(); formsPlot1.Plot.Axes.DateTimeTicksBottom(); this.formsPlot1.Plot.Axes.ContinuouslyAutoscale = true; this.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); } }; Panel2.Controls.Clear(); this.Panel2.Controls.Add(this.formsPlot1); ////그룹데이터목록 this.cmb_tanks.Items.Clear(); for (int i = 0; i <= dispctrl.GROUPS.Length - 1; i++) { var grp = dispctrl.GROUPS[i]; this.cmb_tanks.Items.Add(grp.이름); } DispCtrl1 = dispctrl; PUB.RemoteCommandEvent += Pub_RemoteCommandEvent; Refresh_전해조목록(); if (this.cmb_tanks.Items.Count > 0) this.cmb_tanks.SelectedIndex = 0; Streamer1 = new ScottPlot.Plottables.Scatter[0]; dataVolt = new List[0]; dataTime = new List[0]; } public void Frm_trend_FormClosing(object sender, System.Windows.Forms.FormClosingEventArgs e) { saveviewSetting(); PUB.RemoteCommandEvent -= Pub_RemoteCommandEvent; formsPlot1.MouseDown -= FormsPlot1_MouseDown; formsPlot1.MouseUp -= FormsPlot1_MouseUp; formsPlot1.MouseMove -= FormsPlot1_MouseMove; Timer1.Stop(); } void MakeTempDatabase() { //현재데이터베이스를 복제한다. this.documentElement1.Clear(); this.documentElement1.GRP.Merge(PUB.DS.GRP); this.documentElement1.CHANNEL.Merge(PUB.DS.CHANNEL); this.documentElement1.AcceptChanges(); this.ds1.Clear(); this.ds1.AcceptChanges(); } private void Refresh_전해조목록() { this.cmb_tanks.Items.Clear(); var grplist = this.documentElement1.GRP.Where(t => t.USE == 1); if (grplist.Any() == false) { UTIL.MsgE("활성화된 그룹이 없습니다"); return; } var namelist = grplist.Select(t => $"[{t.IDX:00}] {t.TITLE}").ToArray(); this.cmb_tanks.Items.AddRange(namelist); } public void Frm_trend_Load(System.Object sender, System.EventArgs e) { EndDate = DateTime.Now; ////마지막으로 선택한 그룹을 선택해준다. var selidx0 = (PUB.CONFIG.tvr_selectgroup0); // XMl.Data("trendview", "selectgroup0", "0") this.cmb_tanks.SelectedIndex = selidx0; loadviewSetting(); init = true; Timer1.Start(); } private void Pub_RemoteCommandEvent(object sender, RemoteCommand e) { try { if (e.Command == rCommand.ValueUpdate) { //스트리머,데이터,선택된 채널 정보가 있어야 업데이트 가능하다 if (e.Data != null && this.Streamer1 != null && this.selectchlist.Any()) { var data = e.Data as List; //선택된 채널의 정보만 사용 var recvdatas = data.Where(t => selectchlist.Contains(t.chno)).Select(t => t); if (recvdatas.Any() == false) return; //대상채널데이터가 없다. //받은데이터를 화면에 추가한다. foreach (var newdata in recvdatas) { var ch = newdata.chno - 1; var val = newdata.value; var time = newdata.time; //자료가없거나 스트리머가 없는 경우 if (ch >= this.Streamer1.Length || this.Streamer1[ch] == null) continue; float value = 0; if (PUB.CONFIG.datadiv != 0 && PUB.CONFIG.datadiv != 1) value = (newdata.value) / PUB.CONFIG.datadiv; else value = (newdata.value); //채널정보를 통해서 소수점위치와 옾셋값을 가져온다 if (newdata.decpos > 0) value = (float)(value / Math.Pow(10, newdata.decpos)); //최종옵셋 value += newdata.offset; //데이터 추가 if (this.Streamer1[ch].IsVisible) { var v_time = DateTime.Parse(time); this.dataTime[ch].Add(v_time.ToOADate()); this.dataVolt[ch].Add(value); var mintime = DateTime.FromOADate(dataTime[ch].First()); var maxtime = DateTime.FromOADate(dataTime[ch].Last()); var ts = (maxtime - mintime); if (ts.TotalMinutes >= this.timelimit) { //10개지운다 if (dataTime[ch].Count > 10) { dataTime[ch].RemoveRange(0, 10); dataVolt[ch].RemoveRange(0, 10); } } } } this.BeginInvoke(new Action(() => { if (voltlimit != 0) formsPlot1.Plot.Axes.AutoScaleX(); this.formsPlot1.Refresh(); })); } } } catch (NullReferenceException ex1) { // 예외 메시지, 파일명, 행 번호를 로그에 기록 var stackTrace = new System.Diagnostics.StackTrace(ex1, true); var frame = stackTrace.GetFrame(0); string fileName = frame.GetFileName(); int lineNumber = frame.GetFileLineNumber(); // UI 디스플레이 중, 예외발생 문제 PUB.log.AddE($"NullReferenceException Message={ex1.Message}, 파일명={fileName}, 행번호={lineNumber}"); return; } catch (Exception ex2) { // 예외 메시지, 파일명, 행 번호를 로그에 기록 var stackTrace = new System.Diagnostics.StackTrace(ex2, true); var frame = stackTrace.GetFrame(0); string fileName = frame.GetFileName(); int lineNumber = frame.GetFileLineNumber(); // Else Error !!! PUB.log.AddE($"Exception Message={ex2.Message}, 파일명={fileName}, 행번호={lineNumber}"); return; } } #region "scott plot mouse event" ScottPlot.Plottables.Crosshair CrossHair; ScottPlot.Plottables.VerticalLine[] CursorLine; 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(); } ScottPlot.Plottables.AxisLine PlottableBeingDragged = null; 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); if (mouseCoordinates.X is double.NaN || mouseCoordinates.Y is double.NaN || mouseCoordinates.X is double.PositiveInfinity || mouseCoordinates.Y is double.PositiveInfinity || mouseCoordinates.X is double.NegativeInfinity || mouseCoordinates.Y is double.NegativeInfinity) { return; } 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); if (PlottableBeingDragged is null) { // set cursor based on what's beneath the plottable var lineUnderMouse = GetLineUnderMouse(e.X, e.Y); 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}"; vl.Text = $"{time:yy-MM-dd\nHH:mm:ss}"; } formsPlot1.Refresh(); } } #endregion //그룹이 선택된 경우 public void cmb_group_SelectedIndexChanged(System.Object sender, System.EventArgs e) { if (cmb_tanks.SelectedIndex < 0) return; if (System.Diagnostics.Debugger.IsAttached) Console.WriteLine($"grp selectedindex val={cmb_tanks.SelectedIndex}"); /* 작성자: 이재웅, 작성일: 2024-11-22, 내용: 차트/Scatter/Volt/Time 초기화 */ this.formsPlot1.Plot.Clear(); this.Streamer1 = new ScottPlot.Plottables.Scatter[0]; this.dataVolt = new List[0]; this.dataTime = new List[0]; /**********************************************************************/ //현재등록된 목록을 모두 삭제 this.ds1.channel.Clear(); this.formsPlot1.Plot.Remove(); //데이터스트림삭제 this.formsPlot1.Refresh(); var grpName = cmb_tanks.Text.Substring(4).Trim(); var grpNo = int.Parse(cmb_tanks.Text.Substring(1, 2)); var grpdr = this.documentElement1.GRP.Where(t => t.TITLE.Equals(grpName)).FirstOrDefault(); if (grpdr == null) return; var chlist = this.documentElement1.CHANNEL.Where(t => t.GIDX == grpdr.IDX); if (chlist.Any() == false) return; // 미리저장된 목록을 확인한다. var chstr = ""; if (grpNo == 0) chstr = PUB.CONFIG.grp0chlist; else if (grpNo == 1) chstr = PUB.CONFIG.grp1chlist; else if (grpNo == 2) chstr = PUB.CONFIG.grp2chlist; else if (grpNo == 3) chstr = PUB.CONFIG.grp3chlist; else if (grpNo == 4) chstr = PUB.CONFIG.grp4chlist; else if (grpNo == 5) chstr = PUB.CONFIG.grp5chlist; else if (grpNo == 6) chstr = PUB.CONFIG.grp6chlist; else if (grpNo == 7) chstr = PUB.CONFIG.grp7chlist; else if (grpNo == 8) chstr = PUB.CONFIG.grp8chlist; else if (grpNo == 9) chstr = PUB.CONFIG.grp9chlist; var chbuf = chstr.Split((char[])(new[] { ',' }), StringSplitOptions.RemoveEmptyEntries); //목록은 추가하지만 스트립을 추가하지 않으니 최초 선택시에는 데이터가 나오지 않는다. //사용자는 채널 선택을 사용해야 데이터가 보인다. this.ds1.channel.Clear(); this.selectchlist.Clear(); foreach (var dr in chlist) { var newdr = ds1.channel.NewchannelRow(); newdr.idx = dr.IDX; newdr.use = chbuf.Contains(dr.IDX.ToString()) ? true : false; newdr.c1 = ""; newdr.c2 = ""; newdr.cname = dr.TITLE; newdr.cc = dr.COLOR; /* 작성자: 이재웅, 작성일: 2024-12-09, 내용: '개별보기' 상태에서 전해조 그룹 변경 時 자동으로 선택된 전해조 채널 보이기 */ if (newdr.use) { int idx = dr.IDX; //지정된 플롯의 표시여부를 변경한다. if (this.Streamer1.Length < idx) { Array.Resize(ref Streamer1, idx); Array.Resize(ref dataVolt, idx); Array.Resize(ref dataTime, idx); } if (dataTime[idx - 1] == null) dataTime[idx - 1] = new List(); if (dataVolt[idx - 1] == null) dataVolt[idx - 1] = new List(); if (Streamer1[idx - 1] == null) Streamer1[idx - 1] = formsPlot1.Plot.Add.ScatterPoints(dataTime[idx - 1], dataVolt[idx - 1]); Streamer1[idx - 1].LineWidth = 1; Streamer1[idx - 1].MarkerSize = 0; /* 작성자: 이재웅, 작성일: 2024-11-27, 내용: 변경한 셀이름으로 차트범례 표시 */ Streamer1[idx - 1].LegendText = newdr.cname; //$"CH{idx}"; Streamer1[idx - 1].IsVisible = true; selectchlist.Add(idx); //선택채널에 추가한다. } /************************************************************************/ ds1.channel.AddchannelRow(newdr); } ds1.channel.AcceptChanges(); formsPlot1.Refresh(); } public void ToolStripButton1_Click_1(object sender, EventArgs e) { string dir = System.IO.Path.Combine(PUB.CONFIG.GetDatabasePath(), "DataBase", "volt"); UTIL.RunExplorer(dir); } private void loadviewSetting() { cmb_volt.SelectedIndex = (PUB.CONFIG.cell_voltindex); // Xml.Data("rtlview", "voltindex", "1") cmb_time.SelectedIndex = (PUB.CONFIG.cell_timeindex); // XMl.Data("rtlview", "timeindex", "0") } private void saveviewSetting() { PUB.CONFIG.cell_voltindex = (cmb_volt.SelectedIndex); PUB.CONFIG.cell_timeindex = (cmb_time.SelectedIndex); PUB.CONFIG.Save(); } public void bs_grp_CurrentChanged(object sender, EventArgs e) { ////사용자가 그룹을 선택하면 해당 그룹에 연결된 채널목록을 가지고 사용 데이터 그룹을 결정함 } /// /// 1부터시작하는 채널의 번호입니다.(인덱스가 아님) /// 채널선택화면에서 선택된 채널 번호만 들어있다. /// docuementElement1 에는 모든 그룹/채널 정보가 들어있다 /// List selectchlist = new List(); public void btSelectCH_Click(object sender, EventArgs e) { var grpName = cmb_tanks.Text.Substring(4).Trim(); var grpNo = int.Parse(cmb_tanks.Text.Substring(1, 2)); //현재선택된 그룹의 채널목록을 추출해야한다. if (grpName.isEmpty()) { UTIL.MsgE("채널 그룹을 선택하세요"); return; } var drGrp = documentElement1.GRP.Where(t => t.TITLE == grpName).FirstOrDefault(); if (drGrp == null) { UTIL.MsgE("선택된 채널 그룹 정보가 없습니다"); return; } //채널목록을 확인한다. var chrows = documentElement1.CHANNEL.Where(t => t.GIDX == drGrp.IDX); if (chrows.Any() == false) { UTIL.MsgE("해당 그룹에 등록된 채널정보가 없습니다"); return; } //현재 등록된 채널정보를 토대로 데이터를 선택한다 using (var f = new Frm_SelectCH(ds1.channel)) { if (f.ShowDialog() == DialogResult.OK) { //현재등록된 목록을 모두 삭제 this.formsPlot1.Plot.Remove(); //데이터스트림삭제 this.formsPlot1.Refresh(); this.selectchlist.Clear(); //기존에 선택된 자료 선택하제 foreach (var item in ds1.channel.Where(t => t.use)) { item.use = false; } ds1.channel.AcceptChanges(); foreach (ListViewItem item in f.CheckedListBox1.Items) //선택된 목록만 가져온다 { int idx = int.Parse(item.Tag.ToString()); //.SubItems(0).Text) //지정된 플롯의 표시여부를 변경한다. if (this.Streamer1.Length < idx) { Array.Resize(ref Streamer1, idx); Array.Resize(ref dataVolt, idx); Array.Resize(ref dataTime, idx); } if (dataTime[idx - 1] == null) dataTime[idx - 1] = new List(); if (dataVolt[idx - 1] == null) dataVolt[idx - 1] = new List(); if (Streamer1[idx - 1] != null) this.Streamer1[idx - 1] = null; Streamer1[idx - 1] = this.formsPlot1.Plot.Add.ScatterPoints(dataTime[idx - 1], dataVolt[idx - 1]); Streamer1[idx - 1].LineWidth = 1; Streamer1[idx - 1].MarkerSize = 0; /* 작성자: 이재웅, 작성일: 2024-11-27, 내용: 변경한 셀이름으로 차트범례 표시 */ Streamer1[idx - 1].LegendText = item.Text; //$"CH{idx}"; Streamer1[idx - 1].IsVisible = item.Checked; var dr = ds1.channel.Where(t => t.idx == idx).FirstOrDefault(); if (dr != null) { dr.use = item.Checked; dr.EndEdit(); } if (item.Checked) selectchlist.Add(idx); //선택채널에 추가한다. } ds1.channel.AcceptChanges(); //사용자목록에 저장한다 string chstr = string.Join(",", selectchlist.ToArray()); if (grpNo == 0) PUB.CONFIG.grp0chlist = chstr; else if (grpNo == 1) PUB.CONFIG.grp1chlist = chstr; else if (grpNo == 2) PUB.CONFIG.grp2chlist = chstr; else if (grpNo == 3) PUB.CONFIG.grp3chlist = chstr; else if (grpNo == 4) PUB.CONFIG.grp4chlist = chstr; else if (grpNo == 5) PUB.CONFIG.grp5chlist = chstr; else if (grpNo == 6) PUB.CONFIG.grp6chlist = chstr; else if (grpNo == 7) PUB.CONFIG.grp7chlist = chstr; else if (grpNo == 8) PUB.CONFIG.grp8chlist = chstr; else if (grpNo == 9) PUB.CONFIG.grp9chlist = chstr; PUB.CONFIG.Save(); if (CrossHair == null) { CrossHair = formsPlot1.Plot.Add.Crosshair(0, 0); CrossHair.TextColor = ScottPlot.Colors.White; CrossHair.TextBackgroundColor = CrossHair.HorizontalLine.Color; } formsPlot1.Plot.Axes.ContinuouslyAutoscale = true; this.formsPlot1.Refresh(); } } } public void Timer1_Tick(object sender, EventArgs e) { } private void Panel1_Paint(object sender, PaintEventArgs e) { } private void cmb_volt_SelectedIndexChanged(object sender, EventArgs e) { if (cmb_volt.SelectedIndex < 0) return; if (cmb_volt.SelectedIndex == 0) voltlimit = 0; else voltlimit = short.Parse(this.cmb_volt.Text.Replace("v", "")); if (this.formsPlot1 != null) { if (voltlimit == 0) this.formsPlot1.Plot.Axes.ContinuouslyAutoscale = true; else { this.formsPlot1.Plot.Axes.ContinuouslyAutoscale = false; this.formsPlot1.Plot.Axes.SetLimitsY(0, voltlimit); } } } private void cmb_time_SelectedIndexChanged_1(object sender, EventArgs e) { if (cmb_time.SelectedIndex < 0) return; timelimit = short.Parse(this.cmb_time.Text.Replace("분", "")); } 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(); } } } }