UndoManager - Exception with RaisePropertyChanged

Hi everyone,

I had an issue when activating the UndoManager for my model. And I was able to reproduce the same issue with the UpdateDemo provided by GoWPFDemo.

Mainly, what I’am doing is changing the node’s color depending on whether it is linked from or not.

Here is the UpdateDemo code with my changes:

<code>
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using Northwoods.GoXam.Model;

    namespace UpdateDemo {
      public partial class UpdateDemo : UserControl {
        public UpdateDemo() {
          InitializeComponent();

          // create a couple of nodes initially connected by a link
          var nodes = new ObservableCollection<TestData>() {
            new TestData() { Key="N1", Location=new Point(0, 0) },
            new TestData() { Key="N2", Location=new Point(100, 100) }
          };
          var links = new ObservableCollection<TestLink>() {
            new TestLink() { From="N1", To="N2", Text="N1-N2" }
          };
          var model = new TestModel();
          model.NodesSource = nodes;
          model.LinksSource = links;
          model.Modifiable = true;
          model.HasUndoManager = true;
          model.Changed += model_Changed;  // update the log and TreeView

          // a background double-click creates a new node
          myDiagram.Model = model;
          myDiagram.ClickCreatingTool.DoubleClick = true;
          myDiagram.ClickCreatingTool.PrototypeData = new TestData() { Key="I1" };

          // setup up the second Diagram that shares the single Model
          myDiagram2.Model = model;
          myDiagram2.ClickCreatingTool.DoubleClick = true;
          myDiagram2.ClickCreatingTool.PrototypeData = new TestData() { Key="I1" };
        }

        void model_Changed(object sender, ModelChangedEventArgs e) {
          // add an entry to the log
          if (e.Change >= 0) 
          {
            if (ShowChanges.IsChecked == true) 
            {
              myLog.Text += e.ToString() + Environment.NewLine;
              myScroller.ScrollToVerticalOffset(9e9);
            }
          } 
          else 
          {
            if (ShowLayouts.IsChecked == true ||
                (!"Layout".Equals(e.Data) && !"DelayedRouting".Equals(e.Data))) 
            {
              myLog.Text += e.ToString() + Environment.NewLine;
              myScroller.ScrollToVerticalOffset(9e9);
            }
          }

          // add an item to the tree view showing the UndoManager state
          if (e.Change == ModelChange.CommittedTransaction) 
          {
            UndoManager.CompoundEdit cedit = e.OldValue as UndoManager.CompoundEdit;
            if (cedit != null) 
            {
              if (this.EditToRedo != null) 
              {
                // remove from TreeView all transactions starting with this.EditToRedo
                ItemCollection coll = myTreeView.Items;
                int idx = coll.IndexOf(this.EditToRedo);
                if (idx >= 0) 
                {
                  while (coll.Count > idx) coll.RemoveAt(idx);
                }

                this.EditToRedo = null;
              }

              // add a TreeViewItem representing the completed transaction
              TreeViewItem citem = new TreeViewItem();
              citem.Tag = cedit;
              citem.Header = cedit.Name;
              // all of the changes within the transaction are tree view children 
              foreach (IUndoableEdit edit in cedit.Edits) 
              {
                TreeViewItem eitem = new TreeViewItem();
                eitem.Tag = edit;
                eitem.Header = edit.ToString();
                citem.Items.Add(eitem);
              }
              myTreeView.Items.Add(citem);
            }
          } 
          else if (e.Change == ModelChange.FinishedUndo || e.Change == ModelChange.FinishedRedo) 
          {
            // unselect any currently selected transaction
            if (this.EditToRedo != null) 
            {
              this.EditToRedo.IsSelected = false;
              this.EditToRedo = null;
            }

            IUndoableEdit nextedit = myDiagram.Model.UndoManager.EditToRedo;
            if (nextedit != null) 
            {
              // find the next edit to redo, and select it
              foreach (TreeViewItem item in myTreeView.Items) 
              {
                if (item.Tag == nextedit) {
                  item.IsSelected = true;
                  this.EditToRedo = item;
                  break;
                }
              }
            }
          }
        }

        private TreeViewItem EditToRedo { get; set; }

        private void ClearLog(object sender, RoutedEventArgs e) {
          myLog.Text = "";
        }
      }

      public class TestModel : GraphLinksModel<TestData, String, String, TestLink> {
        // initialize each link''s text label to a string
        protected override TestLink InsertLink(TestData fromdata, string fromparam, TestData todata, string toparam) {
          TestLink link = new TestLink() { From=fromdata.Key, FromPort=fromparam, To=todata.Key,ToPort=toparam};
          link.Text = link.From + "-" + link.To;
          var links = this.LinksSource as IList<TestLink>;
          if (link != null) links.Add(link);
            fromdata.LinkedFrom = true;
          return link;
        }
      }

        public class TestData : GraphLinksModelNodeData<String>
        {
            private bool _linkedFrom;

            public bool LinkedFrom
            {
                get { return _linkedFrom; }
                set
                {
                    var oldColor = DataColor;
                    _linkedFrom = value;
                    RaisePropertyChanged("DataColor", oldColor, DataColor);
                }
            }

            public Brush DataColor
            {
                get
                {
                    return LinkedFrom ? Brushes.Green : Brushes.DarkRed;
                }
            }

            public TestData()
            {
                LinkedFrom = false;
            }
        }

      public class TestLink : GraphLinksModelLinkData<String, String> { }
    }
</code>

As you may have noticed, I only modified the TestData class and added a line in the overridden InsertLink method to set the LinkedFrom flag.

In the xaml file I changed the NodeTemplate to this:

<code>
    <DataTemplate x:Key="NodeTemplate">
    <go:NodePanel Sizing="Auto"
                go:Part.SelectionAdorned="True"
                go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">
    <go:NodeShape go:NodePanel.Figure="Pentagon"
                  Fill="{Binding Path=Data.DataColor}" Stroke="Black" StrokeThickness="1"
                  go:Node.PortId=""
                  go:Node.LinkableFrom="True" go:Node.LinkableTo="True" />
    <TextBlock Text="{Binding Path=Data.Key}" Margin="10"
               HorizontalAlignment="Center" VerticalAlignment="Center" />
  </go:NodePanel>
</DataTemplate>
</code> 

Now whenever the Undo has to remove a link I get the following exception:
An unhandled exception of type ‘System.InvalidOperationException’ occurred in Northwoods.GoWPF.dll
Additional information: DataColor

Is this a bug in GoXam? or how can I fix this?

Many thanks

Having the TestData.LinkedFrom property setter call RaisePropertyChanged(“DataColor”, …) seems like a bug – it ought to be saying that the “LinkedFrom” property changed value. Then undo/redo would be able to restore that state correctly.

Any bindings ought to be on the “LinkedFrom” property, not on “DataColor”. Use a converter to convert the boolean to a Brush. You don’t even need to define a new converter class for that, because GoXam provides one already: BooleanBrushConverter.