/*
* 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