Add Links with databound nodes

Hi Walter,

in my Graphmodel I have several Nodes which are databound to SQL-Server / Entity-Data-Model- classes (Silverlight). The database records are in a 1:n self-relation with the same table. According to this, I got a Navigation-property in my Data-Class called Condition1.

Here’s the definition for my model:

model.NodeKeyPath = "PK_Condition"; 
model.ToNodesPath = "";

The nodes are shown in the diagram as expected, all Values are displayed as expected. But if I try to link my nodes via mouse I always got the error that I should override InsertToNodeKey what I did. You can see actual code down below. I still get an exception in the call: base.InsertToNodeKey! If I omit this line the link is removed from my diagram?! I didn’t find anything helpful in the documentation or the forum. I believe my fault is coming out of the missing ToNodesKeyPath. But if I set this to my readonly Navigation property this doesn’t work either. Could You please help me?

   public class WessyGraphModel : GraphModel
    {
        protected override void InsertToNodeKey(Condition nodedata, Guid tokey)
        {
            var toNode = FindNodeByKey(tokey) as BDF.Wessy.Models.Condition;
 
            BDF.Wessy.Models.Condition fromCondition = nodedata as BDF.Wessy.Models.Condition;
 
            // add the linked node to the navigation-properties node list
            fromCondition.Condition1.Add(toNode);
 
            base.InsertToNodeKey(nodedata, tokey);
        }
 

By setting GraphModel.ToNodesPath to the empty string you are declaring that your Condition class not only holds your node data but also is the collection of node keys for which there are links.

But that doesn’t appear to be the case in your code, where you seem to use the Condition.Condition1 property value as the collection. If that’s the case, you should set GraphModel.ToNodesPath=“Condition1”. If your collection class implements a known collection class, you don’t need to override that InsertToNodeKey method at all.

If I set my ToNodesPath-Property to my Navigation-property I get the following exception when my diagram is loaded:



System.InvalidCastException: Specified cast is not valid.
at Northwoods.GoXam.Model.GraphModel2.DoNodeAdded(NodeType nodedata) at Northwoods.GoXam.Model.GraphModel2.#9l(Boolean clear)
at Northwoods.GoXam.Model.GraphModel`2.set_NodesSource(IEnumerable value)
at Northwoods.GoXam.Diagram.#uc(DependencyObject d, DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.RaisePropertyChangeNotifications(DependencyProperty dp, Object oldValue, Object newValue)
at System.Windows.DependencyObject.UpdateEffectiveValue(DependencyProperty property, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, ValueOperation operation)
at System.Windows.DependencyObject.RefreshExpression(DependencyProperty dp)
at System.Windows.Data.BindingExpression.SendDataToTarget()
at System.Windows.Data.BindingExpression.SourceAcquired()
at System.Windows.Data.BindingExpression.System.Windows.IDataContextChangedListener.OnDataContextChanged(Object sender, DataContextChangedEventArgs e)
at System.Windows.Data.BindingExpression.DataContextChanged(Object sender, DataContextChangedEventArgs e)
at System.Windows.FrameworkElement.OnDataContextChanged(DataContextChangedEventArgs e)
at System.Windows.FrameworkElement.OnAncestorDataContextChanged(DataContextChangedEventArgs e)
at System.Windows.FrameworkElement.NotifyDataContextChanged(DataContextChangedEventArgs e)
at System.Windows.FrameworkElement.OnAncestorDataContextChanged(DataContextChangedEventArgs e)
at System.Windows.FrameworkElement.NotifyDataContextChanged(DataContextChangedEventArgs e)
at System.Windows.FrameworkElement.OnTreeParentUpdated(DependencyObject newParent, Boolean bIsNewParentAlive)
at System.Windows.DependencyObject.UpdateTreeParent(IManagedPeer oldParent, IManagedPeer newParent, Boolean bIsNewParentAlive, Boolean keepReferenceToParent)
at MS.Internal.FrameworkCallbacks.ManagedPeerTreeUpdate(IntPtr oldParentElement, IntPtr parentElement, IntPtr childElement, Byte bIsParentAlive, Byte bKeepReferenceToParent, Byte bCanCreateParent)
Any ideas? Can I do anything to figure out my problem?

Thanks for providing the error message and stack trace.

I guess I’m confused about what you’re trying to do. Does your model have GUID references to Condition objects or does your model use direct memory references to Condition objects?

You have declared your model to use Guid as NodeKey type. That means the type of your Condition.Condition1 property should be IList.

Yet your code appears to be treating the collection as if it had references to Condition objects rather than using Guids.

Hi Walter,

thanks for Your quick response. Yes my Primary- and Foreign-keys are Guids! So my navigation-property Condition1 is of type List but not List! This seems to be the problem. Condition1 is a “normal” navigation property automatically created by the Entity-Framework-Designer.

Is there a way around? My first way was to use a specialized node-class but this ended in transferring my data to and from the collection to get it back to the database. In my actual solution I try to use the databinding-mechanism as it is.

Well, if you really want to use memory references, just change the NodeKey type in your model definitions: GraphModel<Condition, Condition>.

As an optimization, you can also set GraphModel.NodeKeyIsNodeData = true.

I have no idea of what other changes you’ll need to make, since I don’t know what other code you have that is dealing with model data.

Great! This was a remarkable step forward. I Had to change a few little things according to this tip You gave me. Most things are working now.

Two things remain:

If I select several nodes in my diagram and call myDiagram.CommandHandler.Group(); (the PrototypeGroup is set to a new instance of condition) I get the following exception which says nothing to me:

System.InvalidOperationException: CopyNode1: override this method to copy nodes, or have node data implement ICloneable or be Serializable
at #w.#P.#tc(IDiagramModel model, String msg)
at Northwoods.GoXam.Model.GraphModel2.CopyNode1(NodeType nodedata, CopyDictionary env) at Northwoods.GoXam.Model.GraphModel2.AddCollectionCopy(DataCollection coll, CopyDictionary env)
at Northwoods.GoXam.Model.GraphModel2.AddNodeCopy(NodeType nodedata) at Northwoods.GoXam.Model.GraphModel2.#fn(Object nodedata)
at Northwoods.GoXam.CommandHandler.Group()
at BDF.Wessy.ViewComponents.CompCondition.btnGroupNodes_Click(Object sender, RoutedEventArgs e)
at System.Windows.Controls.Primitives.ButtonBase.OnClick()
at System.Windows.Controls.Button.OnClick()
at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
at System.Windows.Controls.Control.OnMouseLeftButtonUp(Control ctrl, EventArgs e)
at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)
Second issue:

If I databind my nodes location-property in my datatemplate for the nodes they disappear from my diagram when I try to move them or when I try to place a new node on the diagram via drag&drop. The location-property in my collection is updated correctly?!

(1) It is trying to make a copy of the CommandHandler.PrototypeGroup data, so that it can be added to the model. But it doesn’t know how to make a copy of the data. (It knows about ICloneable and serializable.) So you need to override the CopyNode1 method to do the copying, because only you know how.

(2) If the data-binding is OneWay, then maybe the problem is that the location value in the data is (NaN,NaN). Did you want the binding to be Mode=TwoWay?

ok, I overwrote CopyNode1 as follows:

        protected override Condition CopyNode1(Condition nodedata, CopyDictionary env)
        {
            Condition copiednode = new Condition()
            {
                PK_Condition = nodedata.PK_Condition,
                Category = nodedata.Category,
                FK_Selection = nodedata.FK_Selection,
                IsSubGraph = nodedata.IsSubGraph
            };
            return copiednode;  
        }
The Exception when Calling the CommandHandler.Group-Method changed:
System.ArgumentException: Object of type 'BDF.Wessy.Models.Condition' cannot be converted to type 'System.Nullable`1[System.Guid]'.
   at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)
   at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
   at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
   at #w.#Q.#Nr(Object source, K newval)
   at Northwoods.GoXam.Model.GraphModel`2.ModifyGroupNodeKey(NodeType nodedata, NodeKey groupkey)
   at Northwoods.GoXam.Model.GraphModel`2.SetGroupNodeKey(NodeType nodedata, NodeKey groupkey)
   at Northwoods.GoXam.Model.GraphModel`2.#En(Object nodedata, Object groupnodedata)
   at Northwoods.GoXam.CommandHandler.Group()
   at BDF.Wessy.ViewComponents.CompCondition.btnGroupNodes_Click(Object sender, RoutedEventArgs e)
   at System.Windows.Controls.Primitives.ButtonBase.OnClick()
   at System.Windows.Controls.Button.OnClick()
   at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
   at System.Windows.Controls.Control.OnMouseLeftButtonUp(Control ctrl, EventArgs e)
   at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)
 
To my Location-Issue:
Yes I tried to use two-way-binding for the user will get all the nodes back in the position where he left the nodes when saving. I forgot
to mention that I tried the location binding with and without animation (according to another post here in the forum). I can see the
databinding is working when I move a node a little bit with the mouse but the node disappears immediatelly when moving starts.
 

(1) It looks like your Condition class’s reference to any containing group/Condition is still using a Guid (actually Nullable) instead of a direct reference.

(2) Try confirming in the debugger or in code that the right Condition instance is in the model’s NodesSource collection, with the expected location Point. Also confirm that the corresponding Node (with .Data == that Condition instance) is in fact in the Diagram.Nodes collection, with the expected Node.Location value.

I’m wondering if the Node is actually present but located elsewhere (including nowhere if NaN,NaN). Or if the Node got removed from the Diagram for some reason.

Have you customized the DraggingTool at all?

to 1: I got to check this. But there shouldn’t be anything like this?!
2.) no I have no subclassed Dragging-tool!
for debugging-reasons I have a datagrid below my diagram where I can see my Nodessource-collection. This grid is showing me that the node is in the collection and that it has a location for example 90;75 after dragging the node to the diagram. But the node is gone?! Perhaps my Nodetemplate has a problem:

		<DataTemplate x:Key="Condition">
			<go:SpotPanel Style="{StaticResource SpotPanelStyle}"
					go:Node.ToSpot="MiddleLeft" go:Node.FromSpot="RightSide"
                	go:Node.Location ="{Binding Path=Data.Location, Mode=TwoWay}"
					MouseEnter="Node_MouseEnter" MouseLeave="Node_MouseLeave">
				    <go:NodePanel Sizing="Auto" go:SpotPanel.Main="True">
					<Path x:Name="Shape" Style="{StaticResource NodeShapeStyle}"
				    go:NodePanel.Figure="Rectangle" />
					<StackPanel>
						<TextBlock Text="{Binding Path=Data.SearchField.Column_name}" 
                                   Style="{StaticResource TextBlockStyle}" go:Part.SelectionAdorned="True" />
						<ComboBox x:Name="cboCondition"  ItemsSource="{Binding ElementName=CondLayoutroot, Path=DataContext.Operators}" 
									SelectedValue="{Binding Data.Operator,Mode=TwoWay}"
								   DisplayMemberPath="Description"  Margin="5"  />
						<Grid>
							<Grid.RowDefinitions>
								<RowDefinition/>
								<RowDefinition/>
								<RowDefinition/>
							</Grid.RowDefinitions>
							<Grid.ColumnDefinitions>
								<ColumnDefinition/>
								<ColumnDefinition/>
							</Grid.ColumnDefinitions>
						
						<TextBlock Text="Value from" Style="{StaticResource TextBlockStyle}" Margin="5"/>
							<TextBox Text="{Binding Path=Data.cValue1,Mode=TwoWay}" go:Part.SelectionAdorned="True" Grid.Column="1" Margin="5"
								 IsEnabled="{Binding ElementName=cboCondition, Path=SelectedItem.lUseFirstValue,Mode=TwoWay}"/>
						<TextBlock Text="Value to" Style="{StaticResource TextBlockStyle}" Margin="5" Grid.Row="1"/>
						<TextBox Text="{Binding Path=Data.cValue2,Mode=TwoWay}"  go:Part.SelectionAdorned="True"  
								 Margin="5" Grid.Row="1" Grid.Column="1"
								 IsEnabled="{Binding ElementName=cboCondition, Path=SelectedItem.lUseSecondValue,Mode=TwoWay}"/>
						<CheckBox IsChecked="{Binding Path=Data.lDynamic,Mode=TwoWay}" Content="dynamic Value" Grid.Row="2" Grid.Column="1"
								  go:Part.SelectionAdorned="True"  Margin="5"/>
						</Grid>
					</StackPanel>
				</go:NodePanel>
				
				<Ellipse Style="{StaticResource EllipseStyle}"
				   Stroke="{Binding Path=Node.Tag,
							Converter={StaticResource theBooleanBrushConverter}}"
				   go:Node.PortId="0"
				   go:Node.LinkableTo="False"
				   go:Node.FromSpot="MiddleBottom"
				   go:SpotPanel.Spot="MiddleBottom"
				   go:SpotPanel.Alignment="MiddleBottom" />
				<Ellipse Style="{StaticResource EllipseStyle}"
				   Stroke="{Binding Path=Node.Tag,
							Converter={StaticResource theBooleanBrushConverter}}"
				   go:Node.PortId="1"
				   go:Node.LinkableFrom="False"
				   go:Node.ToSpot="MiddleTop"
				   go:SpotPanel.Spot="MiddleTop"
				   go:SpotPanel.Alignment="MiddleTop" />
				<Ellipse Style="{StaticResource EllipseStyle}"
				   Stroke="{Binding Path=Node.Tag,
							Converter={StaticResource theBooleanBrushConverter}}"
				   go:Node.PortId="2"
				   go:Node.FromSpot="MiddleLeft"
				   go:Node.ToSpot="MiddleLeft"
				   go:SpotPanel.Spot="MiddleLeft"
				   go:SpotPanel.Alignment="MiddleLeft" />
				<Ellipse Style="{StaticResource EllipseStyle}"
				   Stroke="{Binding Path=Node.Tag,
							Converter={StaticResource theBooleanBrushConverter}}"
				   go:Node.PortId="3"
				   go:Node.FromSpot="MiddleRight"
				   go:Node.ToSpot="MiddleRight"
				   go:SpotPanel.Spot="MiddleRight"
				   go:SpotPanel.Alignment="MiddleRight" />
			</go:SpotPanel>
		</DataTemplate>

(2) OK, now that you’ve confirmed that the node data is present in the model’s NodesSource, you should check that the corresponding Node is present in the Diagram.

Call myDiagram.PartManager.FindNodeForData to find that Node, and check all of its properties to make sure it’s reasonable, including Location and Visibility.

And check the trace listener output for any warning or error messages. Maybe there’s some data-binding errors causing a problem.

Hi Walter,

I found my location problem: the location-values in my collection where ok, but the locations in my nodes where all NaN! The reason is my windows setting. The decimal point over here is “,” and so the databinding went wrong. I wrote a little converter-class and now all is as expected. The definiton of the databinding itself was ok.

But I’m still searching for a solution with the grouping error. I didn’t find anything guiding me to a problem. In my opinion the problem is that I have defined my whole Condition-object as the key and the SubGraphkeyfield is only a GUID. I guess that’s why there is a casting error?!

I’m glad you found the problem with node locations.

And yes, the problem is that models expect to treat all references to nodes in the same manner – whether by integer or string or Guid or object reference. In your case they should either all be Guids or all be memory references to Conditions.

I suppose you could still implement it by overriding GetGroupForNode and SetGroupNodeKey to handle the different representation for references from a node to its containing group. You could implement GetGroupForNode to get the Guid and then lookup the corresponding Condition object. And you could implement SetGroupNodeKey to set your Guid property to the Guid of the given Condition object.