Custom action on paste nodes

Hello,

In my application I have three different views with each a diagram. These three diagrams share the same NodesSource, but work on different Models, which share a common ancestor.

You can imagine this like a wizard. In each step the application gets into, you can add/manipulate a different type of node and see (but not manipulate) all nodes and links from the previous step. Links and nodes for the following steps are hidden. Nodes from the current step can be linked to nodes from the previous step.

It looks like this:

public class StepData : GraphLinksModel<StepNodeData, string, string, StepLinkData> { ... }

public class FirstStepData : StepData { ... }
public class SecondStepData : StepData { ... }
public class ThirdStepData : StepData { ... }

public class StepViewModel {
    public Diagram Diagram;
}

public class FirstStepViewModel : StepViewModel {
    public FirstStepViewModel() {
        base.Diagram.TemplateApplied += InitializeDiagramData;
    }

    public void InitializeDiagramData(object sender, DiagramEventArgs eventArgs) {
        base.Diagram.Model = new FirstStepData() {
            NodesSource = DiagramHandler.NodesSource,
            LinksSource = DiagramHandler.LinksSource
        };
    }
}

public class SecondStepViewModel : StepViewModel {
    public SecondStepViewModel () {
        base.Diagram.TemplateApplied += InitializeDiagramData;
    }

    public void InitializeDiagramData(object sender, DiagramEventArgs eventArgs) {
        base.Diagram.Model = new SecondStepData() {
            NodesSource = DiagramHandler.NodesSource,
            LinksSource = DiagramHandler.LinksSource
        };
    }
}

public class ThirdStepViewModel : StepViewModel {
    public ThirdStepViewModel() {
        base.Diagram.TemplateApplied += InitializeDiagramData;
    }

    public void InitializeDiagramData(object sender, DiagramEventArgs eventArgs) {
        base.Diagram.Model = new ThirdStepData() {
            NodesSource = DiagramHandler.NodesSource,
            LinksSource = DiagramHandler.LinksSource
        };
    }
}

public static class DiagramHandler {
    public static ObservableCollection<StepNodeData> NodesSource = new ObservableCollection<StepNodeData>
    public static ObservableCollection<StepLinkData> NodesSource = new ObservableCollection<StepLinkData>
}

public class StepNodeData : GraphLinksModelNodeData<string> { ... }

public class FirstStepNodeData : StepNodeData { ... }
public class SecondStepNodeData : StepNodeData { ... }
public class ThirdStepNodeData : StepNodeData { ... }

Now, when I’m done creating and linking a node in each step, I get back to the first step and copy+paste the node I added, all the hidden nodes linked to it are supposed to be copied+pasted with it. Therefore I’ve overridden the Copy method in a custom CommandHandler, which selects all linked hidden elements additionally to the currently selected FirstStepNode. Now when I paste the nodes, the pasting itself does work but the existing nodes and links become messed up as there is initialization done in the nodes’ constructors but the copies aren’t created via constructor call. So I need to do that initialization in the paste method but I don’t know how to retrieve the data from the clipboard. The following throws an OutOfMemoryException (even when there’s only one node in each step, so 2 links and 3 nodes in the clipboard):

Clipboard.GetData(this.Diagram.Model.DataFormat);

So, if a simple GetData doesn’t work I wonder how the Paste and PasteFromClipboard methods are implemented to retrieve the data from the clipboard, get the actual NodeData from them and then insert them into the diagram.

Here’s the definition of CommandHandler.PasteFromClipboard:

    protected virtual IDataCollection PasteFromClipboard() {
      Diagram diagram = this.Diagram;
      if (diagram == null) return null;
      IDiagramModel model = diagram.Model;
      if (model == null) return null;
      if (!ClipboardContainsData(model.DataFormat)) return null;
      IDataCollection sel = Clipboard.GetData(model.DataFormat) as IDataCollection;
      return sel;
    }

    private bool ClipboardContainsData(String format) {
      if (format == null || format == "") return false;
      try {
        return Clipboard.ContainsData(format);
      } catch {
      }
      return false;
    }

I assume your override of CommandHandler.Copy selects the hidden nodes and links and then calls the base method.

I wonder it if wouldn’t be easier if you had a single Diagram with multiple pairs of Layers in it. Then each step would just be showing a pair of Layers. At any step you would easily be able to traverse the graph to see what nodes are connected with given nodes.

This implementation still throws an OutOfMemoryException but when I continue, the method gets exited unfinished and afterwards the clone method of my node data classes are called and inserted. And they mess up the nodes and links again. Is it possible Paste() calls for PasteFromClipboard() and does something else on an OutOfMemoryException?

We used one diagram for all four (there is actually a fourth step but that doesn’t need any copy-functionality) modules in the beginning but the ViewModel grew to gigantic proportions, too large to handle properly. Additionally we use very large bitmaps as background images and we need to free allocated memory as soon as one switches from one Step to another, which we only managed to get done by disposing off of the Diagram and the View so even if we had a single Diagram, it would be disposed and recreated anew everytime one switches between the steps.

CommandHandler.Paste calls PasteFromClipboard within a try/catch. If there is an error it substitutes the clipboard’s internal clipboard, which is basically a global variable that is an IDataCollection.

My guess is that your data is not serializable, so the data cannot be put into the windows clipboard successfully. Are all of your data classes [Serializable] or otherwise made serializable?

You were right it wasn’t serializable. I had read about Clipboard issues with non-serializable classes but as the regular copy-paste worked I didn’t assume this to be the source.

Now I’ve marked the classes as Serializable but it still won’t work. I’m actually unsure as to which classes to mark as Serializable. Currently it’s:

  • StepData
  • StepNodeData
  • StepLinkData
  • FirstStepData ( : StepData)
  • FirstStepNodeData ( : StepNodeData)
  • SecondStepData ( : StepData)
  • SecondStepNodeData ( : StepNodeData)
  • SecondStepLinkData ( : StepLinkData)
  • ThirdStepData ( : StepData)
  • ThirdStepNodeData ( : StepNodeData)
  • ThirdStepLinkData ( : StepLinkData)

When I try to copy+paste there’s each an object of FirstStepNodeData, SecondStepLinkData, SecondStepNodeData, ThirdStepLinkData and ThirdStepNodeData selected. In my overridden CopyToClipboard method there’s the FirstStepData set as model and when I check the IsSerializable property of its Type, it says true. I couldn’t find any information to this searching for serialization, but do I have to mark all classes which are used as properties in the selected classes as serializable as well? e.g. SecondNodeData class has custom Size class as property but that is not marked Serializable.

You also have to make sure each data member is serializable.

Alright, so I checked some of the classes but they have properties of type Interface or external classes, which simply are not set as Serializable. How may I approach this now? And which classes exactly have to be marked as Serializable? Only the NodeData and LinkData ones or only the Data ones or both?

If you really want to support serialization of your data, you may need to implement ISerializable.