Model.Changed not fired after drag drop

Hi everyone,

I am facing a serious problem, hoping anyone could help me:
If I drag/drop an external object (e.g., a column from a datagrid) to the diagram, the model.changed event will not be fired anymore, when I move a node in the diagram, for example. But I need to handle this event, because some animations on the nodes and links must be updated.
By the way, I am using the GoWpf 1.1.8.4 and vs2010.
Thank you very much in advance!
haibing

The easiest implementation strategy is to make sure that the drag-and-drop data is an instance of your model’s node data type.

For example, consider the case where my Diagram.Model was created as an instance of GraphLinksModel<TestData, Object, String, TestLink>. TestData and TestLink are data classes that I have defined. And make sure you have set DiagramModel.Modifiable to true.

Then you could build a TreeView where each TreeViewItem had an instance of TestData associated with it. For this example, I have built the tree in XAML and the TreeViewItem.Header is a TestData.

<TreeView x:Name="myTreeView" SelectedValuePath="Header" MouseMove="myTreeView_MouseMove"> <TreeViewItem Header="Root"> <TreeViewItem> <TreeViewItem.Header> <local:TestData Name="First Data" /> </TreeViewItem.Header> </TreeViewItem> <TreeViewItem> <TreeViewItem.Header> <local:TestData Name="Second Data" /> </TreeViewItem.Header> </TreeViewItem> <TreeViewItem> <TreeViewItem.Header> <local:TestData Name="Third Data" /> </TreeViewItem.Header> </TreeViewItem> </TreeViewItem> </TreeView>
(Assume TestData.ToString() returns its Name property, or something like that.)

The drag-and-drop could be started via:

private void myTreeView_MouseMove(object sender, MouseEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed && myTreeView.SelectedItem != null) { DragDrop.DoDragDrop(myTreeView, myTreeView.SelectedValue, DragDropEffects.Copy); } }
Note that this code depends on TreeView.SelectedValuePath=“Header”, as an easy way of getting the TreeViewItem’s header data.

If you also make sure that Diagram.AllowDrop=“True”, I think you’ll find that everything just works.

Hi, I’m experiencing the same problem as discussed above. I have a listbox populated with my node type that I am dragging onto my diagram. After I drop the object, the Model.Changed event is only fired when new nodes are added. These nodes do not show in the diagram either but they are definitely being added to the Model.NodeSource collection.



I have set the Diagram.AllowDrop=“True”



I have the default layout conditions set.



Thanks,



Neil.

Does your Model.NodesSource collection implement INotifyCollectionChanged?

Usually one uses instances of ObservableCollection, but the model is just looking for INotifyCollectionChanged.

Yes, I’m using an ObservableCollection.

I can’t reproduce any problem. Could you describe your situation in more detail?

Are you saying that no Node ever appears in the target Diagram after dragging a node data from a ListBox (or TreeView in the above example)?

Or are you saying that a Node does appear upon the drag-and-drop from the ListBox/TreeView, but then future attempts to create a new Node in the Diagram fail?

Or is there some issue with …Model.Changed events not firing?

All of these things work in my test app that uses a TreeView as the drag source.

Hi Walter,



I’ll try and give more details as to the problem I’m experiencing.



I have a listbox containing a load of instances of my Node object (source).



I have another listbox that is bound to myDiagram.Model.NodeSource.



I am also watching the Model.Changed event.



When the app first loads, I get “Started Transaction: Layout” and “Commited Transaction: Layout”.



I then drag an item from my source listbox onto my diagram and get the “Started Transaction: Drop” model change event, followed by “RolledBackTransaction”.



The drop event handler is then fired and the node object is added to the NodeSource collection. This gives the “AddedNode” model change event.



This Node appears in the second listbox (that is bound to the NodeSource collection) but not on the diagram.



Subsequent drag-drop operations only fire the “AddedNode” model change event. They continue to be added to the NodeSource listbox and not the diagram.

Here’s my XAML for the diagram if it helps…



<br /> <go:Diagram Name="myDiagram" NodeTemplateDictionary="{StaticResource NodeTemplateDictionary}" LinkTemplate="{StaticResource linkTemplate}" <br /> HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" GroupTemplate="{StaticResource myGroupTemplate}" <br /> InitialPanelSpot="Center" IsReadOnly="False" AllowDrop="True" Drop="myDiagram_Drop" Grid.Column="1"> <br /> <go:Diagram.LinkingTool> <br /> <go:LinkingTool PortGravity="20" /> <br /> </go:Diagram.LinkingTool> <br /> <go:Diagram.Layout> <br /> <go:ForceDirectedLayout/> <br /> </go:Diagram.Layout> <br /> </go:Diagram> <br />

The “RolledBackTransaction” is a clue that something failed during the drag-and-drop, and partly explains why you don’t see a Node.

Just to be clear: your ListBox items are instances of your node data class, not instances of Node, right?

Does your node data class implement ICloneable or is it Serializable or have you overridden …Model.CopyNode1? If the data can’t be copied, of course the drop will fail. Check your trace listener output for error messages.

Is …Model.Modifiable true?

Does the mouse ever show a “not-allowed-here” cursor over your target Diagram during the drag-and-drop? It ought to show a “Copy” cursor, corresponding to a DragDropEffects.Copy effect.

What does your myDiagram_Drop method do? Or is it just for debugging?

Ok, my listbox items are my own class called AmpNodeObject. This inherits from GraphLinksModelNodeData so should have all the necessary methods/properties.



Everything else with the drag-drop works. Here’s my drop method.



private void myDiagram_Drop(object sender, DragEventArgs e) <br /> { <br /> if (e.Data.GetDataPresent("myFormat")) <br /> { <br /> AmpNodeObject node = e.Data.GetData("myFormat") as AmpNodeObject; <br /> node = node.Clone() as AmpNodeObject; <br /> node.Key = Guid.NewGuid().ToString(); <br /> node.Location = e.GetPosition(this); <br /> <br /> myDiagram.Model.AddNode(node); <br /> } <br /> }



I will check my trace listener for any clues…

There are no errors shown in the debug output window.

You don’t need any Drop event handler – Diagram provides one that handles the model’s node data type or that handles the model’s DataFormat which is an IDataCollection.

There are at least two problems with the code that you have:

  • You’ll get two copies of the data: one from the default mechanism and one from yours; you probably are not running into this problem all the time because you have not turned on Diagram.AllowDragOut, which you presumably don’t need.
  • You don’t add the node data to the model within a transaction.

I don’t know why you are getting the rollback transaction behavior.

I suspect that you’re not getting a Node because the standard drag-and-drop behavior, which is still executing in your app, temporarily adds the node data to the model upon a drag-enter. This is what the user drags around.

Then when a drop occurs, the temporary node is discarded and the real one is added at the drop point. If your drop code executes first, perhaps the standard behavior is removing the node that it thinks is temporary but that you intend to be permanent.

Anyway, try removing the myDiagram_Drop event handler.

Good morning Walter,



I have removed myDiagram_Drop event handler and I still get the rollback transaction error. This time, no nodes are added to the NodeSource collection.



Here is my method that initiates the drag event.



private void PaletteList_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) <br /> { <br /> ListBox box = (ListBox)sender; <br /> <br /> if (box.SelectedItem != null) <br /> { <br /> AmpNodeObject node = box.SelectedItem as AmpNodeObject; <br /> <br /> node = node.Clone() as AmpNodeObject; <br /> node.Key = Guid.NewGuid().ToString(); <br /> <br /> DragDrop.DoDragDrop(box, node, DragDropEffects.Move); <br /> }



I’m not sure if this is relevant or not, but my palette (i.e. the listbox) and therefore my node data objects inherit from AmpNodeObject.



Also, when setting up the drag-drop, I can’t use DragDropEffects.Copy for some reason. I am not handling any drag-over event for the diagram control, should I be doing this or does the diagram handle this by default like the drop event?



Walter, I have just read an earlier post and yes, I get the “not allowed” icon when I drag over using DragDropEffects.Copy.

I just created a minimal app using a ListBox and a Diagram, and I don’t see any serious problems. Here’s the XAML:

<Window x:Class="WpfApplication1.Window1" 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" Title="Window1" Height="300" Width="500"> <Grid> <ListBox HorizontalAlignment="Left" Name="listBox1" Width="120" DisplayMemberPath="Text" MouseMove="listBox1_MouseMove" /> <go:Diagram Margin="126,0,0,0" Name="myDiagram" Padding="10" AllowDrop="True" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"> <go:Diagram.NodeTemplate> <DataTemplate> <Border BorderBrush="Black" BorderThickness="1" CornerRadius="5" Padding="2" go:Part.SelectionAdorned="True"> <TextBlock Text="{Binding Path=Data.Text}" /> </Border> </DataTemplate> </go:Diagram.NodeTemplate> </go:Diagram> </Grid> </Window>
And the code-behind:

[code]using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using Northwoods.GoXam.Model;

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

  listBox1.ItemsSource = new List<MyData>() {
    new MyData() { Text="first item" },
    new MyData() { Text="second item" },
  };

  var model = new GraphLinksModel<MyData, int, String, MyLink>();
  model.NodesSource = new ObservableCollection<MyData>() {
    new MyData() { Text="existing" }
  };
  model.LinksSource = new ObservableCollection<MyLink>();
  model.Modifiable = true;
  myDiagram.Model = model;
}

private void listBox1_MouseMove(object sender, MouseEventArgs e) {
  if (e.LeftButton == MouseButtonState.Pressed && listBox1.SelectedItem != null) {
    DragDrop.DoDragDrop(listBox1, listBox1.SelectedValue, DragDropEffects.Copy);
  }
}

}

public class MyData : GraphLinksModelNodeData { }

public class MyLink : GraphLinksModelLinkData<int, String> { }
}[/code]
All of the rest of the application is what’s created by default by Visual Studio. (Well, I added a reference to Northwoods.GoWPF.dll, of course.)

Cheers for this Walter. I have also created a minimal app based upon your treeview example above but using a listbox instead. Incidentally, your treeview example works fine but my listbox version does not. I think the differences are that you seem to use the SelectedValue of the sender whereas I am using the SelectedItem. In my simple example, I won’t allow me to cast the selected item to my NodeDataObject. I think it’s someway I’m defining the listbox in XAML. I will look through your example and see if I can pin point it.



Neil.

Ok Walter, I think I’ve found the problem and it’s something I alluded to earlier. I changed your example to use my AmpNodeObject class instead of your MyData class. Once I’d changed the type of the node key (I was using a string, you were using an int) this still worked fine. I then changed your source list to use the same list that I am using and this failed.



Because my list contains objects that inherit from AmpNodeObject and not actual instances of AmpNodeObject itself, I’m guessing your drag-drop code doesn’t accept my objects as being valid for the underlying NodeSource?



I presume I need to override all your drag-drop code to accept the input, or is there an easier way? I’m using a NodeTemplateDictionary and will need to show different objects on the diagram. I suppose I could lump all of these objects into a single object and use the Category property to differentiate them, this doesn’t seem very clean to me though.

Ah – of course. I’m sorry that that possibility didn’t occur to me earlier.

Well, if you can’t pass the expected node data type, you can just go back to the normal mechanism, using a DataObject and an IDataCollection.

private void listBox1_MouseMove(object sender, MouseEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed && listBox1.SelectedItem != null) { var coll = myDiagram.Model.CreateDataCollection(); coll.AddNode(listBox1.SelectedValue); var dataobj = new DataObject(myDiagram.Model.DataFormat, coll); DragDrop.DoDragDrop(listBox1, dataobj, DragDropEffects.Copy); } }
I suppose we should investigate generalizing the default mechanism to handle instances of subtypes of the node data type. Hmmm, after a quick look, that doesn’t seem practical to do.

That’s all working a treat now, thanks for your time on this one. I’ve just filled out the forms to go ahead with a purchase of the software too. I now have a new problem though but I’ll have a play and see if I can fix it, if not I might be giving you a heads-up in a new thread.