Adding a node to a previously create diagram

Hello All,
I have previously written a windows forms application using your GoDiagram product quite successfully and am in the process of migrating it to Silverlight. I can get the diagram to draw but am having difficulty adding a node (then wish to remove it) from a click event. The node adds to the diagram but the location property seems to be being ignored and the text for the new node which has a number as part of the name increments the displayed number portion text.
What am I doing wrong !
Thanks,
Jim
In the XAML
<Border BorderBrush="Black" BorderThickness="1" CornerRadius="3" Background="{Binding Path=Data.Color}"
go:Part.SelectionAdorned="True" MouseRightButtonDown="MyDiagram_MouseRightButtonDown"
MouseRightButtonUp="MyDiagram_MouseRightButtonUp"
>
<go:Diagram Grid.Row="2" Height="439" HorizontalAlignment="Left" Margin="10,137,0,0" Name="MyDiagram" VerticalAlignment="Top"
Width="827" InitialScale="0.75"
InitialPanelSpot="MiddleTop"
InitialDiagramBoundsSpot="MiddleTop"
Padding="10"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Top"
DragSelectingTool="{x:Null}"
DraggingTool="{x:Null}"
NodeTemplate="{StaticResource NodeTemplate}"
LinkTemplateDictionary="{StaticResource LinkTemplateDictionary}">
In the code behind I have this, NodeData class at the bottom
private GraphLinksModel model = new GraphLinksModel();
In MainPage method
model.Modifiable = true;
In my data loading method
MyDiagram.ClearSelection();
model.NodeKeyPath = "Name";
model.LinkFromPath = "From";
model.LinkToPath = "To";
model.LinkCategoryPath = "Category";
// now iterate thru each base node
for (int i = 0; i < baseLinks.Length; i++)
{
var diagramNode = new NodeData();
diagramNode.Key = tmplotno + "/" + tmpplanno;
diagramNode.PlanNo = tmpplanno;
diagramNode.LotNo = tmplotno;
diagramNode.Name = tmplotno + "/" + tmpplanno;
diagramNode.Color = str_baseP;
nodes.Add(diagramNode);
}
model.NodesSource = nodes;
model.LinksSource = links;
MyDiagram.Model = model;
and this method which displays the right click menu
private void MyDiagram_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
var graphLinksModelNodeData = new NodeData();
FrameworkElement elt = MyDiagram.Panel.FindElementAt(MyDiagram.LastMousePointInModel, x => x as FrameworkElement, x => true, SearchLayers.Nodes);
if (elt == null) return;
var o = (PartManager.PartBinding) elt.DataContext;
_clickedNode = new ClickedNode {LocationX = o.Node.Location.X, LocationY = o.Node.Location.Y, Height = o.Node.ActualHeight, Width = o.Node.ActualWidth};
errMsg.Text = o.Data.ToString();
var p = new Point
{
X = (int) e.GetPosition(LayoutRoot).X,
Y = (int) e.GetPosition(LayoutRoot).Y
};
cMenu = new ContextMenu();
menuItem = new MenuItem();
menuItem.Header = "Show Plan Details";
cMenu.Items.Add(menuItem);
menuItem.Click += new RoutedEventHandler(MenuItem_Click);
menuItem = new MenuItem();
menuItem.Header = "Hide Plan Details";
cMenu.Items.Add(menuItem);
menuItem.Click += new RoutedEventHandler(MenuItem_Click);
cMenu.IsOpen = true;
cMenu.HorizontalOffset = p.X;
cMenu.VerticalOffset = p.Y;
}
and this method which should display the node in the bottom right hand corner of the right-clicked node
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
MenuItem mnu = (MenuItem)sender;
switch (mnu.Header.ToString())
{
case "Show Plan Details":
MyDiagram.StartTransaction("Node");
var pdNode = new NodeData();
pdNode.Name = errMsg.Text;
pdNode.Text = errMsg.Text;
pdNode.Key = "Key";
var p = new Point
{
X = _clickedNode.LocationX + _clickedNode.Width,
Y = _clickedNode.LocationY + _clickedNode.Height
};
pdNode.Location = p;
MyDiagram.Model.AddNode(pdNode);
MyDiagram.CommitTransaction("Node");
break;
case "Hide Plan Details":
break;
default:
break;
}
cMenu.IsOpen = false;
}
public class NodeData : GraphLinksModelNodeData
{
public string PlanNo { get; set; }
public string LotNo { get; set; }
public string Name { get; set; }
public String Color { get; set; }
public double Height { get; set; }
public double Width { get; set; }
// The Spot at which all outgoing Links connect to Node (may represent one or more sides).
public Spot FromSpot
{
get { return _FromSpot; }
set
{
if (_FromSpot != value)
{
Spot old = _FromSpot;
_FromSpot = value;
RaisePropertyChanged("FromSpot", old, value);
}
}
}
private Spot _FromSpot = Spot.None;
// The Spot at which all incoming Links connect to Node (may represent one or more sides).
public Spot ToSpot
{
get { return _ToSpot; }
set
{
if (_ToSpot != value)
{
Spot old = _ToSpot;
_ToSpot = value;
RaisePropertyChanged("ToSpot", old, value);
}
}
}
private Spot _ToSpot = Spot.None;
}

It’s commonplace to data-bind the Node.Location property to the Location property of your data:
go:Node.Location="{Binding Path=Data.Location}"
Many of the sample demonstrate this.

If you want the data to automatically get updated Location values as the user or the diagram layout moves nodes, make the Binding Mode=TwoWay.

Are you sure that you want the NodeData.Name property to be the unique key?
If so how are you using the NodeData.Key property?

Walter,

First off thanks for the quick reply at a late hour your time.
With the properties NodeData.name & NodeData.Key & NodeData.Text I think I have got confused as to each of there uses in the beginning.
To confirm
.Text - Is the 'text' displayed on a node.
.Name - Is the 'named' framework element.
.Key - Internal identifier ???
Anyway with the location aspect I have not specified a location property when I add a new node (diagramNode) in the code. I am letting goXam do the layout automatically. I only wish to place a new node at the bottom right corner of the node selected. Will I therefore have to do all of the location calculations myself for all of the nodes if I bind the location property as suggested or will that still automatically be 'placed' on the diagram ?
Any thoughts on why the text on the newly created node is numerically incrementing ?
Thanks,
Jim

Remember that GoDiagram does not use models and data-binding, so perhaps that’s a new concept for you in GoXam.

The default Diagram.Layout and all of the standard layouts try to make sure each Node has a defined Location value.
So you don’t need to specify it.

You don’t need a data-binding for Node.Location if you don’t need to get the location point from the data.

You told the model that the “Name” property is the unique identifier.
You told the TextBlock that it’s Text property gets its value from the “Name” property of the data.

Because the model tries to make sure the “Name” property is unique, it increments it until it is unique.
The TextBlock then shows that value.

Thanks Walter

The Key/Name issue now resolved model.NodeKeyPath = "Key"; changed from "Name".
I have added go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}" to the NodeTemplate as I need to get the bottom right corner location of the right clicked node so that I can place a temporary node at that point with additional information. Code still placing nodes as per the image.
By the way whats the best method to call to clear everything from a diagram programatically ?
Thanks.,
Jim

Yes; in fact the default value for model.NodeKeyPath is “Key”, when the data class is or inherits from one of our predefined node data classes.

I think I misunderstood your intent.
The Diagram.Layout, which is a TreeLayout in your case, is causing everything to be laid out again every time you add or remove a Node or a Link.
I’m not sure when you want the diagram layout to happen automatically.

You can control when it happens by setting the TreeLayout.Conditions property.
If you don’t want it to happen when nodes or links are added or removed, try setting Conditions=“None” or to “InitialOnly”, depending on whether you always want to call Diagram.LayoutDiagram() explicitly when you need it or only automatically when it would want to do an initial layout.

To remove everything from the diagram you can:

  • replace the Diagram.Model with an empty model, or
  • replace the Model.NodesSource with an empty collection, or
  • clear the Model.NodesSource (assuming the collection is an ObservableCollection)

Walter,

Clearing the Model.NodesSource worked fine for clearing the diagram, thanks.
I have now added the ConditionFlags="None" to the XAML and will control when I want the layout called.
<go:TreeLayout ConditionFlags="None" Angle="270" LayerSpacing="50" NodeSpacing="20" SetsChildPortSpot="False" SetsPortSpot="False" />
Requirement is ...
private ObservableCollection<NodeData> nodes = new ObservableCollection<NodeData>();
nodes is initially filled with data and set as the NodesSource of the Model which is then set as the Model of the diagram.
MyDiagram.Model = model; MyDiagram.LayoutDiagram();
User then right-clicks on a node that displays a popup menu (all ok) and upon a selection a new node should be added with it's top left corner attached to the bottom right corner of the right clicked node. In my RightMouseButtonUp event handler I get and save some data regarding the clicked node
var graphLinksModelNodeData = new NodeData();
FrameworkElement elt = MyDiagram.Panel.FindElementAt<FrameworkElement>(MyDiagram.LastMousePointInModel, x => x as FrameworkElement, x => true, SearchLayers.Nodes);
if (elt == null) return;

Node n = null;
n = Part.FindAncestor<Node>(elt);
NodeData pd = (NodeData) n.Data;
_clickedNode = new ClickedNode { LocationX = pd.Location.X, LocationY = pd.Location.Y, Height = n.ActualHeight, Width = n.ActualWidth };

THEN in the event handler of the MenuItem_Click I have coded this
MyDiagram.StartTransaction("Node"); var pdNode = new NodeData(); pdNode.Name = "Some Data to display"; pdNode.Key = "Key"; pdNode.Color = str_astal; var p = new Point { X = _clickedNode.LocationX + _clickedNode.Width, Y = _clickedNode.LocationY + _clickedNode.Height }; pdNode.Location = p;

MyDiagram.Model.AddNode(pdNode);
MyDiagram.CommitTransaction(“Node”);
MyDiagram.LayoutDiagram();

BUT it still places the new node as per the diagram above below the last node. Am I missing something still ? This is quite feasible
Thanks again, Jim

What you are forgetting is what I suggested originally: the data-binding of go:Node.Location="{Binding Path=Data.Location}".

Using data-binding, perhaps with a converter, is the most natural way of effecting change by modifying a data property.

Otherwise you have to write some code to call Diagram.PartManager.FindNodeForData to set the properties, such as Node.Location in this case.

I have the binding to location in the data template, inside the Border tag, and it’s still failing. Is this the correct place to put it ?

Thankyou for your patience.
<DataTemplate x:Key="NodeTemplate" > <Border BorderBrush="Black" BorderThickness="1" CornerRadius="3" Background="{Binding Path=Data.Color}" go:Part.SelectionAdorned="True" MouseRightButtonDown="MyDiagram_MouseRightButtonDown" MouseRightButtonUp="MyDiagram_MouseRightButtonUp" go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}" > <TextBlock Text="{Binding Path=Data.Name}" TextAlignment="Center" FontWeight="Bold" TextWrapping="Wrap" Margin="4 4 4 2" /> <ToolTipService.ToolTip> <TextBlock Text="Right Click for more details."></TextBlock> </ToolTipService.ToolTip> </Border> </DataTemplate>

Yes, that’s right. You’ll see many of the samples do the same thing, and some of those samples also modify node data to position the Node.

Working

Many Thanks...
I will now be able to put together a demo that I'm sure will impress !
I'm assuming that if I have a unique key for each of the temporary created nodes I can use that to subsequently delete the temp node using another menu option.
Thankyou for taking the time and having the patience to work through my issues. I now have a far better understanding of the GoXam framework.
Jim