Serialization

Since I change my Element class to inherit from GraphLinksModelNodeData I can’t serialize it.
I get the following exception:

Cannot serialize member Northwoods.GoXam.Model.GraphLinksModelNodeData1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].MemberKeys of type System.Collections.Generic.IList1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] because it is an interface.

I assume you added a property of type IList. Try specifying a serializable type that implements IList.

This is all related to your question about serializing to/from the clipboard?

I think it has all todo with this.
Maybe I must completely over think my model!

Actually, I want a very simple Node class. Few Properties that are bound to XAML (Text, BackgroundBrush, ForegroundBrush, BorderBrush, FontFamily, FontSize).
But the Data for that comes from a complex Model which holds somewhat like Layers that hold a Group of Displays and Function for each Node. The Displays had Properties that can be allowed to copy to the Node or not. So the ForegroundBrush can come from one Display where else the Text and BackgroundBrush can come from another Display.
If I change my Layers the Display and Function also change and the Nodes will be updated. Now I have some sort of PropertyGrid, in which I can change Properties of the Node, but actual that are no Properties of the Node but Properties of the underlying Layers.
Sound kind of weird, but I have some requirements to do something like that.

This is of cause a problem with the UndoManager and the Serialization cause changing the Node is not changing the LayerModel and serializing the Node doesn’t serialize all Information needed in the LayerModel.

As I describe it to you, I realize how weird this sounds - my boss surely makes troubble if I change the hole model now.

I didn’t change my Element class except inherit from GraphLinksModelNodeData!

Perhaps, I can handle the LayerModel like some kind of unbound node, then I can use UndoManager with it too.
But how to handle PropertyChanges of ListItems? Layer has a List of DisplayTypes and the Properties of each Display can changed by the user.

You don’t need to use GraphLinksModelNodeData if you don’t want to. That class is just there for convenience – most people find it easier to use a predefined model class than to implement everything on their own.

Did you change the base class of your Element class just for supporting serialization to the Windows Clipboard? If that was the case, then implementing your own CopyToClipboard and PasteFromClipboard overrides (that do not call the base methods) sounds like the right thing to do.

Or was there some other reason to use GraphLinksModelNodeData?

I thought I must inherit this to use undo/redo - that was the only reason.

Based on the Drag’n’Drop Example I send you several weeks ago, I tried to test a new Datamodel implementation and it starts promising. My only problem so far are List Properties in the NodeData and how to handle propertychange of a ListItem with the UndoManager. Does he simply save a record with propertyname (as I define it), oldValue and newValue?
If so I can send hin (“Displays[x].Color”, oldValue, newValue), then I can implement (override) IChangeDataValue and build some parser to set the value on the right List Property.

When modifying collections use the OldParam (when removing) and NewParam (for inserting) properties of the ModelChangedEventArgs to record additional information such as index or key.

For regular properties on objects, such as “Color”, it’s just a regular property change.

I don’t think you understanding me.
For example:
I have something like this, and bind a property BackgroundBrush with a Converter to Path=Data.Displays[0].Color.

How can the UndoManager handle this?

[Serializable]
public class MyNode : GraphLinksModelNodeData<Guid>
{
    private Color color;

    public MyNode() { }
    public MyNode(String name)
    {
        _Name = name;
        Key = Guid.NewGuid();
        Displays = new ObservableCollection<Display>();
        Displays.Add(new Display() {Color = Colors.Aqua});
    }


    private String _Name;
    public string Name
    {
        get { return _Name; }
        set
        {
            if (_Name != value)
            {
                var old = _Name;
                _Name = value;
                RaisePropertyChanged("Name", old, value);
            }
        }
    }

    private ObservableCollection<Display> displays;
    public ObservableCollection<Display> Displays
    {
        get { return displays; }
        set
        {
            if (displays == value) return;
            var oldValue = displays;
            displays = value;
            RaisePropertyChanged("Displays" , oldValue, value);
        }
    }
}

public class Display
{
    public Color Color { get; set; }
}

Modifying any property on your Display class is like any other. Implement INotifyPropertyChanged and raise a PropertyChanged event with an instance of ModelChangedEventArgs.

Maybe later I can post the sources for the model data classes, to save you time in implementing the pieces you want.

That would be helpful.

Here’s the source code for the GraphLinksModel data classes:
http://goxam.com/go/GraphLinksModelData.cs.txt

Here’s the source code for the GraphModel data classes:
http://goxam.com/go/GraphModelData.cs.txt

Here’s the source code for the TreeModel data classes:
http://goxam.com/go/TreeModelData.cs.txt

I can’t get it working! I implemented INotifyPropertyChanged on the Display class and raise it with a instance of ModelChangedEventArgs.
But if I press ctrl-z there are no color changes.

[Serializable]
public class MyNode : GraphLinksModelNodeData<Guid>
{
    public MyNode(String name)
    {
        _Name = name;
        Key = Guid.NewGuid();
        Displays = new ObservableCollection<Display>();
        Displays.Add(new Display() {Color = Colors.Aqua});
    }

    private String _Name;
    private ObservableCollection<Display> displays;

    public string Name
    {
        get { return _Name; }
        set
        {
            if (_Name != value)
            {
                var old = _Name;
                _Name = value;
                RaisePropertyChanged("Name", old, value);
            }
        }
    }

    public ObservableCollection<Display> Displays
    {
        get { return displays; }
        set
        {
            if (displays == value) return;
            var oldValue = displays;
            displays = value;
            RaisePropertyChanged("Displays" , oldValue, value);
        }
    }

    public override void ChangeDataValue(ModelChangedEventArgs e, bool undo)
    {
        base.ChangeDataValue(e, undo);
        Debug.WriteLine(e.PropertyName + " " + e.OldValue + " " + e.NewValue);
    }
}

public class Display : INotifyPropertyChanged
{
    private Color color;

    public Color Color
    {
        get { return color; }
        set
        {
            if (value.Equals(color)) return;
            var oldValue = Color;
            color = value;
            OnPropertyChanged(new ModelChangedEventArgs("Color", this, oldValue, value));
            Debug.WriteLine("ColorChanged");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(ModelChangedEventArgs e)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, e);
    }
}

public class MyLink : GraphLinksModelLinkData<Guid, Guid>
{
}

Maybe you also need to implement IChangeDataValue and ICloneable.

Take a look at the Socket class in the Dynamic Ports sample.

You probably also need to override Clone in your MyNode class, to make sure the Displays are probably copied over, since you probably want to avoid sharing those individual Display objects. (But that’s just my guess – I don’t know what your app wants to do.)

It’s not working!
I implemented it as in the Socket class, but the ChangeDataValue was not fired. I found out that the PropertyChanged Event is always null, but the DataContext is set.
I have no idea.
FIY this is just a demo program to proof things for the greater application.

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using System.Windows.Media;  
    using Northwoods.GoXam;
    using Northwoods.GoXam.Model;
    using Northwoods.GoXam.Tool;

namespace WpfApplication54WalterDnD
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    /// <summary>
    ///     Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private Point? startPoint;

        public MainWindow()
        {
            InitializeComponent();
            AllNodes = new ObservableCollection<MyNode>();
            DataContext = this;
        }

        public ObservableCollection<MyNode> AllNodes { get; set; }

        private void MouseLeftButtonDownExecute(MouseButtonEventArgs arg)
        {
            if (arg.ButtonState == MouseButtonState.Pressed)
            {
                startPoint = arg.GetPosition(null);
            }
        }

        private void MouseMoveExecute(MouseEventArgs arg)
        {
            if (arg.LeftButton == MouseButtonState.Pressed && startPoint != null)
            {
                var mousePos = arg.GetPosition(null);
                Vector diff = startPoint.Value - mousePos;

                if (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
                    Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance)
                {
                    ListBox listBox = Part.FindAncestor<ListBox>((Visual) arg.OriginalSource);
                    if (listBox != null)
                    {
                        ListBoxItem listBoxItem = Part.FindAncestor<ListBoxItem>((Visual) arg.OriginalSource);
                        if (listBoxItem != null)
                        {
                            var data = listBoxItem.Content;
                            Debug.WriteLine(data);
                            DataObject dragData = new DataObject(DataFormats.StringFormat, data);
                            DragDrop.DoDragDrop(listBox, dragData, DragDropEffects.Copy);
                            Debug.WriteLine("MouseMove");
                        }
                    }
                }
            }
            else
            {
                startPoint = null;
            }
        }

        private void ListBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            MouseLeftButtonDownExecute(e);
        }

        private void ListBox_MouseMove(object sender, MouseEventArgs e)
        {
            MouseMoveExecute(e);
        }

        private void myDiagram_ExternalObjectsDropped(object sender, DiagramEventArgs e)
        {
            myDiagram.Focus();
        }
    }

    public class CustomDraggingTool : DraggingTool
    {
        private MyNode addedNode;

        protected override void DragOver(Point pt, bool moving, bool copying)
        {
            base.DragOver(pt, moving, copying);
            Part part = Diagram.Panel.FindElementsAt(pt, Part.FindAncestor<Part>, p => !Diagram.SelectedParts.Contains(p), SearchLayers.Parts).FirstOrDefault();
            if (part != null)
            {
                Debug.WriteLine("Dragging over: " + part);       
            }
        }

        protected override void DropOnto(Point pt)
        {
            base.DropOnto(pt);
            Part part = Diagram.Panel.FindElementsAt(pt, Part.FindAncestor<Part>, p => !Diagram.SelectedParts.Contains(p), SearchLayers.Parts).FirstOrDefault();
            if (part != null)
            {
                Debug.WriteLine("Drop onto: " + part);
                var myNode = Diagram.SelectedNode.Data as MyNode;
                if (myNode != null)
                {
                    var node = part.Data as MyNode;
                    if (node != null)
                    {
                        Diagram.StartTransaction("Change");
                        node.Name = myNode.Name;
                        node.Displays[0].Color = Colors.Red;
                        Diagram.CommitTransaction("Change");
                        DoCancel();
                    }
                }
            }
            addedNode = null;
        }

        public override void DoDragEnter(DragEventArgs e)
        {
            var droppedItem = e.Data.GetData(DataFormats.StringFormat) as String;
            if (droppedItem != null && addedNode == null)
            {
                var dataCollection = Diagram.Model.CreateDataCollection();
                addedNode = new MyNode(droppedItem);
                dataCollection.AddNode(addedNode);
                e.Data.SetData(Diagram.Model.DataFormat, dataCollection);
            }
        }

        public override void DoDragLeave(DragEventArgs e)
        {
            if (e.Effects == DragDropEffects.None)
            {
                addedNode = null;
            }
            base.DoDragLeave(e);
        }
    }

    public class MyModel : GraphLinksModel<MyNode, Guid, String, MyLink>
    {
        public MyModel()
        {
            Modifiable = true;
            HasUndoManager = true;
        }
    }

    [Serializable]
    public class MyNode : GraphLinksModelNodeData<Guid>
    {
        public MyNode(String name)
        {
            _Name = name;
            Key = Guid.NewGuid();
            Displays = new ObservableCollection<Display>();
            Displays.Add(new Display() { Parent = this, Color = Colors.Aqua });
        }

        private String _Name;
        private ObservableCollection<Display> displays;

        public string Name
        {
            get { return _Name; }
            set
            {
                if (_Name != value)
                {
                    var old = _Name;
                    _Name = value;
                    RaisePropertyChanged("Name", old, value);
                }
            }
        }

        public ObservableCollection<Display> Displays
        {
            get { return displays; }
            set
            {
                if (displays == value) return;
                var oldValue = displays;
                displays = value;
                RaisePropertyChanged("Displays" , oldValue, value);
            }
        }

        public override object Clone()
        {
            MyNode result = (MyNode) base.Clone();
            //result.Displays = new ObservableCollection<Display>(Displays.Select(d => new Display() { Color = d.Color }));
            return result;
        }

        public override void ChangeDataValue(ModelChangedEventArgs e, bool undo)
        {
            Display display = e.Data as Display;
            if (display != null) 
                display.ChangeDataValue(e, undo);
            else
                base.ChangeDataValue(e, undo);
            Debug.WriteLine(e.PropertyName + " " + e.OldValue + " " + e.NewValue);
        }
    }

    [Serializable]
    public class Display : INotifyPropertyChanged, IChangeDataValue, ICloneable
    {
        public MyNode Parent;

        private Color color;

        public Color Color
        {
            get { return color; }
            set
            {
                if (value.Equals(color)) return;
                var oldValue = Color;
                color = value;
                RaisePropertyChanged("Color", oldValue, value);
                Debug.WriteLine("ColorChanged");
            }
        }

        [field: NonSerializedAttribute()]
        public event PropertyChangedEventHandler PropertyChanged;

        protected void RaisePropertyChanged(String pname, Object oldval, Object newval)
        {
            ModelChangedEventArgs e = new ModelChangedEventArgs(pname, this, oldval, newval);
            // implement INotifyPropertyChanged:
            if (this.PropertyChanged != null) this.PropertyChanged(this, e);
            // implement support for model and undo/redo:
            if (this.Parent != null) this.Parent.OnPropertyChanged(e);
        }

        public void ChangeDataValue(ModelChangedEventArgs e, bool undo)
        {
            switch (e.PropertyName)
            {
                case "Color":
                    Color = (Color) e.GetValue(undo);
                    break;
            }
        }

        public object Clone()
        {
            return MemberwiseClone();
        }
    }

    public class MyLink : GraphLinksModelLinkData<Guid, Guid>
    {
    }

}


    <Window x:Class="WpfApplication54WalterDnD.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:go="http://schemas.nwoods.com/GoXam"
        xmlns:wpfApplication54WalterDnD="clr-namespace:WpfApplication54WalterDnD"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <go:StringBrushConverter x:Key="StringBrushConverter" />
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="118*" />
            <ColumnDefinition Width="399*" />
        </Grid.ColumnDefinitions>

        <ListBox PreviewMouseLeftButtonDown="ListBox_PreviewMouseLeftButtonDown" MouseMove="ListBox_MouseMove">
            <ListBox.Items>
                <ListBoxItem>SimpleNode</ListBoxItem>
                <ListBoxItem>ComplexNode</ListBoxItem>
                <ListBoxItem>SuperNode</ListBoxItem>
            </ListBox.Items>
        </ListBox>

        <go:Diagram Grid.Column="1" x:Name="myDiagram" AllowDrop="True" Background="Bisque"
                    NodesSource="{Binding AllNodes}"
                    VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch"
                    GridSnapCellSize="5 5"
                    GridSnapEnabled="True"
                    GridVisible="True" ExternalObjectsDropped="myDiagram_ExternalObjectsDropped">
            <go:Diagram.DraggingTool>
                <wpfApplication54WalterDnD:CustomDraggingTool />
            </go:Diagram.DraggingTool>
            <go:Diagram.Model>
                <wpfApplication54WalterDnD:MyModel />
            </go:Diagram.Model>
            <go:Diagram.NodeTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Data.Name}" go:Part.SelectionAdorned="True" Background="{Binding Data.Displays[0].Color, Converter={StaticResource StringBrushConverter}}"
                               go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}" />
                </DataTemplate>
            </go:Diagram.NodeTemplate>
            <go:Diagram.GridPattern>
                <go:GridPattern CellSize="10 10">
                    <Path Stroke="LightGray" StrokeThickness="0.2" go:GridPattern.Figure="HorizontalLine" />
                    <Path Stroke="LightGray" StrokeThickness="0.2" go:GridPattern.Figure="VerticalLine" />
                    <Path Stroke="White" StrokeThickness="0.2" go:GridPattern.Figure="HorizontalLine" go:GridPattern.Interval="5" />
                    <Path Stroke="White" StrokeThickness="0.2" go:GridPattern.Figure="VerticalLine" go:GridPattern.Interval="5" />
                    <Path Stroke="Black" StrokeThickness="0.3" go:GridPattern.Figure="HorizontalLine" go:GridPattern.Interval="10" />
                    <Path Stroke="Black" StrokeThickness="0.3" go:GridPattern.Figure="VerticalLine" go:GridPattern.Interval="10" />
                </go:GridPattern>
            </go:Diagram.GridPattern>
        </go:Diagram>

    </Grid>
</Window>

Drag a Node over an existing to changed color and than try ctrl-z. It only changes the Text if it’s necessary (e.g. drop complex node over simple node).

It’s at least misleading and probably wrong to call DoCancel when you don’t really mean to cancel the whole operation.

I’ll try out your code when I get a chance.

When is it that “PropertyChanged Event is always null, but the DataContext is set”?

I want that if I drop a Node onto another already existing Node some of the properties are changed, but the new Node should not appear in the Diagram. I thought it was the right way to do it, isn’t it?

The Display internal PropertyChanged is null always (as in your Dynamic Ports example) - in the I-Net they say the DataContext must set for this Event to be functioning, but I think this is in another case. I just want to ask if it’s right so.

The problem was that the Display.RaisePropertyChanged event wasn’t recording the ModelChangedEventArgs in the UndoManager. The reason is that the Display.Parent property wasn’t set, so there was no way to know who’s responsible.

So I implemented the MyNode.Clone method by making sure that the Display.Parent property got set properly, to the newly copied MyNode.

    public override object Clone() {
      MyNode result = (MyNode)base.Clone();
      result.Displays = new ObservableCollection<Display>(Displays.Select(d =>
          new Display() {
             Parent = result,
             Color = d.Color
          }));
      return result;
    }

And now the color change of a Display is able to be undone.

Oh, wait – I had commented out the call to DoCancel in your DraggingTool. Putting that call back in causes things not to work properly, as I had feared.

I think you really want to move the selection back to its original location, not cancel the whole operation of the tool, including the changing of the Display.Color.

        foreach (var kvp in this.DraggedParts) {
          Node n = kvp.Key as Node;
          if (n != null) {
            n.Location = kvp.Value.Point;
          }
        }

Hi Walter,
thank you very much that you’ve found time to look at this.
Now I have the feeling I can try to do this stuff in the original application and get it work.

Upon review, it seems to me that MyNode.Clone should call Display.Clone and then set its Parent property, not what I wrote.

Also, to explain further, DoCancel() in a tool’s operation will rollback any transaction. Once changing the color was recorded by the UndoManager, that is why the color change would be undone.

So maybe another possibility is to call DoCancel first and then change the color. I have not tried this, though.