using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; namespace TradeIdeas.MiscSupport { /// /// This is a fairly generic way of representing a dataset. The data could be almost /// anything, but the intent is for all the data in each row of a table to be stored in /// one RowData object. For example one alert in TI Pro, or all the data about one /// stock in a market view, would be one RowData object. /// public class RowData { private bool _isVisible = true; //helpful for row-hiding private bool _isRecent = false; private int _id = 0; //helpful for row-hiding /// /// This contains the actual data. You can acces it directly. You can modify it. /// That might be useful if you are computing a derived field and caching the result. /// public Dictionary Data = new Dictionary(); /// /// Look up the value. /// /// This is the key that you are looking up. Keys from the /// server will always be strings. Keys made up on the client side should /// be other types of objects, to avoid a conflict. /// /// Return null if the value is not found. public object GetAsObject(object key) { // Rewrite using TryGetValue instead of ContainsKey. // We were getting KeyNotFoundExceptions after successfully verifing // the dictionary had the key. This logic allows us the get the dictionary // object in one call. object value; if (Data.TryGetValue(key, out value)) return value; else return null; } /// /// Looks up the value in the dictionary. If value isn't present /// or it can't be cast to a DateTime? then it returns null, otherwise /// it returns the DateTime value. /// /// /// public DateTime? GetAsDateTime(object key) { // Rewrite using TryGetValue instead of ContainsKey. // We were getting KeyNotFoundExceptions after successfully verifing // the dictionary had the key. This logic allows us the get the dictionary // object in one call. object value; if (Data.TryGetValue(key, out value)) { DateTime? asDateTime = value as DateTime?; if (null != asDateTime) return asDateTime; else return null; } return null; } /// /// Looks up the value in the dictionary. If value isn't present /// or it can't be cast to a bool? then it returns null, otherwise /// it returns the bool value. /// /// /// public bool? GetAsBool(object key) { // Rewrite using TryGetValue instead of ContainsKey. // We were getting KeyNotFoundExceptions after successfully verifing // the dictionary had the key. This logic allows us the get the dictionary // object in one call. object value; if (Data.TryGetValue(key, out value)) { bool? asBool = value as bool?; if (null != asBool) return asBool; else return null; } else return null; } /// /// This looks up the value in the dictionary, and provides a default /// (rather than an exception) if the value is not found. We always call /// ToString to convert the value into a string, if it is found. We /// expect the value to really be a string (or to be missing) but this /// seems like a reasonable course of action in any case. /// /// The key to look for in the table. /// The default to use if we can't find the string. /// The value we found, or the specified default public string GetAsString(object StringKey, string ifNotFound = "") { // Rewrite using TryGetValue instead of ContainsKey. // We were getting KeyNotFoundExceptions after successfully verifing // the dictionary had the key. This logic allows us the get the dictionary // object in one call. object value; if (Data.TryGetValue(StringKey, out value)) { double? asDouble = value as double?; if (asDouble.HasValue) return ServerFormats.ToString(asDouble.Value); else if (null != value) return value.ToString(); else return ifNotFound; } else return ifNotFound; } public Color GetAsColor(object stringKey, Color defaultColor) { var result = defaultColor; if (!Data.TryGetValue(stringKey, out var value)) return result; if (value is Color asColor) result = asColor; return result; } public double GetAsDouble(object StringKey, double defaultValue) { // Rewrite using TryGetValue instead of ContainsKey. // We were getting KeyNotFoundExceptions after successfully verifing // the dictionary had the key. This logic allows us the get the dictionary // object in one call. double result = defaultValue; object value; if (Data.TryGetValue(StringKey, out value)) { if (null != value) { // the object might be a double and not a string. In that case we need to treat it as such // and not run it through ToString() which could have problems with non-english regional settings. double? asDouble = value as double?; if (asDouble.HasValue) result = asDouble.Value; else { decimal? asDecimal = value as decimal?; if (asDecimal.HasValue) result = (double)asDecimal.Value; else { double d; if (ServerFormats.TryParse(value.ToString(), out d)) result = d; } } } } return result; } // These will cache the conversion. StringKey represents the way the data came // from the server. CacheKey is something new. A good value for a cache key // would be a column object or a the type of a column object. However, multiple // columns of different types can certainly share a value. The CacheKey can // be anything, but it's best to avoid strings as that could conflict with the // input from the server. public double? GetAsDouble(object StringKey, object CacheKey) { // Rewrite using TryGetValue instead of ContainsKey. // We were getting KeyNotFoundExceptions after successfully verifing // the dictionary had the key. This logic allows us the get the dictionary // object in one call. object value; if (null != CacheKey && Data.TryGetValue(CacheKey, out value)) return (double?)value; double? result = null; if (Data.TryGetValue(StringKey, out value)) { if (null != value) { double d; if (ServerFormats.TryParse(value.ToString(), out d)) result = d; } } if (null != CacheKey) Data[CacheKey] = result; return result; // Notice the error checking. If the given column is missing or blank, we // return null. If it contains something that can't parse, we return null. // We are very forgiving when the server sends us crap. However, if you // misuse the cache you can cause runtime errors. If you put in a bad value // for CacheKey -- perhaps you use the same CacheKey to convert to an // integer and to a double -- you can get a runtime error. } public double GetAsDouble(object StringKey, object CacheKey, double defaultValue) { return GetAsDouble(StringKey, CacheKey) ?? defaultValue; } public int? GetAsInt(object StringKey, object CacheKey) { // Rewrite using TryGetValue instead of ContainsKey. // We were getting KeyNotFoundExceptions after successfully verifing // the dictionary had the key. This logic allows us the get the dictionary // object in one call. object value; if (null != CacheKey && Data.TryGetValue(CacheKey, out value)) return (int?)value; int? result = null; if (Data.TryGetValue(StringKey, out value)) { if (null != value) { int i; if (ServerFormats.TryParse(value.ToString(), out i)) result = i; } } if (null != CacheKey) Data[CacheKey] = result; return result; // Notice the error checking. If the given column is missing or blank, we // return null. If it contains something that can't parse, we return null. // We are very forgiving when the server sends us crap. However, if you // misuse the cache you can cause runtime errors. If you put in a bad value // for CacheKey -- perhaps you use the same CacheKey to convert to an // integer and to a double -- you can get a runtime error. } public int GetAsInt(object StringKey, object CacheKey, int defaultValue) { return GetAsInt(StringKey, CacheKey) ?? defaultValue; } // This is aimed at developers, not end users. public override string ToString() { StringBuilder result = new StringBuilder("RowData("); bool first = true; foreach (KeyValuePair kvp in Data) { if (first) first = false; else result.Append("; "); if (!(kvp.Key is string)) { result.Append('['); result.Append(kvp.Key.GetType().Name); result.Append(']'); } result.Append(kvp.Key.ToString()); result.Append(" => "); if (null == kvp.Value) result.Append("null"); else { if (!(kvp.Value is string)) { result.Append('['); result.Append(kvp.Value.GetType().Name); result.Append(']'); } result.Append(kvp.Value.ToString()); } } result.Append(')'); return result.ToString(); } /// /// Clones this instance. /// /// public RowData Clone() { RowData copy = new RowData(); foreach (KeyValuePair kvp in Data) { copy.Data.Add(kvp.Key, kvp.Value); } return copy; } public void setRecent(bool r) { _isRecent = r; } public bool isRecent() { return _isRecent; } public bool IsVisible() { return _isVisible; } public void setVisible(bool value) { _isVisible = value; } public int getId() { return _id; } public void setId(int value) { _id = value; } /*I would have rather used the below combo get;set(IsRecentOrNew). However, when starting up the app and before the data starts streaming in, I see a "phantom column" entitled "IsRecentOrNew" shown on the form at the left corner. Of course, when the data starts streaming in, it is overriden by the columns that are supposed to be there. However, before that happens, we see that phantom column. If one happens to double click on that column, an exception is thrown. Reverting to the "classical way" of getting and setting (methods "setRecent" and "isRecent" eliminated that issue...). This format (using the {get;set} also introduces weird exceptions being thrown. This was noticed with the AI window and toplist form when the _isVisible and _id used the auto-implemented properties instead of * the "normal" way of doing the setting and getting with with separate methods...*/ // public bool IsRecentOrNew // { get; set; } } }