Switching between Layouts programmatically

I want to be able to switch all nodes between ForceDirected and Tree layouts. I have tried to use MultiLayout with a TreeLayout and ForceDirectedLayout and then used the LayoutId parameter in my Node object however, the diagram just stays in the first Layout. How can I tell the Diagram that the Layout has changed?

MultiLayout is used when you have different subsets of the nodes and links in your diagram be laid out in different ways. I suspect you don’t need to use go:Part.LayoutId at all.

If you want to lay out your whole diagram sometimes with ForceDirectedLayout and sometimes with TreeLayout, the easiest thing to do is to set the Diagram.Layout with one or the other. You can do this in code. The layouts could even be defined in XAML if you want, or they can be constructed programmatically. When the value of the Diagram.Layout property changes, a layout will be requested.

I haven’t tried this, but I believe you can even data-bind the Diagram.Layout to be the layout that you want. Whenever the binding changes value, it will perform the layout again.

I can’t find what to bind to in the XAML for Layout. I have tried this programmatically by creating a TreeLayout and ForceDirectedLayout instance and directly setting the Diagram.Layout. When I change the Layout type this causes this exception:

System.InvalidOperationException was unhandled by user code

Message=Element is already the child of another element.

StackTrace:

at MS.Internal.XcpImports.CheckHResult(UInt32 hr)

at MS.Internal.XcpImports.Collection_AddValue[t](PresentationFrameworkCollection1 collection, CValue value) <br /> at MS.Internal.XcpImports.Collection_AddDependencyObject[t](PresentationFrameworkCollection1 collection, DependencyObject value)

at System.Windows.PresentationFrameworkCollection1.AddDependencyObject(DependencyObject value) <br /> at System.Windows.Controls.UIElementCollection.AddInternal(UIElement value) <br /> at System.Windows.PresentationFrameworkCollection1.Add(T value)

at Northwoods.GoXam.Diagram.#0b(Object x)

at Northwoods.GoXam.Diagram.#Mc(DependencyObject d, DependencyPropertyChangedEventArgs e)

at System.Windows.DependencyObject.RaisePropertyChangeNotifications(DependencyProperty dp, Object oldValue, Object newValue)

at System.Windows.DependencyObject.UpdateEffectiveValue(DependencyProperty property, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, ValueOperation operation)

at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value, Boolean allowReadOnlySet)

at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)

at Northwoods.GoXam.Diagram.set_Layout(IDiagramLayout value)

Apparently there’s some “parenting” problem with layouts defined in XAML.

But something like this should work for you:

[code] private bool Flag { get; set; }

private void Button_Click(object sender, System.Windows.RoutedEventArgs e) {
  myDiagram.StartTransaction("changed layout");
  if (this.Flag) {
    myDiagram.Layout = new Northwoods.GoXam.Layout.CircularLayout();
  } else {
    myDiagram.Layout = new Northwoods.GoXam.Layout.ForceDirectedLayout();
  }
  this.Flag = !this.Flag;
  myDiagram.CommitTransaction("changed layout");
}[/code]

Defining a DiagramLayout instance as a resource in a ResourceDictionary and then assigning it to a Diagram.Layout works in WPF but not in Silverlight.

Since an instance of a layout cannot be shared by multiple Diagrams, this probably isn’t a serious drawback.

If you really want to define your layout(s) in XAML, you can still do so by making a copy of them before assigning Diagram.Layout:

private void Button_Click(object sender, System.Windows.RoutedEventArgs e) { myDiagram.StartTransaction("changed layout"); if (this.Flag) { myDiagram.Layout = new CircularLayout(Diagram.FindResource<CircularLayout>(myDiagram, "CLayout")); } else { myDiagram.Layout = new ForceDirectedLayout(Diagram.FindResource<ForceDirectedLayout>(myDiagram, "FDLayout")); } this.Flag = !this.Flag; myDiagram.CommitTransaction("changed layout"); }

Thanks for the assistance. I don’t care to define the Layouts in XAML. That was what you suggested. Your suggestion of setting the Layout via code does not work (It’s the first thing I tired) AS IS. I found this morning that you have to add NodalDiagram.Layout.DoLayout(NodalDiagram.Nodes, NodalDiagram.Links); after setting the Layout. I NOW can switch from Tree to ForceDirected layouts!!! Now for the next problem. When I try to switch from Tree to ForceDirected and back to Tree I get the following exception.

System.InvalidOperationException was unhandled by user code

Message=Element is already the child of another element.

StackTrace:

at MS.Internal.XcpImports.CheckHResult(UInt32 hr)

at MS.Internal.XcpImports.Collection_AddValue[t](PresentationFrameworkCollection1 collection, CValue value) <br /> at MS.Internal.XcpImports.Collection_AddDependencyObject[t](PresentationFrameworkCollection1 collection, DependencyObject value)

at System.Windows.PresentationFrameworkCollection1.AddDependencyObject(DependencyObject value) <br /> at System.Windows.Controls.UIElementCollection.AddInternal(UIElement value) <br /> at System.Windows.PresentationFrameworkCollection1.Add(T value)

at Northwoods.GoXam.Diagram.#0b(Object x)

at Northwoods.GoXam.Diagram.#Mc(DependencyObject d, DependencyPropertyChangedEventArgs e)

at System.Windows.DependencyObject.RaisePropertyChangeNotifications(DependencyProperty dp, Object oldValue, Object newValue)

at System.Windows.DependencyObject.UpdateEffectiveValue(DependencyProperty property, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, ValueOperation operation)

at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value, Boolean allowReadOnlySet)

at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)

at Northwoods.GoXam.Diagram.set_Layout(IDiagramLayout value)



I have tried to Invalidate the Layout before switching. I’m not sure how to avoid this exception.

That’s the same exception as before. Are you sure you are creating a new layout to assign to the Diagram.Layout each time?

Instead of calling DoLayout, it’s better to wrap a transaction around each modification. That will handle all of the side effects that one might expect. The LayoutManager will automatically call DoLayout only if needed.

Also, I tested both of the Button_Click event handlers that I showed above, and they both worked with lots of Button clicks, in both WPF and Silverlight.

Thanks, I finally understood the importance of creating a NEW Layout each time I switched. I was trying to re-use the layout, and that does not work. I am now able to switch between the two. Thanks for the help!