//****************************************************************************************************** // RealTimeData.cs - Gbtc // // Copyright © 2010, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 07/21/2011 - Mehulbhai P Thakkar // Generated original version of source code. // 03/06/2012 - J. Ritchie Carroll // Added virtual device for subscribed measurements when two nodes share a single database. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Globalization; using System.Linq; using System.Windows; using System.Windows.Media; using GSF.Data; using GSF.TimeSeries.UI; namespace GSF.PhasorProtocols.UI.DataModels { /// /// Represents a real-time stream of subscribed data. /// public class RealTimeStream : DataModelBase { #region [ Members ] // Fields private int m_id; private string m_acronym; private string m_name; private string m_companyName; private bool m_enabled; private bool m_expanded; private string m_statusColor; private ObservableCollection m_deviceList; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public RealTimeStream() : base(false, false) { } #endregion #region [ Properties ] /// /// Gets or sets the ID of the . /// public int ID { get { return m_id; } set { m_id = value; OnPropertyChanged(nameof(ID)); } } /// /// Gets or sets the acronym of the current . /// public string Acronym { get { return m_acronym; } set { m_acronym = value; OnPropertyChanged(nameof(Acronym)); } } /// /// Gets or sets the name of the current . /// public string Name { get { return m_name; } set { m_name = value; OnPropertyChanged(nameof(Name)); } } /// /// Gets or sets the company name of the current . /// public string CompanyName { get { return m_companyName; } set { m_companyName = value; OnPropertyChanged(nameof(CompanyName)); } } /// /// Gets or sets the Expanded flag for the current . /// public bool Expanded { get { return m_expanded; } set { m_expanded = value; OnPropertyChanged(nameof(Expanded)); } } /// /// Gets or sets the status color of the current . /// public string StatusColor { get { return m_statusColor; } set { m_statusColor = value; OnPropertyChanged(nameof(StatusColor)); } } /// /// Gets or sets whether the current is enabled. /// public bool Enabled { get { return m_enabled; } set { m_enabled = value; OnPropertyChanged(nameof(Enabled)); } } /// /// Gets or sets collection of s for the current . /// public ObservableCollection DeviceList { get { return m_deviceList; } set { m_deviceList = value; OnPropertyChanged(nameof(DeviceList)); } } #endregion #region [ Static ] private const int GroupAccessID = -99999; // Static Methods /// /// Loads information as an style list. /// /// to connection to database. /// Collection of . public static ObservableCollection Load(AdoDataConnection database) { bool createdConnection = false; try { ObservableCollection realTimeStreamList = null; createdConnection = CreateConnection(ref database); DataSet resultSet = new(); resultSet.EnforceConstraints = false; // Get PDCs list. resultSet.Tables.Add(database.Connection.RetrieveData(database.AdapterType, database.ParameterizedQueryString("SELECT ID, Acronym, Name, ConnectionString, CompanyName, Enabled FROM DeviceDetail " + "WHERE NodeID = {0} AND IsConcentrator = {1} AND Enabled = {2} ORDER BY Acronym", "nodeID", "isConcentrator", "enabled"), DefaultTimeout, database.CurrentNodeID(), database.Bool(true), database.Bool(true)).Copy()); resultSet.Tables[0].TableName = "PdcTable"; // Add a dummy device row in PDC table to associate PMUs which are not PDC and connected directly. DataRow row = resultSet.Tables["PdcTable"].NewRow(); row["ID"] = 0; row["Acronym"] = string.Empty; row["Name"] = "Devices Connected Directly"; row["CompanyName"] = string.Empty; row["Enabled"] = false; resultSet.Tables["PdcTable"].Rows.Add(row); // Get Non-PDC device list. resultSet.Tables.Add(database.Connection.RetrieveData(database.AdapterType, database.ParameterizedQueryString("SELECT ID, Acronym, Name, AccessID, CompanyName, ProtocolName, VendorDeviceName, " + "ParentAcronym, Enabled FROM DeviceDetail WHERE NodeID = {0} AND IsConcentrator = {1} AND Enabled = {2} ORDER BY Acronym", "nodeID", "isConcentrator", "enabled"), DefaultTimeout, database.CurrentNodeID(), database.Bool(false), database.Bool(true)).Copy()); resultSet.Tables[1].TableName = "DeviceTable"; // Get non-statistical measurement list resultSet.Tables.Add(database.Connection.RetrieveData(database.AdapterType, database.ParameterizedQueryString("SELECT ID, DeviceID, SignalID, PointID, PointTag, SignalReference, " + "SignalAcronym, Description, SignalName, EngineeringUnits, HistorianAcronym, Subscribed, Internal FROM MeasurementDetail WHERE NodeID = {0} AND " + "SignalAcronym <> {1} ORDER BY SignalReference", "nodeID", "signalAcronym"), DefaultTimeout, database.CurrentNodeID(), "STAT").Copy()); resultSet.Tables[2].TableName = "MeasurementTable"; // Query for any non-statistical measurements that are subscribed via GEP, but are a part of another node in the same database // IMPORTANT: Make sure columns in this external node query exactly match those of the previous primary measurement query DataTable otherMeasurements = database.Connection.RetrieveData(database.AdapterType, database.ParameterizedQueryString("SELECT ID, 0 AS DeviceID, SignalID, PointID, PointTag, SignalReference, " + "SignalAcronym, Description, SignalName, EngineeringUnits, HistorianAcronym, Subscribed, Internal FROM MeasurementDetail WHERE NodeID <> {0} AND " + "SignalAcronym <> {1} AND Subscribed <> 0 ORDER BY SignalReference", "nodeID", "signalAcronym"), DefaultTimeout, database.CurrentNodeID(), "STAT"); realTimeStreamList = new ObservableCollection( from pdc in resultSet.Tables["PdcTable"].AsEnumerable() let settings = pdc.Field("ConnectionString").ToNonNullString().ParseKeyValuePairs() select new RealTimeStream { ID = pdc.ConvertField("ID"), Acronym = string.IsNullOrEmpty(pdc.Field("Acronym")) ? "DIRECT CONNECTED" : pdc.Field("Acronym"), Name = pdc.Field("Name"), CompanyName = pdc.Field("CompanyName"), StatusColor = string.IsNullOrEmpty(pdc.Field("Acronym")) ? "Transparent" : "Gray", Enabled = Convert.ToBoolean(pdc.Field("Enabled")), Expanded = false, DeviceList = new ObservableCollection( from device in resultSet.Tables["DeviceTable"].AsEnumerable() where device.Field("ParentAcronym").ToNonNullString() == pdc.Field("Acronym") && device.ConvertField("AccessID") != GroupAccessID select new RealTimeDevice { ID = device.ConvertNullableField("ID"), Acronym = device.Field("Acronym"), Name = device.Field("Name"), ProtocolName = device.Field("ProtocolName"), VendorDeviceName = device.Field("VendorDeviceName"), ParentAcronym = string.IsNullOrEmpty(device.Field("ParentAcronym")) ? "DIRECT CONNECTED" : device.Field("ParentAcronym"), Expanded = false, StatusColor = device.ConvertNullableField("ID") == null ? "Transparent" : "Gray", Enabled = Convert.ToBoolean(device.Field("Enabled")), MeasurementList = new ObservableCollection( from measurement in resultSet.Tables["MeasurementTable"].AsEnumerable() where measurement.ConvertNullableField("DeviceID") == device.ConvertNullableField("ID") && (measurement.ConvertField("Subscribed") || measurement.ConvertField("Internal") || (settings.ContainsKey("securityMode") && settings["securityMode"].Equals("None", StringComparison.OrdinalIgnoreCase))) //We will only display measurements which are internal or subscribed to avoid confusion. select new RealTimeMeasurement { ID = measurement.Field("ID"), DeviceID = measurement.ConvertNullableField("DeviceID"), SignalID = Guid.Parse(measurement.Field("SignalID").ToString()), PointID = measurement.ConvertField("PointID"), PointTag = measurement.Field("PointTag"), SignalReference = measurement.Field("SignalReference"), Description = measurement.Field("description"), SignalName = measurement.Field("SignalName"), SignalAcronym = measurement.Field("SignalAcronym"), EngineeringUnit = measurement.Field("SignalAcronym") == "FLAG" ? "Hex" : measurement.Field("EngineeringUnits"), Expanded = false, Selected = false, Selectable = measurement.Field("SignalAcronym") == "IPHM" || measurement.Field("SignalAcronym") == "IPHA" || measurement.Field("SignalAcronym") == "VPHM" || measurement.Field("SignalAcronym") == "VPHA" || measurement.Field("SignalAcronym") == "FREQ", LongTimeTag = "N/A", TimeTag = "N/A", Value = "--", Quality = "N/A", Foreground = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)) } ) } ) } ); if (otherMeasurements.Rows.Count > 0) { // Add subscribed measurements from other nodes realTimeStreamList.Add(new RealTimeStream { ID = 0, Acronym = "SUBSCRIBED", Name = "Subscribed Measurements", CompanyName = string.Empty, StatusColor = "Transparent", Enabled = false, Expanded = false, DeviceList = new ObservableCollection( otherMeasurements.Rows .Cast() .Where(measurement => measurement.ConvertNullableField("DeviceID") == null) .Select(measurement => measurement.Field("SignalReference")) .Select(GetSourceName) .Distinct() .Select(source => new RealTimeDevice { ID = 0, Acronym = source, Name = source, ProtocolName = string.Empty, VendorDeviceName = string.Empty, ParentAcronym = "SUBSCRIBED", Expanded = false, StatusColor = "Gray", Enabled = false, MeasurementList = new ObservableCollection( otherMeasurements.Rows .Cast() .Where(measurement => measurement.ConvertNullableField("DeviceID") == null && measurement.Field("SignalReference").StartsWith(source)) .Select(measurement => new RealTimeMeasurement { ID = measurement.Field("ID"), DeviceID = measurement.ConvertNullableField("DeviceID"), SignalID = Guid.Parse(measurement.Field("SignalID").ToString()), PointID = measurement.ConvertField("PointID"), PointTag = measurement.Field("PointTag"), SignalReference = measurement.Field("SignalReference"), Description = measurement.Field("description"), SignalName = measurement.Field("SignalName"), SignalAcronym = measurement.Field("SignalAcronym"), EngineeringUnit = measurement.Field("SignalAcronym") == "FLAG" ? "Hex" : measurement.Field("EngineeringUnits"), Expanded = false, Selected = false, Selectable = measurement.Field("SignalAcronym") == "IPHM" || measurement.Field("SignalAcronym") == "IPHA" || measurement.Field("SignalAcronym") == "VPHM" || measurement.Field("SignalAcronym") == "VPHA" || measurement.Field("SignalAcronym") == "FREQ", LongTimeTag = "N/A", TimeTag = "N/A", Value = "--", Quality = "N/A", Foreground = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)) }) ) }) ) }); } if (resultSet.Tables["MeasurementTable"].Select("DeviceID IS NULL").Length > 0) { // Add direct connected measurements with no associated device (DeviceID IS NULL) realTimeStreamList.Add(new RealTimeStream { ID = 0, Acronym = "CALCULATED", Name = "Calculated Measurements", CompanyName = string.Empty, StatusColor = "Transparent", Enabled = false, Expanded = false, DeviceList = new ObservableCollection( resultSet.Tables["MeasurementTable"].Rows .Cast() .Where(measurement => measurement.ConvertNullableField("DeviceID") == null) .Select(measurement => measurement.Field("SignalReference")) .Select(GetSourceName) .Distinct() .Select(source => new RealTimeDevice { ID = 0, Acronym = source, Name = source, ProtocolName = string.Empty, VendorDeviceName = string.Empty, ParentAcronym = "CALCULATED", Expanded = false, StatusColor = "Gray", Enabled = false, MeasurementList = new ObservableCollection( resultSet.Tables["MeasurementTable"].Rows .Cast() .Where(measurement => measurement.ConvertNullableField("DeviceID") == null && measurement.Field("SignalReference").StartsWith(source)) .Select(measurement => new RealTimeMeasurement { ID = measurement.Field("ID"), DeviceID = measurement.ConvertNullableField("DeviceID"), SignalID = Guid.Parse(measurement.Field("SignalID").ToString()), PointID = measurement.ConvertField("PointID"), PointTag = measurement.Field("PointTag"), SignalReference = measurement.Field("SignalReference"), Description = measurement.Field("description"), SignalName = measurement.Field("SignalName"), SignalAcronym = measurement.Field("SignalAcronym"), EngineeringUnit = measurement.Field("SignalAcronym") == "FLAG" ? "Hex" : measurement.Field("EngineeringUnits"), Expanded = false, Selected = false, Selectable = measurement.Field("SignalAcronym") == "IPHM" || measurement.Field("SignalAcronym") == "IPHA" || measurement.Field("SignalAcronym") == "VPHM" || measurement.Field("SignalAcronym") == "VPHA" || measurement.Field("SignalAcronym") == "FREQ", LongTimeTag = "N/A", TimeTag = "N/A", Value = "--", Quality = "N/A", Foreground = new SolidColorBrush(Color.FromArgb(255, 0, 0, 0)) }) ) }) ) }); } // Assign parent references for real-time measurements foreach (RealTimeStream stream in realTimeStreamList) { foreach (RealTimeDevice device in stream.DeviceList) { device.Parent = stream; foreach (RealTimeMeasurement measurement in device.MeasurementList) { measurement.Parent = device; } } } return realTimeStreamList; } finally { if (createdConnection && database != null) database.Dispose(); } } // Calculate text width internal static double GetTextWidth(string text) { // Just making some assumptions about font and size here for rough assertion on size - would be more accurate to pass in actual font info Typeface tf = new(new FontFamily("Segoe UI"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal); FormattedText ft = new(text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, tf, 14.0D, Brushes.Black, null, TextFormattingMode.Display); return ft.WidthIncludingTrailingWhitespace; // Try this for Silverlight if prior doesn't work: //TextBlock txtMeasure = new TextBlock(); //txtMeasure.FontSize = fontSize; //txtMeasure.Text = text ?? ""; //return txtMeasure.ActualWidth; } /// /// Retrieves type collection. /// /// to connection to database. /// Indicates if selection on UI is optional for this collection. /// type collection. /// This is only a place holder method with no implementation. public static Dictionary GetLookupList(AdoDataConnection database, bool isOptional = false) { return null; } /// /// Saves information into the database. /// /// to connection to database. /// Information about /// String, for display use, indicating success. /// This is only a place holder method with no implementation. public static string Save(AdoDataConnection database, RealTimeStream stream) { return string.Empty; } /// /// Deletes record from the database. /// /// to connection to database. /// ID of the record to be deleted. /// String, for display use, indicating success. /// This is only a place holder method with no implementation. public static string Delete(AdoDataConnection database, int streamID) { return string.Empty; } private static string GetSourceName(string signalReference) { int hyphenIndex; int colonIndex; // Try to parse source name based on properly formatted signal reference (SOURCENAME-XX#) hyphenIndex = signalReference.LastIndexOf('-'); if (hyphenIndex >= 0) return signalReference.Substring(0, hyphenIndex); // Try to parse source name from signal reference formatted like point tag (SOURCENAME:XXXY#). // This format may include company name, but should be the same for all points from the same device colonIndex = signalReference.LastIndexOf(':'); if (colonIndex >= 0) return signalReference.Substring(0, colonIndex); // Default to '__' if empty if (string.IsNullOrWhiteSpace(signalReference)) return "__"; // Assume the value itself can be used to categorize the signal return signalReference; } #endregion } /// /// Represents a real-time stream of device data. /// public class RealTimeDevice : DataModelBase { #region [ Members ] // Fields private int? m_id; private string m_acronym; private string m_acronymTruncated; private string m_name; private string m_protocolName; private string m_vendorDeviceName; private string m_parentAcronym; private bool m_expanded; private string m_statusColor; private bool m_enabled; private double m_maximumSignalReferenceWidth = double.NaN; private double m_maximumShortSignalReferenceWidth = double.NaN; private RealTimeStream m_parent; private ObservableCollection m_measurementList; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public RealTimeDevice() : base(false, false) { } #endregion #region [ Properties ] /// /// Gets or sets ID of the . /// public int? ID { get { return m_id; } set { m_id = value; OnPropertyChanged(nameof(ID)); } } /// /// Gets or sets Acronym of the . /// public string Acronym { get { return m_acronym; } set { m_acronym = value; AcronymTruncated = m_acronym.Substring(m_acronym.LastIndexOf("!") + 1); OnPropertyChanged(nameof(Acronym)); } } /// /// Gets truncated acronym, i.e. removed parent device prefix. This is only for display purpose. /// public string AcronymTruncated { get { return m_acronymTruncated; } set { m_acronymTruncated = value; } } /// /// Gets or sets Name of the /// public string Name { get { return m_name; } set { m_name = value; OnPropertyChanged(nameof(Name)); } } /// /// Gets or sets ProtocolName of the . /// public string ProtocolName { get { return m_protocolName; } set { m_protocolName = value; OnPropertyChanged(nameof(ProtocolName)); } } /// /// Gets or sets VendorDeviceName of the . /// public string VendorDeviceName { get { return m_vendorDeviceName; } set { m_vendorDeviceName = value; OnPropertyChanged(nameof(VendorDeviceName)); } } /// /// Gets or sets ParentAcronym of the . /// public string ParentAcronym { get { return m_parentAcronym; } set { m_parentAcronym = value; OnPropertyChanged(nameof(ParentAcronym)); } } /// /// Gets or sets Expanded flag of the . /// public bool Expanded { get { return m_expanded; } set { m_expanded = value; OnPropertyChanged(nameof(Expanded)); } } /// /// Gets or sets StatusColor of the . /// public string StatusColor { get { return m_statusColor; } set { m_statusColor = value; OnPropertyChanged(nameof(StatusColor)); } } /// /// Gets or sets Enabled flag of the . /// public bool Enabled { get { return m_enabled; } set { m_enabled = value; OnPropertyChanged(nameof(Enabled)); } } /// /// Gets or sets collection of s of the . /// public ObservableCollection MeasurementList { get { return m_measurementList; } set { m_measurementList = value; m_maximumSignalReferenceWidth = double.NaN; m_maximumShortSignalReferenceWidth = double.NaN; OnPropertyChanged(nameof(MeasurementList)); } } /// /// Gets maximum width of signal reference column. /// public double MaximumSignalReferenceWidth { get { if (double.IsNaN(m_maximumSignalReferenceWidth) && m_measurementList != null) m_maximumSignalReferenceWidth = m_measurementList.Max(rtm => RealTimeStream.GetTextWidth(rtm.SignalReference)); return m_maximumSignalReferenceWidth; } set { m_maximumSignalReferenceWidth = value; OnPropertyChanged(nameof(MaximumSignalReferenceWidth)); } } /// /// Gets maximum width of short signal reference column. /// public double MaximumShortSignalReferenceWidth { get { if (double.IsNaN(m_maximumShortSignalReferenceWidth) && m_measurementList != null) m_maximumShortSignalReferenceWidth = m_measurementList.Max(rtm => RealTimeStream.GetTextWidth(rtm.ShortSignalReference)); return m_maximumShortSignalReferenceWidth; } set { m_maximumShortSignalReferenceWidth = value; OnPropertyChanged(nameof(MaximumShortSignalReferenceWidth)); } } /// /// Gets or sets the parent. /// public RealTimeStream Parent { get { return m_parent; } set { m_parent = value; OnPropertyChanged(nameof(Parent)); } } #endregion } /// /// Represents a real-time stream of Measurement data. /// public class RealTimeMeasurement : DataModelBase { #region [ Members ] // Fields private int? m_deviceID; private Guid m_signalID; private string m_id; private int m_pointID; private string m_pointTag; private string m_signalReference; private string m_description; private string m_signalName; private string m_signalAcronym; private string m_engineeringUnit; private bool m_expanded; private bool m_selected; private bool m_selectable; private string m_longTimeTag; private string m_timeTag; private string m_value; private string m_quality; private long m_lastUpdated; private SolidColorBrush m_foreground; private RealTimeDevice m_parent; #endregion #region [ Constructors ] /// /// Creates a new instance of the class. /// public RealTimeMeasurement() : base(false, false) { } #endregion #region [ Properties ] /// /// Gets or sets parent . /// public RealTimeDevice Parent { get { return m_parent; } set { m_parent = value; OnPropertyChanged(nameof(MaximumSignalReferenceWidth)); } } /// /// Gets maximum with of signal reference column. /// public string MaximumSignalReferenceWidth { get { if (m_parent == null) return "Auto"; double width = m_parent.MaximumSignalReferenceWidth; if (double.IsNaN(width)) return "Auto"; return width.ToString(); } set { } } /// /// Gets maximum with of signal reference column. /// public string MaximumShortSignalReferenceWidth { get { if (m_parent == null) return "Auto"; double width = m_parent.MaximumShortSignalReferenceWidth; if (double.IsNaN(width)) return "Auto"; return width.ToString(); } set { } } /// /// Gets or sets DeviceID for . /// public int? DeviceID { get { return m_deviceID; } set { m_deviceID = value; OnPropertyChanged(nameof(DeviceID)); } } /// /// Gets or sets SignalID for . /// public Guid SignalID { get { return m_signalID; } set { m_signalID = value; OnPropertyChanged(nameof(SignalID)); } } /// /// Gets or sets ID for . /// public string ID { get { return m_id; } set { m_id = value; OnPropertyChanged(nameof(ID)); } } /// /// Gets or sets PointID for . /// public int PointID { get { return m_pointID; } set { m_pointID = value; OnPropertyChanged(nameof(PointID)); } } /// /// Gets or sets PointTag for . /// public string PointTag { get { return m_pointTag; } set { m_pointTag = value; OnPropertyChanged(nameof(PointTag)); } } /// /// Gets or sets SignalReference for . /// public string SignalReference { get { return m_signalReference; } set { m_signalReference = value; OnPropertyChanged(nameof(SignalReference)); OnPropertyChanged(nameof(ShortSignalReference)); } } /// /// Gets a shortened version of the signal reference. /// public string ShortSignalReference { get { return m_signalReference.TrimWithEllipsisMiddle(16); } } /// /// Gets or sets Description for . /// public string Description { get { return m_description; } set { m_description = value; OnPropertyChanged(nameof(Description)); } } /// /// Gets or sets SignalName for . /// public string SignalName { get { return m_signalName; } set { m_signalName = value; OnPropertyChanged(nameof(SignalName)); } } /// /// Gets or sets SignalAcronym for . /// public string SignalAcronym { get { return m_signalAcronym; } set { m_signalAcronym = value; OnPropertyChanged(nameof(SignalAcronym)); } } /// /// Gets or sets Engineering Unit for . /// public string EngineeringUnit { get { return m_engineeringUnit; } set { m_engineeringUnit = value; OnPropertyChanged(nameof(EngineeringUnit)); OnPropertyChanged(nameof(Value)); } } /// /// Gets or sets Expanded flag for . /// public bool Expanded { get { return m_expanded; } set { m_expanded = value; OnPropertyChanged(nameof(Expanded)); } } /// /// Gets or sets Selected flag for . /// public bool Selected { get { return m_selected; } set { m_selected = value; OnPropertyChanged(nameof(Selected)); } } /// /// Gets or sets a flag if can be selected on UI or not. /// public bool Selectable { get { return m_selectable; } set { m_selectable = value; OnPropertyChanged(nameof(Selectable)); } } /// /// Gets or sets LongTimeTag for data. /// public string LongTimeTag { get { return m_longTimeTag; } set { m_longTimeTag = value; OnPropertyChanged(nameof(LongTimeTag)); } } /// /// Gets or sets TimeTag for data. /// public string TimeTag { get { return m_timeTag; } set { m_timeTag = value; OnPropertyChanged(nameof(TimeTag)); } } /// /// Gets or sets Value for data. /// public string Value { get { bool hex; uint hexValue; hex = m_engineeringUnit.ToNonNullString().Trim().ToUpperInvariant() == "HEX"; if (hex && uint.TryParse(m_value, out hexValue)) return hexValue.ToString("X8"); return m_value; } set { m_value = value; OnPropertyChanged(nameof(Value)); } } /// /// Gets or sets Quality for data. /// public string Quality { get { return m_quality; } set { m_quality = value; OnPropertyChanged(nameof(Quality)); } } /// /// Gets or sets LastUpdated for data. /// public long LastUpdated { get { return m_lastUpdated; } set { m_lastUpdated = value; OnPropertyChanged(nameof(LastUpdated)); } } /// /// Gets or sets Foreground for to display on the screen. /// public SolidColorBrush Foreground { get { return m_foreground; } set { m_foreground = value; OnPropertyChanged(nameof(Foreground)); } } #endregion } }