using System; using System.Collections.Generic; using System.Linq; using System.Text; using TradeIdeas.TIProData; using TradeIdeas.XML; using System.Windows.Forms; using System.Drawing; using System.Xml; namespace TradeIdeas.TIProGUI { [XmlSerializerInit] public class GuiDataNode : DataNode { private static Control _control; public static void Init() { if (null != _control) // Already initialized. return; System.Diagnostics.Debug.Assert(Application.MessageLoop); _control = new Control(); var keepTheCompilerFromComplainingIJustNeedTheSideEffects = _control.Handle; // CreateControl() actually works in this case. But that method comes with some // caveats. Calling Handle is the preferred solution. //_control.CreateControl(); //System.Diagnostics.Debug.Assert(_control.IsHandleCreated); } private DataNode _child; private GuiDataNode(object args) { AddAutoLink(((Factory)args).Find(out _child, Forward)); // What about the initial value? Is it safe to read it from another thread? Value = _child.Value; //System.Diagnostics.Debug.WriteLine("GuiDataNode initializing Value to " + Value); } private void Forward() { object value = _child.Value; //System.Diagnostics.Debug.WriteLine("GuiDataNode.Forward value = " + value); // TODO Currently we're forwarding everything with no delay. We should // try to combine requests. _control.BeginInvokeIfRequired(() => Value = value); } /// /// Wrap the given Factory in a GUI data node Factory. The result will be a new factory /// with the same data, but the updates will only come in the GUI thread. /// /// In some cases we can detect that the original factory is already safe. It only /// makes callbacks in the GUI thread. For example, maybe the input to this function /// was the result of a previous call to this function. In this case this function /// will return its input without modification. /// /// If and only if the input is null, the result will be null. /// /// /// public static Factory GetFactory(Factory input) { if (null == input) return null; object metaData = input.MetaData; if ((metaData is GuiDataNode) || (metaData is ConstantDataNode)) // We could add a GuiDataNode Factory around this factory, but it wouldn't // do anything. Return the origianl as is. The result would be functionally // the same if we didn't have this if statement. This is an optimization. return input; else // Create the factory as requested. return new FactoryWithHash(args => new GuiDataNode(args), typeof(GuiDataNode), input); } /// /// If the input is a Factory wrapped in a GuiDataNode Factory, return the original factory. /// If the input is some other type of Factory, return it as is. /// If the input is anything else, return null. /// /// /// public static Factory Deconstruct(object input) { FactoryWithHash original = input as FactoryWithHash; if ((null != original) && (typeof(GuiDataNode).Equals(original.MetaData))) // This is a GUI data node. Return the original argument. // This is the inverse of GetFactory(), and the ideal case of Deconstruct(). return (Factory)original.Arguments; if (input is Factory) // This is some other type of factory. Return it as is. That's consistent with the // way we expect people to use this. The caller has a factory and doesn't know // if it's one of ours or not. return (Factory)input; else // This is not a factory. We always want to return a factory. So return null // to say we failed. return null; } } [XmlSerializerInit("*")] public class GradientInfoDataNode : DataNode { private readonly GradientInfo _gradientInfo; private DataNode _child; private GradientInfoDataNode(object arguments) { StaticList args = (StaticList)arguments; System.Diagnostics.Debug.Assert(args.Length == 2); _gradientInfo = (GradientInfo)args[0]; Factory factory = (Factory)args[1]; AddAutoLink(factory.Find(out _child, Update)); Update(); } private void Update() { object inputAsObject = _child.Value; if (!(inputAsObject is Double)) Value = null; else if (_gradientInfo.Empty()) Value = null; else Value = _gradientInfo.GetColor((double)inputAsObject); } public static Factory GetFactory(GradientInfo gradientInfo, Factory basedOn) { return new FactoryWithHash(args => new GradientInfoDataNode(args), typeof(GradientInfoDataNode), StaticList.Create(gradientInfo, basedOn)); } /// /// Perfect for use with TransformationDataNode. /// /// If this is the background color... /// ...this will make a nice foreground color. public static object AltColor(object originalColor) { if (!(originalColor is Color)) return null; else return GradientInfo.AltColor((Color)originalColor); } private const string GRADIENT_INFO = "gradient_info"; private static void XmlSerializerInit() { XmlSerializer.RegisterDecoder(GRADIENT_INFO, DecodeGradientInfo); XmlSerializer.RegisterEncoder(typeof(GradientInfo), EncodeGradientInfo); XmlSerializer.AddKnownObject(AltColor, "ALT_COLOR"); FactoryWithHash.RegisterStandardDecoder(typeof(GradientInfoDataNode)); } private static object DecodeGradientInfo(XmlElement parent) { XmlElement value = parent.Node(XmlSerializer.VALUE); if (null == value) throw new ArgumentException("Can't find VALUE for GradientInfo."); else return new GradientInfo(value); } private static void EncodeGradientInfo(object toEncode, XmlElement body) { GradientInfo gradientInfo = (GradientInfo)toEncode; gradientInfo.SaveAs(body, XmlSerializer.VALUE); body.SetProperty(XmlSerializer.TYPE, GRADIENT_INFO); } } public static class GuiDataNodeHelper { public static void AutoRelease(this DataNode.Link link, System.ComponentModel.Component component) { component.Disposed += delegate { link.Release(); }; } } }