/* * 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 with support for subgraphs, /// including properties for specifying the containing subgraph node key /// and/or the collection of member node keys. /// 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 : GraphLinksModelNodeData<String> { /// public MyData() { } /// /// public String Color { /// get { return _Color; } /// set { if (_Color != value) { String old = _Color; _Color = value; RaisePropertyChanged("Color", old, value); } } /// } /// private String _Color = "White"; /// /// 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; /// } /// /// Then you can bind to these data properties in the DataTemplate for your nodes: /// /// <DataTemplate x:Key="NodeTemplate"> /// <Border BorderBrush="Black" BorderThickness="1" CornerRadius="5" Padding="5" /// Background="{Binding Path=Data.Color, Converter={StaticResource theColorConverter}}" /// go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"> /// <StackPanel> /// <TextBlock Text="{Binding Path=Data.Name}" HorizontalAlignment="Left" /> /// <TextBlock Text="{Binding Path=Data.Address}" HorizontalAlignment="Left" /> /// </StackPanel> /// </Border> /// </DataTemplate> /// /// /// /// 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 the containing ("parent") group, /// you can use the property. /// If you want subgraph data to keep a list of "references" to the contained /// ("children") nodes, you can use the property. /// You can use both properties at the same time. /// /// /// This class is not useful with or . /// /// [Serializable] public class GraphLinksModelNodeData : 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 GraphLinksModelNodeData() { if (typeof(NodeKey).IsAssignableFrom(typeof(Guid))) { _Key = (NodeKey)((object)Guid.NewGuid()); } } /// /// This constructor also initializes the property. /// /// public GraphLinksModelNodeData(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() { GraphLinksModelNodeData d = (GraphLinksModelNodeData)MemberwiseClone(); d.PropertyChanged = null; d._SubGraphKey = default(NodeKey); d._MemberKeys = 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 == "IsSubGraph") { this.IsSubGraph = (bool)e.GetValue(undo); } else if (e.PropertyName == "IsSubGraphExpanded") { this.IsSubGraphExpanded = (bool)e.GetValue(undo); } else if (e.PropertyName == "WasSubGraphExpanded") { this.WasSubGraphExpanded = (bool)e.GetValue(undo); } else if (e.PropertyName == "SubGraphKey") { this.SubGraphKey = (NodeKey)e.GetValue(undo); } else if (e.PropertyName == "MemberKeys") { this.MemberKeys = (IList)e.GetValue(undo); } else if (e.PropertyName == "IsLinkLabel") { this.IsLinkLabel = (bool)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 GraphLinksModelNodeData.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("IsLinkLabel", this.IsLinkLabel, false)); /// e.Add(XHelper.Attribute("IsSubGraph", this.IsSubGraph, false)); /// e.Add(XHelper.Attribute("IsSubGraphExpanded", this.IsSubGraphExpanded, true)); /// e.Add(XHelper.Attribute("WasSubGraphExpanded", this.WasSubGraphExpanded, false)); /// e.Add(XHelper.Attribute<NodeKey>("SubGraphKey", this.SubGraphKey, default(NodeKey), ConvertNodeKeyToString)); /// e.Add(XHelper.Elements<NodeKey>("MemberKeys", "Key", this.MemberKeys, ConvertNodeKeyToString)); /// 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("IsLinkLabel", this.IsLinkLabel, false)); e.Add(XHelper.Attribute("IsSubGraph", this.IsSubGraph, false)); e.Add(XHelper.Attribute("IsSubGraphExpanded", this.IsSubGraphExpanded, true)); e.Add(XHelper.Attribute("WasSubGraphExpanded", this.WasSubGraphExpanded, false)); e.Add(XHelper.Attribute("SubGraphKey", this.SubGraphKey, default(NodeKey), ConvertNodeKeyToString)); e.Add(XHelper.Elements("MemberKeys", "Key", this.MemberKeys, ConvertNodeKeyToString)); 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.IsLinkLabel = XHelper.Read("IsLinkLabel", e, false); /// this.IsSubGraph = XHelper.Read("IsSubGraph", e, false); /// this.IsSubGraphExpanded = XHelper.Read("IsSubGraphExpanded", e, true); /// this.WasSubGraphExpanded = XHelper.Read("WasSubGraphExpanded", e, false); /// this.SubGraphKey = XHelper.Read<NodeKey>("SubGraphKey", e, default(NodeKey), ConvertStringToNodeKey); /// this.MemberKeys = (IList<NodeKey>)XHelper.ReadElements<NodeKey>(e.Element("MemberKeys"), "Key", new ObservableCollection<NodeKey>(), ConvertStringToNodeKey); /// 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.IsLinkLabel = XHelper.Read("IsLinkLabel", e, false); this.IsSubGraph = XHelper.Read("IsSubGraph", e, false); this.IsSubGraphExpanded = XHelper.Read("IsSubGraphExpanded", e, true); this.WasSubGraphExpanded = XHelper.Read("WasSubGraphExpanded", e, false); this.SubGraphKey = XHelper.Read("SubGraphKey", e, default(NodeKey), ConvertStringToNodeKey); this.MemberKeys = (IList)XHelper.ReadElements(e.Element("MemberKeys"), "Key", new ObservableCollection(), ConvertStringToNodeKey); 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 whether this node data represents a "label" on a link instead of a simple node. /// /// /// By default this is false. /// public bool IsLinkLabel { get { return _IsLinkLabel; } set { if (_IsLinkLabel != value) { bool old = _IsLinkLabel; _IsLinkLabel = value; RaisePropertyChanged("IsLinkLabel", old, value); } } } private bool _IsLinkLabel; /// /// Gets or sets whether this node data represents a group or "subgraph" /// instead of a normal "atomic" node. /// /// /// By default this is false. /// public virtual bool IsSubGraph { get { return _IsSubGraph; } set { if (_IsSubGraph != value) { bool old = _IsSubGraph; _IsSubGraph = value; RaisePropertyChanged("IsSubGraph", old, value); } } } private bool _IsSubGraph; /// /// Gets or sets a reference to the containing subgraph node, if any. /// public NodeKey SubGraphKey { get { return _SubGraphKey; } set { if (!EqualityComparer.Default.Equals(_SubGraphKey, value)) { NodeKey old = _SubGraphKey; _SubGraphKey = value; RaisePropertyChanged("SubGraphKey", old, value); } } } private NodeKey _SubGraphKey; /// /// Gets or sets a list of references to member nodes. /// /// /// By default this is an empty . /// public IList MemberKeys { get { if (_MemberKeys == null) _MemberKeys = new ObservableCollection(); return _MemberKeys; } set { if (_MemberKeys != value && value != null) { IList old = _MemberKeys; _MemberKeys = value; RaisePropertyChanged("MemberKeys", old, value); } } } private IList _MemberKeys; /// /// 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 IsSubGraphExpanded { get { return _IsSubGraphExpanded; } set { if (_IsSubGraphExpanded != value) { bool old = _IsSubGraphExpanded; _IsSubGraphExpanded = value; RaisePropertyChanged("IsSubGraphExpanded", old, value); } } } private bool _IsSubGraphExpanded = true; /// /// Gets or sets whether this node had been "expanded" when its containing group was "collapsed". /// /// /// By default this is false. This is meaningful only when the container subgraph is not expanded. /// /// /// Although this data property is defined for your convenience, the model does not know about this property. /// public bool WasSubGraphExpanded { get { return _WasSubGraphExpanded; } set { if (_WasSubGraphExpanded != value) { bool old = _WasSubGraphExpanded; _WasSubGraphExpanded = value; RaisePropertyChanged("WasSubGraphExpanded", old, value); } } } private bool _WasSubGraphExpanded = 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 = ""; } /// /// A simple representation of link data /// 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 /// the Type of an optional value that helps distinguish different "ports" on a node /// /// /// This provides a standard implementation of /// /// data that represents links, /// including properties that are "references" to the two nodes at each end /// of the link and optional "port" parameter information at both ends. /// You can use this class if you do not already have your own application class /// holding information about links 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 MyLinkData : GraphLinksModelLinkData<String, String> { /// public double Cost { /// get { return _Cost; } /// set { if (_Cost != value) { double old = _Cost; _Cost = value; RaisePropertyChanged("Cost", old, value); } } /// } /// private double _Cost; /// } /// /// This associates a number with each link so that you can bind values to this property in a DataTemplate. /// For example, look at the TextBlock's binding of Text in this template: /// /// <DataTemplate x:Key="LinkTemplate"> /// <go:LinkPanel go:Part.SelectionElementName="Path" go:Part.SelectionAdorned="True"> /// <go:LinkShape x:Name="Path" go:LinkPanel.IsLinkShape="True" Stroke="Black" StrokeThickness="1" /> /// <Path Fill="Black" go:LinkPanel.ToArrow="Standard" /> /// <TextBlock Text="{Binding Path=Data.Cost}" /> /// </go:LinkPanel> /// </DataTemplate> /// /// (In Silverlight, replace the go:LinkShape with Path.) /// /// /// 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 WPF the properties that you define should also be serializable, /// in order for the data to be copiable, especially to and from the clipboard. /// For both Silverlight and WPF you should override the method. /// /// /// In the sample above, /// the is declared to be a string, /// which is used as the way to uniquely refer to node data. /// The is declared to be a string, /// which is useful if you want to distinguish between multiple "ports" on a single node. /// However, if your nodes only have a single port, you can ignore the port /// parameter information, and the type does not really matter, /// although string is recommended for compatibility with the implementation of /// methods that deal with ports. /// /// /// This class is not useful with or . /// /// [Serializable] public class GraphLinksModelLinkData : INotifyPropertyChanged, IChangeDataValue, ICloneable { /// /// The default constructor produces an empty object with no references to nodes. /// public GraphLinksModelLinkData() { } /// /// This constructor initializes the and properties. /// /// /// public GraphLinksModelLinkData(NodeKey from, NodeKey to) { _FromKey = from; _ToKey = to; } /// /// This constructor initializes the and properties. /// /// a node key identifying the node data from which the link comes /// an optional value identifying which port on the "from" node the link is connected to /// a node key identify the node data to which the link goes /// an optional value identifying which port on the "to" node the link is connected to public GraphLinksModelLinkData(NodeKey from, PortKey fromport, NodeKey to, PortKey toport) { _FromKey = from; _ToKey = to; _FromPort = fromport; _ToPort = toport; } /// /// 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 must override this method and make sure the additional state is copied to the /// newly cloned object returned by calling the base method. /// This reinitializes the and properties. /// public virtual object Clone() { GraphLinksModelLinkData d = (GraphLinksModelLinkData)MemberwiseClone(); d.PropertyChanged = null; d._FromKey = default(NodeKey); d._ToKey = default(NodeKey); d._LabelNode = default(NodeKey); // can't share the collection of Points if (_Points != null) d._Points = new List(_Points); 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 and properties /// as this object's default text rendering. /// /// a String like "Node1 --> Node2" public override string ToString() { String s = ""; if (this.From != null) s += this.From; if (this.FromPort != null) s += "(" + this.FromPort.ToString() + ")"; if (this.To != null) s += " --> " + this.To; if (this.ToPort != null) s += "(" + this.ToPort.ToString() + ")"; if (this.LabelNode != null) s += " lab: " + this.LabelNode.ToString(); return s; } /// /// 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 == "From") { this.From = (NodeKey)e.GetValue(undo); } else if (e.PropertyName == "To") { this.To = (NodeKey)e.GetValue(undo); } else if (e.PropertyName == "FromPort") { this.FromPort = (PortKey)e.GetValue(undo); } else if (e.PropertyName == "ToPort") { this.ToPort = (PortKey)e.GetValue(undo); } else if (e.PropertyName == "LabelNode") { this.LabelNode = (NodeKey)e.GetValue(undo); } else if (e.PropertyName == "Text") { this.Text = (String)e.GetValue(undo); } else if (e.PropertyName == "Points") { this.Points = (IEnumerable)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 GraphLinksModelLinkData.ChangeDataValue"); } } } /// /// Constructs a Linq for XML XElement holding the data of this link. /// /// 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>("From", this.From, default(NodeKey), ConvertNodeKeyToString)); /// e.Add(XHelper.Attribute<NodeKey>("To", this.To, default(NodeKey), ConvertNodeKeyToString)); /// e.Add(XHelper.Attribute<PortKey>("FromPort", this.FromPort, default(PortKey), ConvertPortKeyToString)); /// e.Add(XHelper.Attribute<PortKey>("ToPort", this.ToPort, default(PortKey), ConvertPortKeyToString)); /// e.Add(XHelper.Attribute<NodeKey>("LabelNode", this.LabelNode, default(NodeKey), ConvertNodeKeyToString)); /// e.Add(XHelper.Attribute("Category", this.Category, "")); /// e.Add(XHelper.Attribute("Text", this.Text, "")); /// e.Add(XHelper.Attribute("Points", this.Points, null)); /// return e; /// } /// /// /// /// If you add properties to this link 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("From", this.From, default(NodeKey), ConvertNodeKeyToString)); e.Add(XHelper.Attribute("To", this.To, default(NodeKey), ConvertNodeKeyToString)); e.Add(XHelper.Attribute("FromPort", this.FromPort, default(PortKey), ConvertPortKeyToString)); e.Add(XHelper.Attribute("ToPort", this.ToPort, default(PortKey), ConvertPortKeyToString)); e.Add(XHelper.Attribute("LabelNode", this.LabelNode, default(NodeKey), ConvertNodeKeyToString)); e.Add(XHelper.Attribute("Category", this.Category, "")); e.Add(XHelper.Attribute("Text", this.Text, "")); e.Add(XHelper.Attribute("Points", this.Points, null)); return e; } /// /// Initialize this link data with data held in a Linq for XML XElement. /// /// the XElement /// /// /// This sets this link 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.From = XHelper.Read<NodeKey>("From", e, default(NodeKey), ConvertStringToNodeKey); /// this.To = XHelper.Read<NodeKey>("To", e, default(NodeKey), ConvertStringToNodeKey); /// this.FromPort = XHelper.Read<PortKey>("FromPort", e, default(PortKey), ConvertStringToPortKey); /// this.ToPort = XHelper.Read<PortKey>("ToPort", e, default(PortKey), ConvertStringToPortKey); /// this.LabelNode = XHelper.Read<NodeKey>("LabelNode", e, default(NodeKey), ConvertStringToNodeKey); /// this.Category = XHelper.Read("Category", e, ""); /// this.Text = XHelper.Read("Text", e, ""); /// this.Points = XHelper.Read("Points", e, (IEnumerable<Point>)null); /// } /// /// /// /// If you add properties to this link 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) { this.From = XHelper.Read("From", e, default(NodeKey), ConvertStringToNodeKey); this.To = XHelper.Read("To", e, default(NodeKey), ConvertStringToNodeKey); this.FromPort = XHelper.Read("FromPort", e, default(PortKey), ConvertStringToPortKey); this.ToPort = XHelper.Read("ToPort", e, default(PortKey), ConvertStringToPortKey); this.LabelNode = XHelper.Read("LabelNode", e, default(NodeKey), ConvertStringToNodeKey); this.Category = XHelper.Read("Category", e, ""); this.Text = XHelper.Read("Text", e, ""); this.Points = XHelper.Read("Points", e, (IEnumerable)null); } /// /// 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, 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(Guid))) { return XHelper.ToString((Guid)(Object)key); } else { ModelHelper.Error("Cannot convert NodeKey type to String: " + typeof(NodeKey).ToString() + " -- override GraphLinksModelLinkData.ConvertNodeKeyToString"); return key.ToString(); } } /// /// Convert a key value to a string. /// /// /// a String from which can recover the original key value /// /// Currently this handles PortKey types that are String, Int32, or Guid. /// Override this method to handle additional types. /// However, the PortKey is almost always a String, so the need to override this method is rare. /// protected virtual String ConvertPortKeyToString(PortKey key) { if (typeof(PortKey).IsAssignableFrom(typeof(String))) { return XHelper.ToString((String)(Object)key); } else if (typeof(PortKey).IsAssignableFrom(typeof(int))) { return XHelper.ToString((int)(Object)key); } else if (typeof(PortKey).IsAssignableFrom(typeof(Guid))) { return XHelper.ToString((Guid)(Object)key); } else { ModelHelper.Error("Cannot convert PortKey type to String: " + typeof(PortKey).ToString() + " -- override GraphLinksModelLinkData.ConvertPortKeyToString"); return key.ToString(); } } /// /// Convert a string to a key value. /// /// /// a /// /// Currently this handles NodeKey types that are String, Int32, 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(Guid))) { return (NodeKey)(Object)XHelper.ToGuid(s); } else { ModelHelper.Error("Cannot convert String to NodeKey type: " + typeof(NodeKey).ToString() + " -- override GraphLinksModelData.ConvertStringToNodeKey"); return default(NodeKey); } } /// /// Convert a string to a key value. /// /// /// a /// /// Currently this handles PortKey types that are String, Int32, or Guid. /// Override this method to handle additional types. /// However, the PortKey is almost always a String, so the need to override this method is rare. /// protected virtual PortKey ConvertStringToPortKey(String s) { if (typeof(PortKey).IsAssignableFrom(typeof(String))) { return (PortKey)(Object)XHelper.ToString(s); } else if (typeof(PortKey).IsAssignableFrom(typeof(int))) { return (PortKey)(Object)XHelper.ToInt32(s); } else if (typeof(PortKey).IsAssignableFrom(typeof(Guid))) { return (PortKey)(Object)XHelper.ToGuid(s); } else { ModelHelper.Error("Cannot convert String to PortKey type: " + typeof(PortKey).ToString() + " -- override GraphLinksModelData.ConvertStringToPortKey"); return default(PortKey); } } /// /// Gets or sets a String that names the category to which the link 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 value of the node from which this link comes. /// public NodeKey From { get { return _FromKey; } set { if (!EqualityComparer.Default.Equals(_FromKey, value)) { NodeKey old = _FromKey; _FromKey = value; RaisePropertyChanged("From", old, value); } } } private NodeKey _FromKey; /// /// Gets or sets the key value of the node to which this link goes. /// public NodeKey To { get { return _ToKey; } set { if (!EqualityComparer.Default.Equals(_ToKey, value)) { NodeKey old = _ToKey; _ToKey = value; RaisePropertyChanged("To", old, value); } } } private NodeKey _ToKey; /// /// Gets or sets the optional parameter information for the node's port. /// public PortKey FromPort { get { return _FromPort; } set { if (!EqualityComparer.Default.Equals(_FromPort, value)) { PortKey old = _FromPort; _FromPort = value; RaisePropertyChanged("FromPort", old, value); } } } private PortKey _FromPort; /// /// Gets or sets the optional parameter information for the node's port. /// public PortKey ToPort { get { return _ToPort; } set { if (!EqualityComparer.Default.Equals(_ToPort, value)) { PortKey old = _ToPort; _ToPort = value; RaisePropertyChanged("ToPort", old, value); } } } private PortKey _ToPort; /// /// Gets or sets the key value of the node which is on this link acting as if it were a label. /// public NodeKey LabelNode { get { return _LabelNode; } set { if (!EqualityComparer.Default.Equals(_LabelNode, value)) { NodeKey old = _LabelNode; _LabelNode = value; RaisePropertyChanged("LabelNode", old, value); } } } private NodeKey _LabelNode; /// /// Gets or sets a string that can be used for a text label on a link. /// /// /// 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 = ""; /// /// Gets or sets a collection of Points used to define the path of the link. /// /// /// The default value is null. /// /// /// Although this data property is defined for your convenience, the model does not know about this property. /// public IEnumerable Points { get { return _Points; } set { if (_Points != value) { IEnumerable old = _Points; _Points = value; RaisePropertyChanged("Points", old, value); } } } private IEnumerable _Points; } /// /// This simple class is handy with many s /// or with , so that for many cases you do not need to define /// your own link data class inheriting from . /// /// /// /// The node data type can be any Object; the (optional) port parameter information is assumed /// to be of type String. /// There are constructors for most of the common uses. /// /// /// For reasons of both compile-time type checking and run-time efficiency, /// we recommend defining your own data class derived from . /// Doing so also permits the addition of application-specific properties. /// /// /// This class is not useful with or . /// /// [Serializable] public sealed class UniversalLinkData : GraphLinksModelLinkData { /// /// The default constructor produces link-representing data that needs to be initialized. /// public UniversalLinkData() { } /// /// This constructor produces a link-representing data that connects two nodes, identified by their node data keys. /// /// the initial value for /// the initial value for public UniversalLinkData(Object fromnodekey, Object tonodekey) { this.From = fromnodekey; this.To = tonodekey; } /// /// This constructor produces a link-representing data that connects two nodes, /// identified by their node data keys, and the text for a label annotation. /// /// the initial value for /// the initial value for /// the initial value for public UniversalLinkData(Object fromnodekey, Object tonodekey, String labeltext) { this.From = fromnodekey; this.To = tonodekey; this.Text = labeltext; } /// /// This constructor produces a link-representing data that connects two nodes and also provides port-identifying information at each node. /// /// the initial value for /// the initial string value for /// the initial value for /// the initial string value for public UniversalLinkData(Object fromnodekey, String fromport, Object tonodekey, String toport) { this.From = fromnodekey; this.FromPort = fromport; this.To = tonodekey; this.ToPort = toport; } /// /// This constructor produces a link-representing data that connects two nodes and /// also provides port-identifying information at each node and the text for a label annotation. /// /// the initial value for /// the initial string value for /// the initial value for /// the initial string value for /// the initial value for public UniversalLinkData(Object fromnodekey, String fromport, Object tonodekey, String toport, String labeltext) { this.From = fromnodekey; this.FromPort = fromport; this.To = tonodekey; this.ToPort = toport; this.Text = labeltext; } } }