/*
* 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 = "";
}
}