Change Node.Location from code behind

Hi Walter,

How can I change node location from code behind? And yes, my node location is a TwoWay binding. Should I just modify the .location property and that’s it? because when I did that, I don’t know why, but my node location is modified somewhere.

Concretelly, here is what I’m trying to reproduce. I have 1 link and 2 nodes and when I make some action, I add 2 more nodes and 2 mores links. It works fine, but like I said, my node location are modified even if I try to modify them from code behind.

If there is a data-binding, the normal way to cause a change in the view is to modify the data.

So for changing the Node.Location in your app where there is a data-binding, you should modify the data property of your node data.

However it is true that for data-bindings that are Mode=“TwoWay”, you could modify the Node.Location directly by setting it in code.

Either way, you should make those changes inside a transaction.

In your app, which node location is being modified unexpectedly and with what values?

Before going further, maybe you can help me on something. I’m trying to reproduce the image below. Initialy, the horizontal lines (red and blue) are already define. So my first challenge is to find a way to layout these lines like this. These can ben modified after in the diagram but initialy, they are created somewhere else. The oranges lines are created directly from the diagram by dragging from one node to an other. I can create an unlimited number of orange lines between horizontal lines.

So, my question is, what is the best approach to layout my diagram? Should I use a GridLayout, should I create from scratch a new Layout inheriting from DiagramLayout or should I only managed from the code behind my node location?

I don’t think a GridLayout would be satisfactory, because you have at least two kinds of layout going on: one for the horizontal objects (red and blue) and one for the vertical objects (orange).

I’m not sure you need to define a new DiagramLayout, but that would be the most general thing to do. You might want to start off just writing the code to do what you want and only later decide whether you want to replace the default Diagram.Layout with your custom layout, perhaps to simplify the maintenance of the layout as the diagram/model is changed.

The layout of the red and blue objects is easy. If the number of red and number of blue lines is variable, I suppose those properties should be part of the model.

Presumably your model data is just a collection of pairs: (0,0) (1, 0) (2, 1) or something like that. I guess I’m assuming that each orange object must connect a red object with a blue object.

I don’t know if you need to use Nodes and Links or use something simpler. It depends on how you want the user to select things and modify or create or delete them.

The layout of the orange objects seems to just be determining the X values, since the Y values are determined by the Y positions of the red and blue lines.
I don’t know if you need to sort the orange objects to make sure they appear left-to-right in a particular order.

Well, I’m not sure if I understand your collection of pairs. The example that I showed you was a really simple example, but I need to do something like the image below.

If we don’t pay attention to the orange line, here is how I see it.

  • Each group of line connected togheter are Group Node. To layout each of these groups I would use the GridLayout with WrappingColumn=1.
  • Inside a group, I would use a TreeLayout to display all the nodes
Am I on the right way?

Other question: I tried to override the DoLayout method of the GridLayout class and I’m wondering, which property of the node is changed to modify the layout? I looked at Part.Data.Location but I only see NaN NaN…

Well, that’s a lot more complicated than I was guessing at before, based on your first sketch.
I guess what you propose is plausible, but I still have no idea if it will work well, or if more primitive means would be better (as I was suggesting earlier).
It depends on a lot on the interactive behaviors that you expect to support.

Layouts modify the Node.Location of all of the Nodes that they lay out.
A data property will only be updated if there is a data-binding on the Node.Location that has Mode=“TwoWay”.
You’ll see a lot of the samples do this:

<DataTemplate x:Key="NodeTemplate">
  <...  <b>go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}"</b> ... />
</DataTemplate>

Ok it works for the Data.Location, like you said, I forgot to databind my location on TwoWay.

By more primitive means you mean modifying the Data.Location manually each time a Group is added, removed or resized? Because I currently use the GridLayout and like you said, every time I do something, everything is re-layout.

No, I meant instead of using Groups, just using Nodes consisting of Polylines, and not any of our predefined layouts.

I would but I can’t. My model is created in such a way that I have no choice to use Groups. The behaviour of the Groups should be the same as a Control in a StackPanel. Is there any way to mimic this behaviour with a GridLayout or should I manually manage it?

In the image below, each black box represent a group. Inside a group, I will probably use a TreeLayout to manage my nodes.

Yes, that should work, because GridLayout ignores links (i.e. the orange links that appear to connect the groups) and if each Group has its own Layout.

But if you are expecting to allow users to re-order the groups interactively, you should use a custom layout that preserves the current Y position of each node (well, group in your case).

[code] public class VerticalLayout : DiagramLayout {
public override void DoLayout(IEnumerable nodes, IEnumerable links) {
// make sure each node has a Position, defaulting to 0,0
foreach (Node n in nodes) {
if (!n.Visible) continue;
Point pos = n.Position;
if (Double.IsNaN(pos.X)) n.Move(new Point(0, 0), false);
}
// now set their Y positions so that they are all adjacent,
// while setting their X positions to zero
double y = 0;
foreach (Node n in nodes.OrderBy(n => Spot.Center.PointInRect(n.Bounds).Y)) {
if (!n.Visible) continue;
Rect b = n.Bounds;
n.Move(new Point(0, y), true);
y += b.Height;
// for some kinds of nodes you might also want to add some spacing
}
}
}

// This provides visual interactive feedback for where a dragged node will be placed.
// More generally this should use an unbound temporary Node that is defined in XAML,
// rather than hard-coding the definition of the insertion line.
public class VerticalInsertionDraggingTool : DraggingTool {
public override void DoActivate() {
this.InsertionCursor = CreateInsertionCursor();
this.Diagram.PartsModel.AddNode(this.InsertionCursor);
base.DoActivate();
}
public override void DoDeactivate() {
this.Diagram.PartsModel.RemoveNode(this.InsertionCursor);
this.InsertionCursor = null;
base.DoDeactivate();
}

protected Node InsertionCursor { get; set; }
protected Node CreateInsertionCursor() {
  Node n = new Node();
  n.Id = "InsertionCursor";
  n.LayerName = "Tool";
  var line = new System.Windows.Shapes.Line();
  line.Stroke = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red);
  line.StrokeThickness = 1;
  line.X1 = -1000;
  line.Y1 = 0;
  line.X2 = +1000;
  line.Y2 = 0;
  n.Content = line;
  return n;
}

protected override void DragOver(Point pt, bool moving, bool copying) {
  if (this.InsertionCursor != null) {
    double miny = Double.MaxValue;
    double maxy = Double.MinValue;
    foreach (Node n in this.Diagram.Nodes) {
      if (!n.Visible || !n.IsBoundToData) continue;
      if (moving && this.DraggedParts != null && this.DraggedParts.ContainsKey(n)) continue;
      if (copying && this.CopiedParts != null && this.CopiedParts.ContainsKey(n)) continue;
      Rect b = n.Bounds;
      miny = Math.Min(miny, b.Y);
      maxy = Math.Max(maxy, b.Y+b.Height);
      if (b.Y <= pt.Y && pt.Y <= b.Y+b.Height/2) {
        this.InsertionCursor.Position = new Point(pt.X, b.Y);
        break;
      } else if (b.Y+b.Height/2 <= pt.Y && pt.Y <= b.Y+b.Height) {
        this.InsertionCursor.Position = new Point(pt.X, b.Y+b.Height);
        break;
      }
    }
    if (pt.Y <= miny && miny < Double.MaxValue) {
      this.InsertionCursor.Position = new Point(pt.X, miny);
    } else if (pt.Y >= maxy && maxy > Double.MinValue) {
      this.InsertionCursor.Position = new Point(pt.X, maxy);
    }
  }
  base.DragOver(pt, moving, copying);
}

}[/code]
Install with:

[code]<go:Diagram …>
go:Diagram.Layout
<local:VerticalLayout Conditions=“All” />
</go:Diagram.Layout>

go:Diagram.DraggingTool
<local:VerticalInsertionDraggingTool />
</go:Diagram.DraggingTool>
</go:Diagram>[/code]

Thank for the example, it really helped me. But I still have some refreshing problems. When I add a Group, the VerticalLayout set the right position, but at the screen, this position is not updated. I need to perform an other action and then the position takes the right value. I tried to use the Diagram.LayoutDiagram method to re-layout it, but it’s not working…

When you make modifications to the model or to its data, do you do so within a transaction?

BTW, I had adopted the VerticalLayout code from another use, but when posting it here I had not completed simplifying it. I have updated the code, above.

Just after posting this message, I added a Start/CommitTransaction when I add a Group. It works but partially. Let see the example below:

The Height of my groups is 46.
When I add c2 (the second group):

  • DoLayout is called, and c2.Position=0,0 // c1.Position=0,14
    • Why 14, maybe because at this moment the group is not resized
  • Then, DoLayout is called a second time, c2.Position=0,0 // c1.Position=0.46
    • Now we have the right value
But what I see at the screen is c1.Position=0,14, like the first example below. Then I perform an action, and my group is re-layout at the right position.

Forget what I said, I replace Diagram.LayoutManager.MoveAnimated by n.Move like in your update and it works!!!

I’m a little bit confuse. Is the LayoutDiagram method call the DiagramLayout.DoLayout method? How does it works?

Because I have another problem when I add groups. My Groups are not resized automatically. Well, I’m not sure if it’s the group that is not resize correctly or if it’s the TreeLayout that doesn’t perform it DoLayout method correctly. Because inside my group, I use a TreeLayout to display my nodes.

The first image below represent the group when added, the second image represent the group resized.

Diagram.LayoutDiagram() just asks the LayoutManager to make sure all of the layouts in the diagram are up-to-date. It recursively walks the groups to do them first, and then does the Diagram.Layout last. It only calls DoLayout when the particular layout is no longer valid. But I think Diagram.LayoutDiagram explicitly invalidates all layouts first. One normally does not need to call LayoutDiagram.

And about my resize problem, no idea? I don’t understand why, but it’s my GroupPanel that doesn’t resize automatically.

Is this just a problem for newly added Groups? Any group already in the graph is laid out correctly?

The LayoutManager does make sure any Group is remeasured after its Group.Layout is run, so that it can be treated as a simple Node by the layout the group is in.

Yes, for newly added Groups. I tried the method Remeasure() directly in the DoLayout but without success. When I close and re-open the window, everything is lay out correctly.

Hmm, I don’t know. Do you add all of the Nodes and Links of the subgraph before you add the Group to the model? I’m wondering if the order in which you add things matters for some reason.