Here’s one way to do it. I have modified the StateChart sample to save/load the Route.Points list of Points in the XML.
There are two parts of this puzzle. One is adding the Points information to the link data, in this sample the Transition class. Presumably you have already done this, although it isn’t clear if you have extended your link data class with the set of link points.
The second part is to copy the points information to/from the link data to the Link.Route. The tricky point here is making sure you do so after all of the nodes have been realized.
So for the first part, we add a property to the Transition link data class:
[code] public class Transition : GraphLinksModelLinkData<String, String> {
. . .
public List<Point> Points { get; set; }
// write the extra property on the link data
public override XElement MakeXElement(XName n) {
XElement e = base.MakeXElement(n);
e.Add(XHelper.Attribute("Curviness", this.Curviness, Double.NaN));
e.Add(XHelper1.Attribute("Points", this.Points, new List<Point>()));
return e;
}
// read the extra property on the link data
public override void LoadFromXElement(XElement e) {
base.LoadFromXElement(e);
this.Curviness = XHelper.Read("Curviness", e, Double.NaN);
this.Points = new List<Point>(XHelper1.Read("Points", e, new List<Point>()));
}
}[/code]
In this case I’d like to use standard conversion methods to convert a sequence of Points to a String and vice-versa. Those methods are normally static methods of the XHelper class, but this particular one (IEnumerable) doesn’t exist in version 1.0. So I’ve extracted those methods into a separate XHelper1 class:
// static methods taken from version 1.1 XHelper class:
public static class XHelper1 {
public static XAttribute Attribute(XName n, IEnumerable<Point> v, IEnumerable<Point> defval) {
return XHelper.Attribute<IEnumerable<Point>>(n, v, defval, ToString);
}
public static IEnumerable<Point> Read(XName n, XElement e, IEnumerable<Point> defval) {
return XHelper.Read<IEnumerable<Point>>(n, e, defval, ToIEnumerableOfPoint);
}
public static String ToString(IEnumerable<Point> v) {
StringBuilder sb = new StringBuilder();
bool first = true;
foreach (Point p in v) {
if (!first) sb.Append(" ");
first = false;
sb.Append(XmlConvert.ToString(p.X));
sb.Append(" ");
sb.Append(XmlConvert.ToString(p.Y));
}
return sb.ToString();
}
public static IEnumerable<Point> ToIEnumerableOfPoint(String s) {
if (s == null) return Enumerable.Empty<Point>();
char[] separators = { ' ' };
String[] nums = s.Split(separators, StringSplitOptions.RemoveEmptyEntries);
Point[] v = new Point[nums.Length/2];
for (int i = 0; i < nums.Length; i++) {
double x = XmlConvert.ToDouble(nums[ i ]);
i++;
double y = ((i < nums.Length) ? XmlConvert.ToDouble(nums[ i ]) : 0);
v[i/2] = new Point(x, y);
}
return v;
}
}
So, to summarize, the above changes add a Points property to the link data class and augment methods to read and write that data from and to XML.
For the second part of the task, here are the modifications to the Load and Save button click methods, with the extra property and method that the load functionality depends on:
[code] // save and load the model data as XML, visible in the “Saved” tab of the Demo
private void Save_Click(object sender, RoutedEventArgs e) {
var model = myDiagram.Model as GraphLinksModel<State, String, String, Transition>;
if (model == null) return;
foreach (Link link in myDiagram.Links) {
Transition transition = (Transition)link.Data;
if (transition == null) continue;
transition.Points = new List(link.Route.Points);
}
XElement root = model.Save<State, Transition>(“StateChart”, “State”, “Transition”);
Demo.MainPage.Instance.SavedXML = root.ToString();
LoadButton.IsEnabled = true;
model.IsModified = false;
}
private void Load_Click(object sender, RoutedEventArgs e) {
var model = myDiagram.Model as GraphLinksModel<State, String, String, Transition>;
if (model == null) return;
try {
XElement root = XElement.Parse(Demo.MainPage.Instance.SavedXML);
model.Load<State, Transition>(root, "State", "Transition");
this.NeedsLoadedRoutes = true;
} catch (Exception ex) {
MessageBox.Show(ex.ToString());
}
model.IsModified = false;
}
private bool NeedsLoadedRoutes { get; set; }
private void UpdateRoutes(object sender, DiagramEventArgs e) {
if (!this.NeedsLoadedRoutes) return;
this.NeedsLoadedRoutes = false;
foreach (Link link in myDiagram.Links) {
Transition transition = (Transition)link.Data;
if (transition == null) continue;
link.Route.Points = transition.Points;
}
}[/code]
The only change to Save_Click is to explicitly copy the Link.Route.Points data to the Transition link data objects. The same is true for Load_Click, except that instead of changing the Route.Points property right away, it does so only after everything else has settled down.
It uses the Diagram.LayoutCompleted event as one way to execute this code late enough so that the construction of the nodes does not (inadvertantly) cause the connected link routes to be recomputed. The NeedsLoadedRoutes property is just to ensure that the code is only executed once after a Load_Click.
Don’t forget to register that event handler:
myDiagram.LayoutCompleted += UpdateRoutes;