//----------------------------------------------------------------------- // // Copyright © 2012 Nils Hammar. All rights reserved. // //----------------------------------------------------------------------- /* * Software to access vehicle information via the OBD-II connector. * * Copyright © 2012 Nils Hammar * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * Alternative licensing is possible, see the licensing document. * * The above text may not be removed or modified. */ namespace UserInterface.GUI.OBD { using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Windows.Forms; using System.Windows.Forms.DataVisualization.Charting; using global::DataSource.FileAccess; using global::Protocol.OBD; using global::SharedObjects; using global::SharedObjects.DataMgmt; using global::SharedObjects.GUI; using global::SharedObjects.GUI.OBD; using global::SharedObjects.Misc; using global::SharedObjects.Misc.Objects; using global::SharedObjects.Protocol; using global::SharedObjects.Protocol.OBD; using global::UserInterface.GUI.Objects; /// /// Panel for presentation of snapshot data items. /// public partial class CurrentDataPanel : AbstractUserControl, IDataPresentation, IProgressFeedback { /// /// Gets OBD data panel instance. /// public ObdDataPanel obdDataPanel { get { return this.obdDataPanel1; } } /// /// Gets poll interval when plotting. /// private int pollInterval; /// /// Messaging instance. /// private IMessaging iMessaging; /// /// Data source instance. /// private IDataSource iDataSource; /// /// Data requester instance. /// private DataRequester dataRequester; /// /// Indicate if plotting is checked. /// private bool plotting = false; /// /// Indicate if logging is checked. /// private bool logging = false; /// /// Start time point for logging/plotting to get relative time. /// /// Value is in milliseconds. /// /// private long t0 = 0; /// /// Dictionary of plot panels. /// private SortedDictionary plotPanels = new SortedDictionary(); /// /// Preferences data. /// private IPreferences iPreferences; #if DEBUG1 /// /// To only get one shot of data. /// private bool oneShot = false; #endif /// /// Mode parser instance. /// private ModeParser modeParser; /// /// List of requested items. /// private IList requestedItems = null; /// /// Tool tip handling for disabled controls. /// private Control currentToolTipControl = null; /// /// Tool tip handling. /// private ToolTip toolTip = new ToolTip(); /// /// Gets data file directory. /// protected string dataFileDir { get; private set; } /// /// Initializes a new instance of the class. /// /// Logging instance. /// Preferences data. /// Application tree instance. /// Messaging instance. /// Data source interface instance. /// Current vehicle instance. /// Data requester instance. /// Mode parser instance. /// Data file directory path. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "CanApp.CustomToolTip", Justification = "Reviewed.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Reviewed.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Reviewed.")] public CurrentDataPanel( ILogging iLogging, IPreferences iPreferences, IApplicationTree applicationTree, IMessaging iMessaging, IDataSource iDataSource, XmlClass.vehicle vehicle, DataRequester dataRequester, ModeParser modeParser, string dataFileDir) : base(iLogging, vehicle, applicationTree) { this.iPreferences = iPreferences; this.iMessaging = iMessaging; this.iDataSource = iDataSource; this.dataRequester = dataRequester; this.modeParser = modeParser; this.dataFileDir = dataFileDir; this.InitializeComponent(); this.pollInterval = this.iPreferences.defaultTimeout; this.pollUpDown.Value = this.pollInterval; this.goButton.Enabled = this.logDataCheckBox.Checked | this.plotDataCheckBox.Checked; this.obdDataPanel1.init( iLogging, iMessaging, applicationTree, 0x01, iDataSource, vehicle, this.dataFileDir); this.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.logDataCheckBox.Enabled = this.iLogging.getValueDataLogWriter() != null; if (!this.logDataCheckBox.Enabled) { this.logDataCheckBox.Checked = false; } this.folderBrowserDialog1.SelectedPath = this.iPreferences.getPreference(PreferencesFileAccess.LAST_DATA_DIRECTORY); this.toolTip.SetToolTip(this.saveButton, "Save the current selection to file."); this.toolTip.SetToolTip(this.loadButton, "Load a saved selection setting from file."); this.toolTip.SetToolTip(this.getSnapshotDataButton, "Do a one shot read of data according to the selection list."); this.toolTip.SetToolTip(this.refreshButton, "Refresh the list of selectable items and clear all current selections."); this.toolTip.SetToolTip(this.plotDataCheckBox, "Data shall be plotted to charts in sub-panels."); this.toolTip.SetToolTip(this.logDataCheckBox, "Data shall be logged to file in CSV format."); this.toolTip.SetToolTip(this.pollUpDown, "Base interval between polls.\r\nNotice that parameters can be defined to a lower frequency."); this.toolTip.SetToolTip(this.goButton, "Start logging/plotting of data selected in table below."); this.toolTip.SetToolTip(this.stopButton, "Stop logging/plotting of data."); this.toolTip.SetToolTip(this.clearButton, "Clear all sub-panels with data and charts."); this.toolTip.SetToolTip(this.saveDataButton, "Save the data in the plot charts to CSV file or files."); this.toolTip.SetToolTip(this.saveChartsButton, "Save plotted charts as images."); this.toolTip.SetToolTip(this.progressBar1, "This is an animation to indicate that the data request thread is active."); } /// /// Close the panel. /// public void close() { if (this.dataRequester != null) { this.dataRequester.stopPlotting(); } this.owningNode.Nodes.Clear(); this.plotPanels.Clear(); } /// /// Initialize and refresh data. /// public void init() { this.timer1.Start(); } /// /// Trig a get data event. /// public override void getDataEvent() { this.prepareRequestData(); this.dataRequester.requestSelectedPids(); } /// /// Dispatch the data to the suitable receiver. /// /// Presentation data wrapper. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Reviewed, intentional.")] public void simpleDataAdd(IPresentationData presentationData) { if ((presentationData.mode & 0xbf) != 0x02) { if (!this.plotting && !this.logging) { EcuDataPanel ecuDataPanel = getDataPanelByAddress(presentationData.sourceAddress, -1); ecuDataPanel.simpleDataAdd(presentationData); } if (this.logging && presentationData.pidItem != null && presentationData.sensor != null) { this.logData(presentationData); } if (this.plotting) { this.plotData(presentationData); } } } /// /// Add an item of the type Control. /// /// ECU source address for data. /// Which parameter mode that is selected. /// Item to add. public void add(uint sourceAddress, byte mode, Control control) { if ((mode & 0xbf) != 0x02) { if (!this.plotting && !this.logging) { EcuDataPanel ecuDataPanel = getDataPanelByAddress(sourceAddress, -1); ecuDataPanel.add(sourceAddress, mode, control); } } } /// /// Update progress bar to indicate that we do something. /// /// Counter value. public void tickProgress(uint count) { if (this.progressBar1.InvokeRequired) { try { this.Invoke(new tickProgressFunc(this.tickProgress), new object[] { count }); } catch (System.Reflection.TargetParameterCountException ex) { MessageBox.Show( "Exception: " + ex.Message + "\r\n", "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } catch (System.ObjectDisposedException) { // Ignore. } } else { this.progressBar1.Value = (int)(count % 6); } } /// /// Write the file heading row. /// /// All labels array. /// Stream to write to. /// CSV field separator. private static void writeHeading(string[] allLabels, StreamWriter streamWriter, string csvSep) { streamWriter.Write("t (s)"); foreach (string str in allLabels) { streamWriter.Write(csvSep); streamWriter.Write("\""); streamWriter.Write(str); streamWriter.Write("\""); } streamWriter.WriteLine(); } /// /// Write the values to the file. /// /// All mapped data dictionary. /// Stream to write to. /// CSV field separator. private static void writeData(SortedDictionary allMappedData, StreamWriter streamWriter, string csvSep) { DataPoint[] previousDpa = null; foreach (KeyValuePair kvp in allMappedData) { DataPoint[] dpa = kvp.Value; if (dpa[0] != null) { if (previousDpa != null) { for (int i = 0; i < dpa.Length && i < previousDpa.Length; i++) { if (dpa[i] == null && previousDpa[i] != null) { dpa[i] = previousDpa[i]; } } } streamWriter.Write(dpa[0].XValue.ToString(CultureInfo.CurrentCulture)); foreach (DataPoint dp in dpa) { streamWriter.Write(csvSep); if (dp != null) { streamWriter.Write(dp.YValues[0].ToString(CultureInfo.CurrentCulture)); } } previousDpa = dpa; streamWriter.WriteLine(); } } } /// /// Handle an exception and present to the user. /// /// Name of file related. /// Exception data. private static void handleException(string fileName, Exception ex) { #if DEBUG MessageBox.Show( "Could not open file '" + fileName + "'\r\n\r\n" + ex.Message + "\r\n\r\n" + ex.StackTrace, "Error", MessageBoxButtons.OK, MessageBoxIcon.Hand); #else MessageBox.Show( "Could not open file '" + fileName + "'\r\n\r\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Hand); #endif } /// /// Close writer and stream for the file. /// /// Stream to close. /// Writer to close. private static void closeFile(Stream fileStream, StreamWriter streamWriter) { if (streamWriter != null) { try { streamWriter.Close(); } catch { } } if (fileStream != null) { try { fileStream.Close(); } catch { } } } /// /// Log data to file. /// /// Data to log. private void logData(IPresentationData presentationData) { uint k = 0; foreach (XmlClass.pidgroup.pidlist.sensordata sensor in presentationData.pidItem.sensors) { if (sensor.offset == presentationData.sensor.offset && sensor.unit == presentationData.sensor.unit) { XmlClass.unititem thisunit = null; foreach (XmlClass.unititem unit in this.iDataSource.units) { if (sensor.unit.Equals(unit.name)) { thisunit = unit; break; } } if (this.iPreferences.selectedUnitCategory == 0 || (thisunit != null && (thisunit.category == 0 || (thisunit.category > 0 && thisunit.category == this.iPreferences.selectedUnitCategory)))) { uint? destinationAddress = DataRequester.getDestinationAddress(this.iMessaging.protocolHandler, presentationData.sourceAddress); uint da = (destinationAddress != null) ? (uint)destinationAddress : 0; uint[] keys = { da, (uint)(presentationData.mode & 0xbf), presentationData.pid, k }; if (sensor.unit == "bit") { this.dataRequester.iDataLog.logData(keys, presentationData.value < 0.5 ? 0 : 1); } else { this.dataRequester.iDataLog.logData(keys, presentationData.value); } } } k++; } } /// /// Plot the data on a chart. /// /// Data to plot. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Reviewed, intentional.")] private void plotData(IPresentationData presentationData) { uint key = (uint)(((uint)(presentationData.mode & 0xff) << 24) | (uint)(presentationData.pid & 0xffffff)); bool grouped = false; if (this.requestedItems != null) { foreach (PidItemWrapper item in this.requestedItems) { if (item != null) { if ((item.pidMode & 0xbf) == (presentationData.mode & 0xbf) && item.pidItem.pid_int == presentationData.pid && item.plotGroup != 0) { key = (uint)item.plotGroup; grouped = true; break; } } } } PanelTreeNode panelTreeNode; if (this.plotPanels.ContainsKey(key)) { panelTreeNode = this.plotPanels[key]; } else { XmlClass.pidgroup.pidlist pidItem = this.getPidItem(presentationData); string title = "N/A"; if (grouped) { title = "Plot Group " + key; } else { if (pidItem != null) { title = pidItem.name; } } PlotDataPanel plotDataPanel = new PlotDataPanel( this.iLogging, this.iPreferences, this.applicationTree, title, this.t0, this.pollInterval, grouped, (byte)(presentationData.mode & 0xbf), presentationData.pid); panelTreeNode = new PanelTreeNode(title, "chart_line.png", "chart_line.png", plotDataPanel); this.plotPanels.Add(key, panelTreeNode); this.addNode(panelTreeNode); } // Precaution in something odd happens. if (panelTreeNode.userControl is PlotDataPanel) { PlotDataPanel plotDataPanel1 = (PlotDataPanel)panelTreeNode.userControl; plotDataPanel1.simpleDataAdd(presentationData); } } /// /// Manually refresh the data. /// /// Sending object. /// Event data. private void refreshButton_Click(object sender, EventArgs e) { this.obdDataPanel1.refreshModeData(this.vehicle); this.init(); } /// /// Poll interval value was changed by the user. /// /// Sending object. /// Event data. private void pollUpDown_ValueChanged(object sender, EventArgs e) { this.pollInterval = (int)this.pollUpDown.Value; if (this.dataRequester != null) { this.dataRequester.pollInterval = this.pollInterval; } } /// /// Present snapshot data. /// /// Sending object. /// Event data. private void getSnapshotDataButton_Click(object sender, EventArgs e) { this.t0 = DateTime.Now.Ticks / Utils.TICKS_PER_MILLISECOND; // Refresh the dictionary with latest data. this.modeParser.configureModePidDictionary(); this.getDataEvent(); } /// /// Prepare the request data. /// private void prepareRequestData() { this.requestedItems = this.obdDataPanel1.getSelectedItems(); this.dataRequester.prepareRequestData(); foreach (PidItemWrapper item in this.requestedItems) { this.dataRequester.handlePid( item.addrList, (byte)(item.actualMode & 0xff), 0, item.pidMode, item.pidItem, item.pidItem.pid_int); } this.dataRequester.finishPrepareDataRequest(); } /// /// Add node in the navigation tree. /// /// Node to add. private delegate void addNodeFunc(PanelTreeNode panelTreeNode); /// /// Add node in the navigation tree. /// /// Node to add. private void addNode(PanelTreeNode panelTreeNode) { if (this.applicationTree.InvokeRequired) { try { this.Invoke(new addNodeFunc(this.addNode), new object[] { panelTreeNode }); } catch (System.Reflection.TargetParameterCountException ex) { MessageBox.Show( "Exception: " + ex.Message + "\r\n", "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } catch (System.ObjectDisposedException) { // Ignore. } } else { this.owningNode.Nodes.Add(panelTreeNode); this.owningNode.Expand(); } } /// /// Get PID item matching the data. /// /// Data to get PID item for. /// PID item or 'null' if not found. private XmlClass.pidgroup.pidlist getPidItem(IPresentationData presentationData) { XmlClass.pidgroup.pidlist pidItem = null; foreach (XmlClass.pidgroup pidgroup in this.iDataSource.pidgroups) { if (pidgroup.mode_int == (presentationData.mode & 0xbf)) { foreach (XmlClass.pidgroup.pidlist pid in pidgroup.pids) { if (pid.pid_int == presentationData.pid) { pidItem = pid; break; } } if (pidItem != null) { break; } } } return pidItem; } /// /// Start logging/plotting. /// /// Sending object. /// Event data. private void goButton_Click(object sender, EventArgs e) { DialogResult res = DialogResult.Yes; if (this.plotPanels.Count > 0) { res = MessageBox.Show( "Select 'Yes' to continue on the current series of data" + " and 'No' to start a new series on the same chart starting from zero.", "Question", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1); this.t0 = DateTime.Now.Ticks / Utils.TICKS_PER_MILLISECOND; if (res == DialogResult.No) { foreach (PanelTreeNode node in this.plotPanels.Values) { if (node.userControl is PlotDataPanel) { PlotDataPanel plotDataPanel = (PlotDataPanel)node.userControl; plotDataPanel.restart(this.t0); } } } } else { this.t0 = DateTime.Now.Ticks / Utils.TICKS_PER_MILLISECOND; } if (res != DialogResult.Cancel) { // Refresh the dictionary with latest data. this.modeParser.configureModePidDictionary(); this.setPlotActionButtonsVisible(false); this.stopButton.Enabled = true; this.goButton.Enabled = false; this.plotDataCheckBox.Enabled = false; this.logDataCheckBox.Enabled = false; this.getSnapshotDataButton.Enabled = false; this.refreshButton.Enabled = false; this.clearButton.Enabled = false; this.saveDataButton.Enabled = false; this.saveChartsButton.Enabled = false; this.dataRequester.preparePlotting(this.plotting, this.logging); if (!string.IsNullOrEmpty(this.iPreferences.gpsSerialPortName)) { this.dataRequester.startGpsThread(); } this.prepareRequestData(); this.dataRequester.startPlotThread(this); } } /// /// Update progress bar to indicate that we do something. /// /// Counter value. private delegate void tickProgressFunc(uint count); /// /// Set the plot action buttons visibility in all the related plot panels. /// /// 'true' if set to visible, 'false' to hide. private void setPlotActionButtonsVisible(bool visible) { foreach (PanelTreeNode node in this.plotPanels.Values) { if (node.userControl is PlotDataPanel) { PlotDataPanel panel = (PlotDataPanel)node.userControl; panel.setViewButtonsVisible(visible); } } } /// /// Stop logging/plotting. /// /// Sending object. /// Event data. private void stopButton_Click(object sender, EventArgs e) { this.dataRequester.stopPlotting(); this.stopButton.Enabled = false; this.goButton.Enabled = this.logDataCheckBox.Checked | this.plotDataCheckBox.Checked; this.plotDataCheckBox.Enabled = true; this.logDataCheckBox.Enabled = this.iLogging.getValueDataLogWriter() != null; this.getSnapshotDataButton.Enabled = true; this.refreshButton.Enabled = true; this.clearButton.Enabled = true; this.saveDataButton.Enabled = true; this.saveChartsButton.Enabled = true; this.setPlotActionButtonsVisible(true); if (!this.logDataCheckBox.Enabled) { this.logDataCheckBox.Checked = false; } } /// /// Select data logging. /// /// Sending object. /// Event data. private void logDataCheckBox_CheckedChanged(object sender, EventArgs e) { this.goButton.Enabled = this.logDataCheckBox.Checked | this.plotDataCheckBox.Checked; this.logging = this.logDataCheckBox.Checked; } /// /// Select data plotting. /// /// Sending object. /// Event data. private void plotDataCheckBox_CheckedChanged(object sender, EventArgs e) { this.goButton.Enabled = this.logDataCheckBox.Checked | this.plotDataCheckBox.Checked; this.plotting = this.plotDataCheckBox.Checked; } /// /// Clear the current set of sub-panels. /// /// Sending object. /// Event data. private void clearButton_Click(object sender, EventArgs e) { if (MessageBox.Show( "Do you want to discard the current set of sub-panels?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1) == DialogResult.Yes) { this.owningNode.Nodes.Clear(); this.plotPanels.Clear(); this.ecuData.Clear(); } } /// /// Log data checkbox activation. /// /// Sending object. /// Event data. private void CurrentDataPanel_VisibleChanged(object sender, EventArgs e) { this.logDataCheckBox.Enabled = this.iLogging.getValueDataLogWriter() != null; if (!this.logDataCheckBox.Enabled) { this.logDataCheckBox.Checked = false; } } /// /// Log data checkbox activation. /// /// Sending object. /// Event data. private void CurrentDataPanel_MouseEnter(object sender, EventArgs e) { this.logDataCheckBox.Enabled = this.iLogging.getValueDataLogWriter() != null; if (!this.logDataCheckBox.Enabled) { this.logDataCheckBox.Checked = false; } } /// /// Log data checkbox activation. /// /// Sending object. /// Event data. private void getSnapshotDataButton_Paint(object sender, PaintEventArgs e) { this.logDataCheckBox.Enabled = this.iLogging.getValueDataLogWriter() != null; if (!this.logDataCheckBox.Enabled) { this.logDataCheckBox.Checked = false; } } /// /// Save current selection to file. /// /// Sending object. /// Event data. private void saveButton_Click(object sender, EventArgs e) { this.saveFileDialog1.FileName = string.Empty; this.saveFileDialog1.DefaultExt = ".xml"; this.saveFileDialog1.AddExtension = true; this.saveFileDialog1.ValidateNames = true; this.saveFileDialog1.Filter = "XML Format|*.xml"; DialogResult res = this.saveFileDialog1.ShowDialog(); if (res == DialogResult.OK) { ModePidSelectionFileAccess modePidSelectionFileAccess = new ModePidSelectionFileAccess(this.iLogging, this.saveFileDialog1.FileName, this.dataFileDir); IList items = this.obdDataPanel1.getSelectedItems(); foreach (IPidItemWrapper item in items) { XmlClass.selectionModePid oneSelection = new XmlClass.selectionModePid(); List addressList = new List(); foreach (uint addr in item.addrList) { XmlClass.selectionModePid.addressItem addrItem = new XmlClass.selectionModePid.addressItem(); addrItem.address = addr; addressList.Add(addrItem); } oneSelection.addrList = addressList; oneSelection.mode_int = item.pidMode; oneSelection.pid_int = item.pidItem.pid_int; oneSelection.plotGroup = item.plotGroup; modePidSelectionFileAccess.selections.Add(oneSelection); } modePidSelectionFileAccess.save(); } } /// /// Load saved selection from file. /// /// Sending object. /// Event data. private void loadButton_Click(object sender, EventArgs e) { this.openFileDialog1.FileName = string.Empty; this.openFileDialog1.DefaultExt = ".xml"; this.openFileDialog1.AddExtension = true; this.openFileDialog1.ValidateNames = true; this.openFileDialog1.Filter = "XML Format|*.xml"; DialogResult res = this.openFileDialog1.ShowDialog(); if (res == DialogResult.OK) { ModePidSelectionFileAccess modePidSelectionFileAccess = new ModePidSelectionFileAccess(this.iLogging, this.openFileDialog1.FileName, this.dataFileDir); modePidSelectionFileAccess.load(); this.obdDataPanel1.selectItems(modePidSelectionFileAccess.selections); } } /// /// Select a directory to save data to. /// /// Out value with the selected path. /// Action preferred by the user. private DialogResult selectDirectory(out string path) { DialogResult res = DialogResult.No; path = null; while (res == DialogResult.No) { res = this.folderBrowserDialog1.ShowDialog(); if (res == DialogResult.OK) { path = this.folderBrowserDialog1.SelectedPath; this.iPreferences.setPreference(PreferencesFileAccess.LAST_DATA_DIRECTORY, path); if (Directory.GetFiles(path).Length > 0) { res = MessageBox.Show( "Directory '" + path + "' is not empty, do you want to use it anyway?", "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning); } } } return res; } /// /// Save chart data to CSV file. /// /// Sending object. /// Event data. private void saveDataButton_Click(object sender, EventArgs e) { string path; DialogResult res = this.selectDirectory(out path); if (res != DialogResult.Cancel && path != null) { DialogResult res1 = MessageBox.Show( "Do you want to save data in one single file or several individual files?\r\n" + "Yes = Single file, No = Multiple files.", "Question", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); switch (res1) { case DialogResult.Yes: this.saveSingleFile(path); break; case DialogResult.No: this.saveMultiFile(path); break; default: // Do nothing. break; } } } /// /// Save data to multiple files. /// /// Path to save to. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Reviewed.")] private void saveSingleFile(string path) { SortedDictionary allMappedData = new SortedDictionary(); string[] allLabels = this.joinAllData(allMappedData); string fileName = path + "\\AllData.csv"; Stream fileStream = null; StreamWriter streamWriter = null; try { fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write); streamWriter = new StreamWriter(fileStream, Utils.iso_8859_1); string csvSep = ";"; if (CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator == ".") { csvSep = ","; } writeHeading(allLabels, streamWriter, csvSep); writeData(allMappedData, streamWriter, csvSep); } catch (Exception ex) { handleException(fileName, ex); } finally { closeFile(fileStream, streamWriter); } } /// /// Join data from all charts into one single set. /// /// Set to join data into. /// Array of headings. private string[] joinAllData(SortedDictionary allMappedData) { int totalColumns = this.getTotalColumns(); string[] allLabels = new string[totalColumns]; try { int columnIndex = 0; foreach (PanelTreeNode node in this.plotPanels.Values) { if (node.userControl is PlotDataPanel) { PlotDataPanel plotDataPanel = (PlotDataPanel)node.userControl; int seriesColumns = plotDataPanel.countSeriesColumns(); SortedDictionary mappedData = new SortedDictionary(); string[] labels = plotDataPanel.getMappedData(seriesColumns, mappedData); for (int i = 0; i < seriesColumns && i < labels.Length; i++) { allLabels[columnIndex + i] = labels[i]; } foreach (KeyValuePair kvp in mappedData) { DataPoint[] allPoints; if (allMappedData.ContainsKey(kvp.Key)) { allPoints = allMappedData[kvp.Key]; } else { allPoints = new DataPoint[totalColumns]; allMappedData.Add(kvp.Key, allPoints); } DataPoint[] mappedPoints = kvp.Value; for (int i = 0; i < seriesColumns && i < mappedPoints.Length; i++) { allPoints[columnIndex + i] = mappedPoints[i]; } } columnIndex += seriesColumns; } } } catch (Exception ex) { MessageBox.Show( ex.Message + "\r\n" + ex.StackTrace, "Error", MessageBoxButtons.OK, MessageBoxIcon.Stop); } return allLabels; } /// /// Count total number of columns to write. /// /// Total number of columns. private int getTotalColumns() { int totalColumns = 0; foreach (PanelTreeNode node in this.plotPanels.Values) { if (node.userControl is PlotDataPanel) { PlotDataPanel plotDataPanel = (PlotDataPanel)node.userControl; int seriesColumns = plotDataPanel.countSeriesColumns(); totalColumns += seriesColumns; } } return totalColumns; } /// /// Save data to multiple files. /// /// Path to save to. private void saveMultiFile(string path) { foreach (PanelTreeNode node in this.plotPanels.Values) { if (node.userControl is PlotDataPanel) { PlotDataPanel plotDataPanel = (PlotDataPanel)node.userControl; plotDataPanel.saveData(path); } } } /// /// Save chart images to CSV file. /// /// Sending object. /// Event data. private void saveChartsButton_Click(object sender, EventArgs e) { string path; DialogResult res = this.selectDirectory(out path); if (res != DialogResult.Cancel && path != null) { foreach (PanelTreeNode node in this.plotPanels.Values) { if (node.userControl is PlotDataPanel) { PlotDataPanel plotDataPanel = (PlotDataPanel)node.userControl; plotDataPanel.saveChartImage(path); } } } } /// /// Act on mouse moved. /// /// Sending object. /// Event data. private void CurrentDataPanel_MouseMove(object sender, MouseEventArgs e) { Control control = GetChildAtPoint(e.Location); if (control != null) { if (!control.Enabled && this.currentToolTipControl == null) { string toolTipString = this.toolTip.GetToolTip(control); // trigger the tooltip with no delay and some basic positioning just to give you an idea this.toolTip.Show(toolTipString, control, control.Width / 2, control.Height / 2); this.currentToolTipControl = control; } } else { if (this.currentToolTipControl != null) { this.toolTip.Hide(this.currentToolTipControl); } this.currentToolTipControl = null; } } /// /// Delayed request for data. /// /// Sending object. /// Event data. private void timer1_Tick(object sender, EventArgs e) { this.timer1.Stop(); IRequestData rd = new RequestData(null, TxMsg.MODE_CURRENT, null, new TxMsg(TxMsg.GET_SUPPORTED_PIDS)); this.iMessaging.queueMsg(rd); this.iMessaging.sendMsg(); } } }