/* * Copyright © Northwoods Software Corporation, 1998-2015. All Rights Reserved. * * Restricted Rights: Use, duplication, or disclosure by the U.S. * Government is subject to restrictions as set forth in subparagraph * (c) (1) (ii) of DFARS 252.227-7013, or in FAR 52.227-19, or in FAR * 52.227-14 Alt. III, as applicable. * * This software is proprietary to and embodies the confidential * technology of Northwoods Software Corporation. Possession, use, or * copying of this software and media is authorized only pursuant to a * valid written license from Northwoods or an authorized sublicensor. */ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Xml.Linq; using System.Windows; // for Point namespace Northwoods.GoXam.Model { /// /// A simple representation of node data for /// that supports property change notification, copying, and undo /// via the INotifyPropertyChanged, ICloneable, and interfaces. /// /// the Type of a value uniquely identifying a node data in the model /// /// /// This provides a standard implementation of /// /// data that represents nodes and includes a property for /// referring to the "parent" node, and a property for holding a list of /// "children" nodes. /// You can use this class if you do not already have your own application class /// holding information about nodes and if you want to inherit from an existing /// class so that you can just add your own properties. Here's a simple example: /// /// /// /// [Serializable] /// public class MyData : TreeModelNodeData<String> { /// public MyData() { } /// /// public String Name { /// get { return _Name; } /// set { if (_Name != value) { String old = _Name; _Name = value; RaisePropertyChanged("Name", old, value); } } /// } /// private String _Name; /// /// public String Address { /// get { return _Address; } /// set { if (_Address != value) { String old = _Address; _Address = value; RaisePropertyChanged("Address", old, value); } } /// } /// private String _Address; /// } /// /// /// /// Note that property setters need to raise the model's Changed event, /// so that the model knows about changes in the data and can then update the diagram. /// You should call only when the value has actually changed, /// and you should pass both the previous and the new values, in order to support undo/redo. /// /// /// For both Silverlight and WPF you should override the method /// if the fields contain data that should not be shared between copies. /// For WPF the properties that you define should also be serializable, /// in order for the data to be copiable, especially to and from the clipboard. /// /// /// If you add properties to this node data class, and if you are using the /// and /// methods, /// you should override the and /// methods to add new attributes and/or elements as needed, /// /// /// Normally, each should have a unique value within the model. /// You can maintain that yourself, by setting the to unique values /// before adding the node data to the model's collection of nodes. /// Or you can ensure this by overriding the /// /// method. The override (or the setting of the same-named delegate in /// ) /// is required if nodes might be copied within the model. /// /// /// If you want each node to keep a reference to its "parent" node, /// you can use the property. /// If you want each node to keep a list of reference to its "children" nodes, /// you can use the property, which is a list of node keys. /// You can use both and at the same time. /// /// [Serializable] public class TreeModelNodeData : INotifyPropertyChanged, IChangeDataValue, ICloneable { /// /// The default constructor produces an empty object. /// /// /// If the NodeKey is Guid, this automatically assigns the to be a new Guid. /// public TreeModelNodeData() { if (typeof(NodeKey).IsAssignableFrom(typeof(Guid))) { _Key = (NodeKey)((object)Guid.NewGuid()); } } /// /// This constructor also initializes the property. /// /// public TreeModelNodeData(NodeKey key) { _Key = key; } /// /// Create a copy of this data; this implements the ICloneable interface. /// /// /// /// /// When you add your own state in a subclass, and when you expect to be able to copy the data, /// you should override this method in your derived class when it has some fields that are object references. /// Your override method should first call base.Clone() to get the newly copied object. /// The result should be the object you return, /// after performing any other deeper copying of referenced objects that you deem necessary, /// and after removing references that should not be shared (such as to cached data structures). /// /// /// The standard implementation of this method is to do a shallow copy, by Object.MemberwiseClone(), /// and reinitialize the and properties. /// You do not need to override this method if you have only added some fields/properties /// that are values or are references to intentionally shared objects. /// /// public virtual object Clone() { TreeModelNodeData d = (TreeModelNodeData)MemberwiseClone(); d.PropertyChanged = null; d._ParentKey = default(NodeKey); d._ChildKeys = new ObservableCollection(); return d; } /// /// This event implements the interface, /// so that both the model and the dependency object system can be informed /// of changes to property values. /// [field: NonSerializedAttribute()] public event PropertyChangedEventHandler PropertyChanged; /// /// Raise the event. /// /// public virtual void OnPropertyChanged(ModelChangedEventArgs e) { if (this.PropertyChanged != null) this.PropertyChanged(this, e); } /// /// Call this method from property setters to raise the event. /// /// the property name /// the value before the property was set /// the new value /// /// Only call this method when the property value actually changes. /// The and values are needed /// to support undo/redo. /// protected void RaisePropertyChanged(String pname, Object oldval, Object newval) { OnPropertyChanged(new ModelChangedEventArgs(pname, this, oldval, newval)); } /// /// For debugging, use the property as this object's default text rendering. /// /// public override String ToString() { return (this.Key != null) ? this.Key.ToString() : "(nokey)"; } /// /// This method implements the interface, /// used to perform state changes for undo and redo. /// /// an edit describing the change to be performed /// true if undoing; false if redoing /// /// Unless you override this method to explicitly handle each property that you define, /// this implementation uses reflection to set the property. /// public virtual void ChangeDataValue(ModelChangedEventArgs e, bool undo) { if (e == null) return; if (e.PropertyName == "Location") { this.Location = (Point)e.GetValue(undo); } else if (e.PropertyName == "Text") { this.Text = (String)e.GetValue(undo); } else if (e.PropertyName == "Key") { this.Key = (NodeKey)e.GetValue(undo); } else if (e.PropertyName == "IsTreeExpanded") { this.IsTreeExpanded = (bool)e.GetValue(undo); } else if (e.PropertyName == "WasTreeExpanded") { this.WasTreeExpanded = (bool)e.GetValue(undo); } else if (e.PropertyName == "ParentKey") { this.ParentKey = (NodeKey)e.GetValue(undo); } else if (e.PropertyName == "ChildKeys") { this.ChildKeys = (IList)e.GetValue(undo); } else if (e.PropertyName == "Category") { this.Category = (String)e.GetValue(undo); } else if (e.Change == ModelChange.Property) { if (!ModelHelper.SetProperty(e.PropertyName, e.Data, e.GetValue(undo))) { ModelHelper.Error("ERROR: Unrecognized property name: " + e.PropertyName != null ? e.PropertyName : "(noname)" + " in TreeModelNodeData.ChangeDataValue"); } } } /// /// Constructs a Linq for XML XElement holding the data of this node. /// /// the name of the new XElement /// an initialized XElement /// /// /// This constructs a new XElement and adds an XAttribute for each simple property /// that has a value different from its default value. /// For each property that is a collection, it adds an XElement with nested item elements. /// This does not add an element if the collection is empty. /// /// /// Because the type might be a type for which we have an implementation /// to convert to and from strings for XML, this calls the method, /// which you can override. /// /// /// This is implemented as: /// /// public virtual XElement MakeXElement(XName n) { /// XElement e = new XElement(n); /// e.Add(XHelper.Attribute<NodeKey>("Key", this.Key, default(NodeKey), ConvertNodeKeyToString)); /// e.Add(XHelper.Attribute("Category", this.Category, "")); /// e.Add(XHelper.Attribute<NodeKey>("ParentKey", this.ParentKey, default(NodeKey), ConvertNodeKeyToString)); /// e.Add(XHelper.Elements<NodeKey>("ChildKeys", "Key", this.ChildKeys, ConvertNodeKeyToString)); /// e.Add(XHelper.Attribute("IsTreeExpanded", this.IsTreeExpanded, true)); /// e.Add(XHelper.Attribute("WasTreeExpanded", this.WasTreeExpanded, false)); /// e.Add(XHelper.Attribute("Location", this.Location, new Point(Double.NaN, Double.NaN))); /// e.Add(XHelper.Attribute("Text", this.Text, "")); /// return e; /// } /// /// /// /// If you add properties to this node data class, and if you are using the /// and /// methods, /// you should override this method to add new attributes and/or elements as needed, /// and you should override . /// /// public virtual XElement MakeXElement(XName n) { XElement e = new XElement(n); e.Add(XHelper.Attribute("Key", this.Key, default(NodeKey), ConvertNodeKeyToString)); e.Add(XHelper.Attribute("Category", this.Category, "")); e.Add(XHelper.Attribute("ParentKey", this.ParentKey, default(NodeKey), ConvertNodeKeyToString)); e.Add(XHelper.Elements("ChildKeys", "Key", this.ChildKeys, ConvertNodeKeyToString)); e.Add(XHelper.Attribute("IsTreeExpanded", this.IsTreeExpanded, true)); e.Add(XHelper.Attribute("WasTreeExpanded", this.WasTreeExpanded, false)); e.Add(XHelper.Attribute("Location", this.Location, new Point(Double.NaN, Double.NaN))); e.Add(XHelper.Attribute("Text", this.Text, "")); return e; } /// /// Initialize this node data with data held in a Linq for XML XElement. /// /// the XElement /// /// /// This sets this node data's properties by reading the data from attributes and nested elements /// of the given XElement. /// /// /// Because the type might be a type for which we have an implementation /// to convert to and from strings for XML, this calls the method, /// which you can override. /// /// /// This is implemented as: /// /// public virtual void LoadFromXElement(XElement e) { /// this.Key = XHelper.Read<NodeKey>("Key", e, default(NodeKey), ConvertStringToNodeKey); /// this.Category = XHelper.Read("Category", e, ""); /// this.ParentKey = XHelper.Read<NodeKey>("ParentKey", e, default(NodeKey), ConvertStringToNodeKey); /// this.ChildKeys = (IList<NodeKey>)XHelper.ReadElements<NodeKey>(e.Element("ChildKeys"), "Key", new ObservableCollection<NodeKey>(), ConvertStringToNodeKey); /// this.IsTreeExpanded = XHelper.Read("IsTreeExpanded", e, true); /// this.WasTreeExpanded = XHelper.Read("WasTreeExpanded", e, false); /// this.Location = XHelper.Read("Location", e, new Point(Double.NaN, Double.NaN)); /// this.Text = XHelper.Read("Text", e, ""); /// } /// /// /// /// If you add properties to this node data class, and if you are using the /// and /// methods, /// you should override this method to add new attributes and/or elements as needed, /// and you should override . /// /// public virtual void LoadFromXElement(XElement e) { if (e == null) return; this.Key = XHelper.Read("Key", e, default(NodeKey), ConvertStringToNodeKey); this.Category = XHelper.Read("Category", e, ""); this.ParentKey = XHelper.Read("ParentKey", e, default(NodeKey), ConvertStringToNodeKey); this.ChildKeys = (IList)XHelper.ReadElements(e.Element("ChildKeys"), "Key", new ObservableCollection(), ConvertStringToNodeKey); this.IsTreeExpanded = XHelper.Read("IsTreeExpanded", e, true); this.WasTreeExpanded = XHelper.Read("WasTreeExpanded", e, false); this.Location = XHelper.Read("Location", e, new Point(Double.NaN, Double.NaN)); this.Text = XHelper.Read("Text", e, ""); } /// /// Convert a key value to a string. /// /// /// a String from which can recover the original key value /// /// Currently this handles NodeKey types that are String, Int32, Double, /// DateTime, TimeSpan, or Guid. /// Override this method to handle additional types. /// protected virtual String ConvertNodeKeyToString(NodeKey key) { if (typeof(NodeKey).IsAssignableFrom(typeof(String))) { return XHelper.ToString((String)(Object)key); } else if (typeof(NodeKey).IsAssignableFrom(typeof(int))) { return XHelper.ToString((int)(Object)key); } else if (typeof(NodeKey).IsAssignableFrom(typeof(double))) { return XHelper.ToString((double)(Object)key); } else if (typeof(NodeKey).IsAssignableFrom(typeof(Guid))) { return XHelper.ToString((Guid)(Object)key); } else if (typeof(NodeKey).IsAssignableFrom(typeof(DateTime))) { return XHelper.ToString((DateTime)(Object)key); } else if (typeof(NodeKey).IsAssignableFrom(typeof(TimeSpan))) { return XHelper.ToString((TimeSpan)(Object)key); } else if (typeof(NodeKey).IsAssignableFrom(typeof(Decimal))) { return XHelper.ToString((Decimal)(Object)key); } else { ModelHelper.Error("Cannot convert NodeKey type to String: " + typeof(NodeKey).ToString() + " -- override GraphLinksModelNodeData.ConvertNodeKeyToString"); return key.ToString(); } } /// /// Convert a string to a key value. /// /// /// a /// /// Currently this handles NodeKey types that are String, Int32, Double, /// DateTime, TimeSpan, or Guid. /// Override this method to handle additional types. /// protected virtual NodeKey ConvertStringToNodeKey(String s) { if (typeof(NodeKey).IsAssignableFrom(typeof(String))) { return (NodeKey)(Object)XHelper.ToString(s); } else if (typeof(NodeKey).IsAssignableFrom(typeof(int))) { return (NodeKey)(Object)XHelper.ToInt32(s); } else if (typeof(NodeKey).IsAssignableFrom(typeof(double))) { return (NodeKey)(Object)XHelper.ToDouble(s); } else if (typeof(NodeKey).IsAssignableFrom(typeof(Guid))) { return (NodeKey)(Object)XHelper.ToGuid(s); } else if (typeof(NodeKey).IsAssignableFrom(typeof(DateTime))) { return (NodeKey)(Object)XHelper.ToDateTime(s); } else if (typeof(NodeKey).IsAssignableFrom(typeof(TimeSpan))) { return (NodeKey)(Object)XHelper.ToTimeSpan(s); } else if (typeof(NodeKey).IsAssignableFrom(typeof(Decimal))) { return (NodeKey)(Object)XHelper.ToDecimal(s); } else { ModelHelper.Error("Cannot convert String to NodeKey type: " + typeof(NodeKey).ToString() + " -- override GraphLinksModelNodeData.ConvertStringToNodeKey"); return default(NodeKey); } } /// /// Gets or sets the key property for this node data. /// /// /// The type is the parameterized type , /// which must be compatible with and should the same as the NodeKey type parameter /// used for the model, . /// public NodeKey Key { get { return _Key; } set { if (!EqualityComparer.Default.Equals(_Key, value)) { NodeKey old = _Key; _Key = value; RaisePropertyChanged("Key", old, value); } } } private NodeKey _Key; /// /// Gets or sets a String that names the category to which the node data belongs. /// /// /// /// The default value is an empty string. /// /// public String Category { get { return _Category; } set { if (_Category != value) { String old = _Category; _Category = value; RaisePropertyChanged("Category", old, value); } } } private String _Category = ""; /// /// Gets or sets the key property referring to the "parent" node, if any. /// public NodeKey ParentKey { get { return _ParentKey; } set { if (!EqualityComparer.Default.Equals(_ParentKey, value)) { NodeKey old = _ParentKey; _ParentKey = value; RaisePropertyChanged("ParentKey", old, value); } } } private NodeKey _ParentKey; /// /// Gets or sets the list of keys identifying the "children" nodes. /// /// /// By default this is an empty . /// Usually you will not need to set this, but if you do, you may want to make sure the /// new value also implements the interface. /// public IList ChildKeys { get { if (_ChildKeys == null) _ChildKeys = new ObservableCollection(); return _ChildKeys; } set { if (_ChildKeys != value) { IList old = _ChildKeys; _ChildKeys = value; RaisePropertyChanged("ChildKeys", old, value); } } } private IList _ChildKeys; /// /// Gets or sets whether this node is in the "expanded" state. /// /// /// By default this is true. /// /// /// Although this data property is defined for your convenience, the model does not know about this property. /// public bool IsTreeExpanded { get { return _IsTreeExpanded; } set { if (_IsTreeExpanded != value) { bool old = _IsTreeExpanded; _IsTreeExpanded = value; RaisePropertyChanged("IsTreeExpanded", old, value); } } } private bool _IsTreeExpanded = true; /// /// Gets or sets whether this node had been "expanded" when its parent node was "collapsed". /// /// /// By default this is true. This is meaningful only when the subtree is not expanded. /// /// /// Although this data property is defined for your convenience, the model does not know about this property. /// public bool WasTreeExpanded { get { return _WasTreeExpanded; } set { if (_WasTreeExpanded != value) { bool old = _WasTreeExpanded; _WasTreeExpanded = value; RaisePropertyChanged("WasTreeExpanded", old, value); } } } private bool _WasTreeExpanded = false; /// /// Gets or sets a Point that is the location of the node in model coordinates. /// /// /// The default value is (NaN, NaN). /// /// /// Although this data property is defined for your convenience, the model does not know about this property. /// public Point Location { get { return _Location; } set { if (_Location != value) { // NaN != NaN, but don't treat them as different! if (Double.IsNaN(_Location.X) && Double.IsNaN(value.X) && Double.IsNaN(_Location.Y) && Double.IsNaN(value.Y)) return; Point old = _Location; _Location = value; RaisePropertyChanged("Location", old, value); } } } private Point _Location = new Point(Double.NaN, Double.NaN); /// /// Gets or sets a String that is associated with the node. /// /// /// The default value is an empty string. /// /// /// Although this data property is defined for your convenience, the model does not know about this property. /// public String Text { get { return _Text; } set { if (_Text != value) { String old = _Text; _Text = value; RaisePropertyChanged("Text", old, value); } } } private String _Text = ""; } }