GraphLinksModel with interfaces

I’m trying to declare my model using interfaces instead of concrete types, like so:

public class MyModel : GraphLinksModel<INode, INodeKey, string, ILink> { public MyModel { Modifiable = true; } }

I then create a new instance of the Node class, explicitly assign its Key property, and call MyModel.AddNode(). I always get back an InvalidOperationException with the text:

“Found duplicate key ‘null’ for node; override MakeNodeKeyUnique to modify data”

I overrode MakeNodeKeyUnique just so I could have a breakpoint. I verified that the nodedata that gets passed in actually has a key value (and since it’s the first node in the diagram it’s unique). But it looks like GoXam is thinking the key is null. (My implementation of INodeKey is a class that overrides the virtual bool Object.Equals(object) method. It does not implement INotifyPropertyChanged, IChangeDataValue, or ICloneable.)

When I switch my model definition to use concrete types for the node and link types, everything just works:

public class MyModel : GraphLinksModel<<font ="apple-style-span"="" color="#ff0000">Node</font>, INodeKey, string, <font ="apple-style-span"="" color="#ff0000">Link</font>> { public MyModel { Modifiable = true; } }

What the heck is going on?

edit: I’m running in Silverlight 4, if it matters.

If the key value is null for that particular node data object, it tries to generate a reasonable value.
But if the node key type is INodeKey, however you have defined it, it won’t know what to do.

I don’t know why it would be different with your particular node data type declared.

This seems like a bug then. I’ve verified with a debugger that INode.Key has a value and is definitely not null.

Could you also see what value is returned by GraphLinksModel.FindKeyForNode on that object?

What is the value of GraphLinksModel.NodeKeyPath?
Is it just “Key”?

What is the result of calling GetType() on your data object?
FindKeyForNode calls Type.GetProperty on that Type and then calls PropertyInfo.GetValue on that result.
It then casts that result to your INodeKey type and returns it.

When I construct my model with <font =“apple-style-span”="" color="#ff0000">interfaces for NodeType, NodeKey, and LinkType I get the following:

FindKeyForNode returns null.
NodeKeyPath is “” which causes a the call to GetProperty() to return a null PropertyInfo object.
GetType() on my object returns the concrete class that implements the interface.

When I construct my model with the <font =“apple-style-span”="" color="#ff0000">concrete classes for NodeType and LinkType and an interface for NodeKey I get the following:

FindKeyForNode returns the expected object instance which is the key value for the node.
NodeKeyPath is “Key”
GetType() is the same type as with the interfaces
Calling PropertyInfo.GetValue() returns the expected key and everything works.

Here’s the code I used:

[code]protected override void InsertNode(NodeModel nodedata)  // NodeModel = concrete, INodeModel = interface
{
   var key = FindKeyForNode(nodedata);
   var path = NodeKeyPath;
   var type = nodedata.GetType();
   var property = type.GetProperty(path);
   var result = property.GetValue(nodedata, new object[] { });
 
   base.InsertNode(nodedata);
}[/code]
Here's an excerpt of my INodeModel interface:
[code]public interface INodeModel : INotifyPropertyChanged, IChangeDataValue, ICloneable
{
   ...

IObjectReference Key { <span =“apple-style-span”="" style=“color: rgb0, 139, 139; “>get; }

<font =“apple-style-span”=”” face=“Verdana, Arial, Helvetica, sans-serif”><span =“apple-style-span”="" style=“font-family: Consolas; : rgb255, 255, 255; “>}[/code]
<span =“apple-style-span”=”” style=“font-size: 13px; line-height: 18px; “><font =“apple-style-span”=”” face=“Verdana, Arial, Helvetica, sans-serif”>Do I need to make the Key property read/write?  (I was honestly hoping to exclude it; it’s only there right now because someone wrote some poor code which uses it.)
<span =“apple-style-span”="">
<span =“apple-style-span”=""><font =“apple-style-span”="" face=“Verdana, Arial, Helvetica, sans-serif”>To be clear, my Node class inherits from GraphLinksModelNodeData and implements the INode interface (to get the other members I excluded).

<span =“apple-style-span”=""><font =“apple-style-span”="" face=“Verdana, Arial, Helvetica, sans-serif”>

It looks like you should set NodeKeyPath = “Key”. Does that help?

The GraphLinksModel constructor would do that automatically except that the .NET type system cannot determine that GraphLinksModelNodeData is assignable from INode.

Furthermore I find this even more confusing because it appears that you have changed the types names within this one forum topic. If that’s the case, it would help if you used consistent naming. If that’s not the case, then I’m surprised you didn’t get compiler warnings, since the IObjectReference Key { get; } interface property isn’t satisfied by the INodeKey { get; set; } class property in the GraphLinksModelNodeData class.

My apologies for changing the type names. The type names potentially reveal proprietary information so I changed them. I should have taken more care to change them consistently. I’ve gone back and edited my last post for clarity to future readers.

Setting NodeKeyPath does help, but it just gets me to the next problem: when I draw a link between two nodes, I get the following exception:

System.InvalidOperationException: Override InsertLink(NodeType, PortKey, NodeType, PortKey) to support creating a new link

Stack trace:
at #w.#P.#tc(IDiagramModel model, String msg)
at Northwoods.GoXam.Model.GraphLinksModel4.InsertLink(NodeType fromdata, PortKey fromparam, NodeType todata, PortKey toparam) at Northwoods.GoXam.Model.GraphLinksModel4.AddLink(NodeType fromdata, PortKey fromparam, NodeType todata, PortKey toparam)
at Northwoods.GoXam.Model.GraphLinksModel`4.#rn(Object fromdata, Object fromparam, Object todata, Object toparam)
at Northwoods.GoXam.Tool.LinkingTool.DoMouseUp()
at Northwoods.GoXam.DiagramPanel.OnMouseLeftButtonUp(MouseButtonEventArgs e)
at Northwoods.GoXam.DiagramPanel.#Iz(Object s, MouseButtonEventArgs e)
at MS.Internal.CoreInvokeHandler.InvokeEventHandler(Int32 typeIndex, Delegate handlerDelegate, Object sender, Object args)
at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)

My Link class does have a default constructor.

Ah, that would explain it. If anyone is ever not comfortable posting in this forum, they are always welcome to send e-mail. In fact, we get more e-mail by far than messages here in the forum.

Regarding creating a new link, if the model only knows about your ILink type as the LinkType, then it can’t construct a new instance of it because it’s an interface rather than a regular class with a default constructor. That’s why you need to override that InsertLink method so that you can create a new link data object and call InsertLink(LinkType).

When you had specified a class type (Link), which apparently inherits from GraphLinksModelLinkData, it didn’t have a problem calling Activator.CreateInstance.

More generally I should say that if you really want to use interfaces when instantiating a generic class, you can, but you had better have a good reason for it. Otherwise you’ll have to do all of the other work related to making a usable model. I bet you could find other problems too, although it’s plausible that your particular application might not ever run into them.

Yes, I noticed some issues with GraphLinksModel.Save and Load dealing with converting the interfaces to the appropriately templated node/link base class type. Maybe this is one problem on which I’ll take a pass and accept the higher coupling. Thanks for your help.

How did you solve your problem? Because I’m also using an interface as Link Type and my concrete class doesn’t inherited from GraphLinksModelLink. Intuitively, I decided to override the InsertLink method in the model but the code crashed before.

at Northwoods.GoXam.Model.GraphLinksModel`4.#rn(Object fromdata, Object fromparam, Object todata, Object toparam)

at Northwoods.GoXam.Tool.LinkingTool.DoMouseUp()

at Northwoods.GoXam.DiagramPanel.OnMouseUp(MouseButtonEventArgs e)

at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)

at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)

at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)

at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)

at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)

What I'm doing wrong?

I believe overriding InsertLink was sufficient for me so I’m not sure why your code is crashing. Walter?

Maybe by “crashing” pstjean meant raising the same exception that you got and that is expected in such circumstances.

Fundamentally the programmer has to provide an implementation in order to choose and allocate the class that implements the declared LinkType interface. After all, there could be any number of such classes, and how is the model supposed to know which one to instantiate?

Hi Walter, here is how it looks. What am I doing wrong?

    Private Function PopulateModel() As CustomModel
        Dim objModel As CustomModel = New CustomModel
        objModel.Modifiable = True
        objModel.ValidUnconnectedLinks = GoXam.Model.ValidUnconnectedLinks.Allowed
        objModel.NodeKeyPath = "GUID"

        objModel.NodesSource = New ObjectModel.ObservableCollection(Of Connector)
        objModel.LinksSource = New ObjectModel.ObservableCollection(Of IGraphLink)


        Dim stream1 As Stream = New Stream()
        Dim stream2 As Stream = New Stream()
        Dim stream3 As Stream = New Stream
        Dim streamConnector1 As StreamConnector = New StreamConnector
        Dim streamConnector2 As StreamConnector = New StreamConnector
        Dim streamConnector3 As StreamConnector = New StreamConnector
        Dim streamConnector4 As StreamConnector = New StreamConnector
        Dim streamConnector5 As StreamConnector = New StreamConnector
        Dim streamConnector6 As StreamConnector = New StreamConnector

        stream1.Name = "stream1"
        stream2.Name = "stream2"
        stream3.Name = "stream3"

        stream1.FromConnector = streamConnector1.GUID
        stream1.ToConnector = streamConnector2.GUID

        stream2.FromConnector = streamConnector3.GUID
        stream2.ToConnector = streamConnector4.GUID

        stream3.FromConnector = streamConnector5.GUID
        stream3.ToConnector = streamConnector6.GUID

        objModel.AddLink(stream2)
        objModel.AddLink(stream1)
        objModel.AddNode(streamConnector1)
        objModel.AddNode(streamConnector2)
        objModel.AddNode(streamConnector3)
        objModel.AddNode(streamConnector4)

        objModel.AddNode(streamConnector5)
        objModel.AddNode(streamConnector6)
        objModel.AddLink(stream3)


        Return objModel
    End Function

I forgot to mention but the exception occurred when I’m trying to connect two nodes in the diagram.

I found it. It was because the PortKey was a System.Guid Type. I only replace that type by a String Type and it’s working!!