When pasting, when do links get connected


#1

When I copy some nodes and the links that connect them, then paste them into a document, I get events for GoLayer.InsertedObject for the links first, before the nodes. At this point the links have no from node or to node. Later, the links are magically connected to nodes. What event can I listen for that corresponds to the endpoints of the link being set?
Also, if I needed to force nodes to be created before links during a paste operation, how could I do it?


#2

GoLink.ChangedFromPort and GoLink.ChangedToPort are the GoChangedEventArgs.SubHint values that you want to look for. (The event is GoDocument.Changed, of course, and the GoChangedEventArgs.Hint is GoLayer.ChangedObject, as it is for all GoObjects.)
GoView.CopyToClipboard and GoView.PasteFromClipboard both call GoDocument.CopyFromCollection to do the copying. But maybe you can change the order of the objects in the collection that is passed into GoView.CopyToClipboard, by overriding that method and calling the base method with a re-ordered collection.
But a better question is: why do you care? Or is it the order OK once you are handling the GoLink.ChangedFrom/ToPort events?


#3

Well, I might care and I might not. My application would benefit by using GoDiagram as a visual editor for documents that aren’t GoDocuments (as opposed to forcing my app’s document to be a GoDocument). In this situation, I would treat GoDocuments as just views of what my application considers a document. Ideally, I could respond to GoDocument.Changed events to trigger changes to the real document in parallel with changes to the GoDocument.
In that case, the interface to my app’s document would need to be analogous to that of GoDocument – for example, links being able to exist without endpoints, at least for awhile, so that when links get pasted first by Go, I can add links to the underlying document then fix them up later, when the endpoints get set in the GoDocument.
Or, if I could control the order of Go operations (e.g. pasting nodes before links), then maybe I could have a simpler API to the underlying document, i.e. require that endpoints of a link exist before links can be created. With this sort of interface, it’s easier to keep the document in a valid state.
Just trying to figure out a workable approach.
There seem to be many issues with trying to have GoDiagram edit something beyond a GoDocument. The biggest is probably undo/redo (whereby I probably need my own undo manager to wrap Go’s, since the GoDocument represents only a part of the real document), followed by having GoObjects hold hard references to objects outside the GoDocument (since it’s hard to get Go to serialize references to objects rather than the objects themselves. Go seems to want to “own” these objects, but it can’t because they belong to something else that doesn’t need Go at all).
A complete example of an application doing something like this would be very helpful if available.
It’s also possible that my thought process is completely wrong-headed, and that realistically Go needs to be the “center of attention” in applications that use it. Any comments you have would be appreciated.
Thanks


#4

Yes, that makes sense. Having a GoDocument be a “view” onto the real document is very common. Most of the more complicated samples do this–where the real document is actually an XML DOM.
However, those examples use a “batch” style of synchronization between the GoDocument and the real document, rather than an incremental one, which is what you are attempting to do and which you believe is required.
Probably the right compromise is to override GoDocument.FinishTransaction. At that time you can either “save” the whole document [batch style], or you can iterate over the all of the GoChangedEventArgs in the GoUndoManager.CurrentEdit (IF the base method returned true!) [incremental style].
You can extend the undo manager for your own objects. Basically, the GoUndoManager holds a list of GoUndoManagerCompoundEdits, <SPAN =“873281020-13092004”>each of which holds a list of GoChangedEventArgs. However, <SPAN =“873281020-13092004”>GoUndoManagerCompoundEdit <SPAN =“873281020-13092004”>really holds a list of IGoUndoableEdits. So you could define your own class(es) that implement <SPAN =“873281020-13092004”>IGoUndoableEdit, and add them onto the UndoManager’s list, in a class inheriting from <SPAN =“873281020-13092004”>GoUndoManager:

[Serializable]
public class AppEventArgs : EventArgs, IGoUndoableEdit {
public AppEventArgs() {}
... implement IGoUndoableEdit methods: PresentationName; Clear, CanUndo, Undo, CanRedo, Redo
// add your own App-specific change state/information
}
[Serializable]
public class AppUndoManager : GoUndoManager {
public AppUndoManager() {}
// call this method when your AppChanged event occurs:
public virtual void AppChanged(Object sender, AppEventArgs e) {
if (this.IsUndoing || this.IsRedoing) return;
GoUndoManagerCompoundEdit cedit = this.CurrentEdit;
if (cedit == null) {
cedit = new GoUndoManagerCompoundEdit();
this.CurrentEdit = cedit;
}
cedit.AddEdit(e); // or may be safer to add a copy of the AppEventArgs instead
}
}
I hope this is enough to get you started.

#5

Thanks much Walter for taking the time to provide these ideas.
I can see that the incremental approach will be more work, but there are real benefits to it, such as being able to recompute aggregates over the graph each time the user makes a change.
Let me go down this road awhile and see if I can get it to work. I may have some follow-ups later.
Thanks again
MSD


#6

Hmmm.
If I’d like to use both of your ideas (update the underlying doc in GoDocument.FinishTransaction and extend the undo manager to incorporate my app objects), then I’d want to include the changes to the app document (made in FinishTransaction) in the same transaction as the GoDocument changes.
So, I think I’d need to defer any call to base.FinishTransaction until after I made changes to the app document. But, I also don’t want to change the app document until I’m about to commit a top-level transaction.
Would it suffice to do something like this?
public override bool FinishTransaction(string tname) {
if (UndoManager != null && UndoManager.TransactionLevel == 1) {
//Here, analyze the contents of UndoManager.CurrentEdit and apply changes
}
//finally, finish off the transaction (and hope it returns true!)
return base.FinishTransaction(tname);
}
Under what circumstances would base.FinishTransaction return false?


#7

That’s the main criterion. I suppose if someone tried to call FinishTransaction when there hadn’t been any changes (because GoUndoManager.CurrentEdit was null), it would return false too.
Perhaps a better design would be to override GoUndoManager.EndTransaction instead of GoDocument.FinishTransaction. That way your undo manager can be used independently of the GoDocument, by whatever parts of your application are editing things unrelated to the GoDiagram graph. But all the standard code that ends up calling GoDocument.FinishTransaction will still go through your code.