How to change template for node

Hi,
now the users want to change the visual representation of a node in runtime.
Is it possible to change the template of a node after it is created?
“Undo” should work also.

Do you know about template dictionaries? Read the “Template Dictionaries” section of the GoXamIntro PDF document, http://goxam.com/2.2/GoXamIntro.pdf.

So you could implement a command that modifies the node data’s Category property, of course within a transaction.

Hi Walter,
the exchange is working now, but the Undo doesn’t work. The UndoManager can make a undo but the visual doesn’t change if doing the UndoCommand.

private void ChangeElementTypeExecute(ElementType arg)
{
    DiagramInView.StartTransaction("ChangeElementType");

    var selectedNodes = DiagramInView.SelectedParts.Where(p => p is Node).Cast<Node>().ToList();

    foreach (var node in selectedNodes)
    {
        var element = node.Data as Element;
        if (element != null)
        {
            element.Type = arg;
            element.Category = arg + "Template";
        }
    }

    DiagramInView.CommitTransaction("ChangeElementType");
}

Are you saying that upon an undo the value of Element.Category changes (correctly) to the old value, but the node is not re-created using the old DataTemplate?

I assume a redo has the same kind of results. We’ll investigate.

Yes,
I found out that the ModelChangedEventArgs have no “old”-Value for the category change - if this is helping you.

The “old” value would be the default value, which is the empty string. Perhaps you are misreading the debug output to interpret the “old” or “previous” value to not be present when it actually is present but is Empty.

I just reply that cause I think the information might help you!

Maybe I missunderstood you. If you write “We’ll investigate” I interpred that to “I can reproduce something and give it a test”.

By the way, it seems that the redo is working but the undo not.

I made a little video.

https://dl.dropboxusercontent.com/u/54189035/2016-04-18_1451.swf

No, I meant that as soon as I got a chance I would try to reproduce the problem. I still have not yet done so. But thank you for the video, which confirms the kind of situation that you have.

I am unable to reproduce the problem. Here is the “minimal” app in which I have two Node DataTemplates in a DataTemplateDictionary. The button toggles the data.Category property value between “” and “Other”.

<Window x:Class="WpfApplication1.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:local="clr-namespace:WpfApplication1">

  <FrameworkElement.Resources>
    <go:DataTemplateDictionary x:Key="NodeTemplateDictionary">
      <DataTemplate x:Key="">
        <go:NodePanel Sizing="Auto"
                      go:Part.SelectionAdorned="True" go:Part.SelectionElementName="Shape"
                      go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">
          <Ellipse x:Name="Shape" Fill="{Binding Path=Data.Color}" Stroke="Black" StrokeThickness="1" />
          <TextBlock Text="{Binding Path=Data.Key}" Margin="10" />
        </go:NodePanel>
      </DataTemplate>
      <DataTemplate x:Key="Other">
        <go:NodePanel Sizing="Auto"
                      go:Part.SelectionAdorned="True" go:Part.SelectionElementName="Shape"
                      go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">
          <Rectangle x:Name="Shape" Fill="White" Stroke="Black" StrokeThickness="1" />
          <TextBlock Text="{Binding Path=Data.Key}" Margin="10" />
        </go:NodePanel>
      </DataTemplate>
    </go:DataTemplateDictionary>
  </FrameworkElement.Resources>

  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <StackPanel Grid.Row="0" Orientation="Horizontal">
      <Button Content="Toggle Category" Click="Button_Click" />
    </StackPanel>
    <go:Diagram Grid.Row="1" x:Name="myDiagram" Padding="10"
                BorderBrush="Green" BorderThickness="1"
                HorizontalContentAlignment="Stretch"
                VerticalContentAlignment="Stretch"
                NodeTemplateDictionary="{StaticResource NodeTemplateDictionary}">
      <go:Diagram.Layout>
        <go:TreeLayout Angle="90" Conditions="InitialOnly" />
      </go:Diagram.Layout>
    </go:Diagram>
  </Grid>
</Window>
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using Northwoods.GoXam;
using Northwoods.GoXam.Model;

namespace WpfApplication1 {
  public partial class MainWindow : Window {
    public MainWindow() {
      InitializeComponent();

      var model = new GraphLinksModel<MyNodeData, String, String, MyLinkData>();

      model.NodesSource = new ObservableCollection<MyNodeData>() {
          new MyNodeData() { Key="Alpha", Color="LightBlue" },
          new MyNodeData() { Key="Beta", Color="Orange" },
          new MyNodeData() { Key="Gamma", Color="LightGreen" },
          new MyNodeData() { Key="Delta", Color="Pink" }
        };

      model.LinksSource = new ObservableCollection<MyLinkData>() {
          new MyLinkData() { From="Alpha", To="Beta" },
          new MyLinkData() { From="Alpha", To="Gamma" },
          new MyLinkData() { From="Gamma", To="Delta" }
      };

      model.Modifiable = true;
      model.HasUndoManager = true;

      myDiagram.Model = model;
    }

    private void Button_Click(object sender, RoutedEventArgs e) {
      myDiagram.StartTransaction("Change Category");
      foreach (var p in myDiagram.SelectedParts.OfType<Node>().ToArray()) {
        var data = (MyNodeData)p.Data;
        data.Category = (data.Category == "") ? "Other" : "";
      }
      myDiagram.CommitTransaction("Change Category");
    }
  }


  // Define custom node data; the node key is of type String.
  // Add a property named Color that might change.
  [Serializable]  // serializable in WPF to support the clipboard
  public class MyNodeData : GraphLinksModelNodeData<String> {
    public String Color
    {
      get { return _Color; }
      set {
        if (_Color != value) {
          String old = _Color;
          _Color = value;
          RaisePropertyChanged("Color", old, value);
        }
      }
    }
    private String _Color = "White";
  }

  // Define custom link data; the node key is of type String,
  // the port key should be of type String but is unused in this app.
  [Serializable]  // serializable in WPF to support the clipboard
  public class MyLinkData : GraphLinksModelLinkData<String, String> {
    // nothing to add
  }
}

I have a CustomPartManager, this is making the problem.
If the Undo occurs the value of Type is still the old one - that is making the problem.
Do you have an idea to change this.

public class CustomPartManager : PartManager
{
    protected override string FindCategoryForNode(object nodedata, IDiagramModel model, bool isgroup, bool islinklabel)
    {
        Element element = nodedata as Element;

        if (element != null)
        {
            return element.Type + "Template";
        }   
     
        return "SimpleStringTemplate";
    }
}

Could you set GraphLinksModel.NodeCategoryPath to “Type”, thereby ignoring the GraphLinksModelNodeData.Category property, and rename your templates not to have that “…Template” suffix?

I tried to do this, but after that I can’t drag’n’drop Nodes from my ListView anymore. I found that the FindCategoryForNode wasn’t called like before.
I also can’t read my old saved diagrams, there is a can’t convert string to ElementType error.

I reverted anything and tried to ignore the Category change in the CustomUndoManager SkipEvent, but this also doesn’t help. The problem is that when the FindCategoryForNode-Method is called the value for Type is still the “old” value. So either I need some sorting of the changedevents or a second call of FindCategoryForNode if Type is changed.

I will give it a try to change the program to complete ignore the Type property and use Category instead. I hope the drag’n’drop for the ListView is still working then.

I made it!
Undo is working. I just use Category for the Templates and all functions are working as expected.

Thank you for your great support.

Very good. So you do not need a custom PartManager after all?

Yes, I don’t need the PartManager anymore.
The only little problem is that I don’t want to have a DataTemplate for a node with Category=="".
But I think I can handle that while loading the saved xml file.