Drag drop not working with ContentTemplate

I am currently trying to display several “Logical Pages” with diagrams. And it seems to work when I try to statically use the goXAML Diagram control. However the drag/drop seems to stops working when I try to use the Diagram control in a TabControl.ContentTemplate. The visual trees look identical, the model properties all look set correctly, and yet the drop fails to drop.

And before anyone asks, yes I am setting Modifiable on the Model. :)

The Code in the view model looks like:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Xml.Linq;
using Northwoods.GoXam;
using Northwoods.GoXam.Model;
using Prism.Commands;
using Prism.Mvvm;

namespace GoDiagrams
{
    public class RelayLogicViewModel : BindableBase
    {
        private LogicPage _selectedPage;

        public GraphLinksModel<NodeData, string, string, WireData> Pallet { get; set; }

        public ObservableCollection<LogicPage> LogicPages { get; set; } = new ObservableCollection<LogicPage>()
        {
            new LogicPage()
        };

        public LogicPage SelectedPage
        {
            get { return _selectedPage; }
            set
            {
                _selectedPage = value;
                OnPropertyChanged();
            }
        }

        public RelayLogicViewModel()
        {
            // create pallet
            Pallet = new GraphLinksModel<NodeData, string, string, WireData>();
            Pallet.NodeCategoryPath = "GateType";
            Pallet.LinksSource = new List<WireData>();
            Pallet.NodesSource = new List<NodeData>()
            {
                new NodeData() { Figure=NodeFigure.Circle, GateType="Input", Key="Input", Category = "Input"},
                new NodeData() { Figure=NodeFigure.Rectangle, GateType="Output", Key="Output", Category = "Output"}
            };

            SelectedPage = LogicPages.FirstOrDefault();
        }
    }

    public class LogicPage : BindableBase
    {
        private bool _isInEditMode = false;
        private bool _hasChanges = false;
        private static int _count = 0;
        public GraphLinksModel<NodeData, string, string, WireData> ConfiguredLogic { get; set; }
        public DelegateCommand ToggleEditMode { get; set; }
        public string PageName { get; set; } = "Diagram" + _count++;

        public bool HasChanges
        {
            get { return _hasChanges; }
            set
            {
                _hasChanges = value;
                OnPropertyChanged();
            }
        }

        public bool IsInEditMode
        {
            get { return _isInEditMode; }
            set
            {
                _isInEditMode = value;
                OnPropertyChanged();
            }
        }

        public LogicPage()
        {
            ConfiguredLogic = new GraphLinksModel<NodeData, string, string, WireData>();
            ConfiguredLogic.Modifiable = true;

            ToggleEditMode = new DelegateCommand(() =>
            {
                IsInEditMode = !IsInEditMode;
                HasChanges = IsInEditMode;
            });
        }
    }

    public class NodeData : GraphLinksModelNodeData<string>
    {
        private bool _value;
        public string GateType { get; set; }
        public NodeFigure Figure { get; set; }

        public bool Value
        {
            get { return _value; }
            set
            {
                bool old = _value;
                if (old != value)
                {
                    _value = value;
                    RaisePropertyChanged("Value", old, value);
                }
            }
        }

        public override XElement MakeXElement(XName n)
        {
            XElement e = base.MakeXElement(n);
            e.Add(XHelper.Attribute("GateType", this.GateType, ""));
            e.Add(XHelper.AttributeEnum<NodeFigure>("Figure", this.Figure, NodeFigure.Rectangle));
            e.Add(XHelper.Attribute("Value", this.Value, false));
            return e;
        }

        public override void LoadFromXElement(XElement e)
        {
            base.LoadFromXElement(e);
            this.GateType = XHelper.Read("GateType", e, "");
            this.Figure = XHelper.ReadEnum<NodeFigure>("Figure", e, NodeFigure.Rectangle);
            this.Value = XHelper.Read("Value", e, false);
        }
    }

    public class WireData : GraphLinksModelLinkData<string, string>
    {
        private bool _value;

        public bool Value
        {
            get { return _value; }
            set
            {
                bool old = _value;
                if (old != value)
                {
                    _value = value;
                    RaisePropertyChanged("Value", old, value);
                }
            }
        }
    }
}

And the XAML that Works looks like:
Notice that the TabControl has no bindings, and the TabItem DataContext is bound to the SelectedPage.

<Window x:Class="GoDiagrams.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:go="http://schemas.nwoods.com/GoXam"
        xmlns:local="clr-namespace:GoDiagrams"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow"
        Width="800"
        Height="450"
        mc:Ignorable="d">
    <Window.Resources>
        <SolidColorBrush x:Key="NodeLinks" Color="Gray" />
        <SolidColorBrush x:Key="NodeFill" Color="LightGray" />
        <SolidColorBrush x:Key="NodeForgroundText" Color="Black" />
        <SolidColorBrush x:Key="NodeConnectors" Color="Black" />

        <SolidColorBrush x:Key="NodeSelected" Color="Blue" />
        <SolidColorBrush x:Key="NodeUnselected" Color="Black" />

        <go:BooleanBrushConverter x:Key="NodeSelection" FalseBrush="{StaticResource NodeUnselected}" TrueBrush="{StaticResource NodeSelected}" />

        <DataTemplate x:Key="NodeLink">
        <go:LinkPanel>
            <go:Link.Route>
                <go:Route Curve="JumpOver" RelinkableFrom="True" RelinkableTo="True" Routing="AvoidsNodes" />
            </go:Link.Route>
            <go:LinkShape Stroke="{StaticResource NodeLinks}" StrokeThickness="2">
                <go:LinkShape.Style>
                    <Style TargetType="go:LinkShape">
                        <Setter Property="StrokeThickness" Value="1" />
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="true">
                                <Setter Property="StrokeThickness" Value="3" />
                            </Trigger>
                            <DataTrigger Binding="{Binding Link.IsSelected}" Value="True">
                                <Setter Property="StrokeThickness" Value="2" />
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </go:LinkShape.Style>
            </go:LinkShape>
            <Path go:LinkPanel.ToArrow="Standard" Fill="{StaticResource NodeLinks}" />
        </go:LinkPanel>
    </DataTemplate>
        <go:DataTemplateDictionary x:Key="NodeTemplateDictionary">
        <DataTemplate x:Key="Input">
            <go:SpotPanel go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">
                <ToolTipService.ToolTip>
                    <TextBlock Text="{Binding Path=Data.Key}" />
                </ToolTipService.ToolTip>
                <go:NodePanel>
                    <Grid>
                        <go:NodeShape Width="50"
                                      Height="50"
                                      go:NodePanel.Figure="{Binding Path=Data.Figure}"
                                      Fill="{StaticResource NodeFill}"
                                      Stroke="{Binding Path=Node.IsSelected, Converter={StaticResource NodeSelection}}"
                                      StrokeThickness="1" />
                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{StaticResource NodeForgroundText}">Input</TextBlock>
                    </Grid>
                </go:NodePanel>
                <Rectangle Width="8"
                           Height="8"
                           go:Node.FromSpot="MiddleRight"
                           go:Node.LinkableFrom="True"
                           go:Node.LinkableMaximum="1"
                           go:Node.PortId=""
                           go:SpotPanel.Spot="MiddleRight"
                           Cursor="Hand"
                           Fill="{StaticResource NodeConnectors}" />
            </go:SpotPanel>
        </DataTemplate>
        <DataTemplate x:Key="Output">
            <go:SpotPanel go:Node.Location="{Binding Path=Data.Location, Mode=TwoWay}">
                <ToolTipService.ToolTip>
                    <TextBlock Text="{Binding Path=Data.Key}" />
                </ToolTipService.ToolTip>
                <go:NodePanel>
                    <Grid>
                        <go:NodeShape Width="50"
                                      Height="50"
                                      go:NodePanel.Figure="{Binding Path=Data.Figure}"
                                      Fill="{StaticResource NodeFill}"
                                      Stroke="{Binding Path=Node.IsSelected, Converter={StaticResource NodeSelection}}"
                                      StrokeThickness="1" />
                        <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="{StaticResource NodeForgroundText}">Output</TextBlock>
                    </Grid>
                </go:NodePanel>
                <Rectangle Width="8"
                           Height="8"
                           go:Node.FromSpot="MiddleLeft"
                           go:Node.LinkableMaximum="1"
                           go:Node.LinkableTo="True"
                           go:Node.PortId=""
                           go:SpotPanel.Spot="MiddleLeft"
                           Cursor="Hand"
                           Fill="{StaticResource NodeConnectors}" />
            </go:SpotPanel>
        </DataTemplate>
    </go:DataTemplateDictionary>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />

    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="250" />
        </Grid.ColumnDefinitions>
        <TabControl Grid.Column="0">
            <TabItem DataContext="{Binding SelectedPage}" Header="{Binding PageName}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto" />
                        <RowDefinition />
                    </Grid.RowDefinitions>
                    <Border Grid.Row="0" Margin="1" BorderBrush="{DynamicResource EditControlBorder}" BorderThickness="0,0,0,1">
                        <Button Margin="0,0,0,2"
                                HorizontalAlignment="Right"
                                Background="Blue"
                                Command="{Binding ToggleEditMode}"
                                Content="Edit"
                                Foreground="White">
                            <Button.Style>
                                <Style TargetType="Button">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding IsInEditMode}" Value="True">
                                            <Setter Property="Background" Value="DarkGray" />
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Button.Style>
                        </Button>
                    </Border>
                    <go:Diagram Name="TargetDiagram"
                                Grid.Row="1"
                                HorizontalContentAlignment="Stretch"
                                VerticalContentAlignment="Stretch"
                                AllowDrop="True"
                                GridSnapCellSize="10 10"
                                GridSnapEnabled="True"
                                LinkTemplate="{DynamicResource NodeLink}"
                                Model="{Binding ConfiguredLogic}"
                                NodeTemplateDictionary="{DynamicResource NodeTemplateDictionary}" />
                </Grid>
            </TabItem>
        </TabControl>
        <Grid Grid.Column="1">
            <Border BorderBrush="Black" BorderThickness="1">
                <go:Palette Name="SourcePalette"
                            AllowDrop="True"
                            FontFamily="Verdana"
                            FontSize="11"
                            FontWeight="Normal"
                            InitialScale="1"
                            Model="{Binding Pallet}"
                            NodeTemplateDictionary="{DynamicResource NodeTemplateDictionary}" />
            </Border>
        </Grid>
    </Grid>
</Window>

While the code that isn’t working looks is pretty much just replace the TabControl above with:
Here the Tab Control ItemsSource is bound to the collection of pages, the Selected Item is the current tab, and the TabControl.ContentTemplate is set to the type of LogicPage data type.

                        <TabControl Grid.Column="0" ItemsSource="{Binding LogicPages}" SelectedItem="{Binding SelectedPage}">
            <TabControl.ItemContainerStyle>
                <Style BasedOn="{StaticResource {x:Type TabItem}}" TargetType="TabItem">
                    <Setter Property="HeaderTemplate">
                        <Setter.Value>
                            <DataTemplate>
                                <DockPanel HorizontalAlignment="Center" VerticalAlignment="Center">
                                    <TextBlock DockPanel.Dock="Left" Text="{Binding PageName}" />
                                    <TextBlock DockPanel.Dock="Left" Text="*" Visibility="{Binding HasChanges, Converter={StaticResource BooleanToVisibilityConverter}}" />
                                </DockPanel>
                            </DataTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </TabControl.ItemContainerStyle>
            <TabControl.ContentTemplate>
                <DataTemplate DataType="{x:Type local:LogicPage}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition />
                        </Grid.RowDefinitions>
                        <Border Grid.Row="0" Margin="1" BorderBrush="{DynamicResource EditControlBorder}" BorderThickness="0,0,0,1">
                            <Button Margin="0,0,0,2"
                                    HorizontalAlignment="Right"
                                    Background="Blue"
                                    Command="{Binding ToggleEditMode}"
                                    Content="Edit"
                                    Foreground="White">
                                <Button.Style>
                                    <Style TargetType="Button">
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding IsInEditMode}" Value="True">
                                                <Setter Property="Background" Value="DarkGray" />
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </Button.Style>
                            </Button>
                        </Border>
                        <go:Diagram Name="TargetDiagram"
                                    Grid.Row="1"
                                    HorizontalContentAlignment="Stretch"
                                    VerticalContentAlignment="Stretch"
                                    AllowDrop="True"
                                    GridSnapCellSize="10 10"
                                    GridSnapEnabled="True"
                                    LinkTemplate="{DynamicResource NodeLink}"
                                    Model="{Binding ConfiguredLogic}"
                                    NodeTemplateDictionary="{DynamicResource NodeTemplateDictionary}" />
                    </Grid>
                </DataTemplate>
            </TabControl.ContentTemplate>
        </TabControl>

Any ideas as to why dragging from my pallet stops working when I change it to a ContentTemplate? And any way to fix it so that it does work? I have a bunch of dynamic pages I need to load.

The UI looks like it is binding correctly and I see the diagram, just the can drop is coming back as false and I don’t understand why.

Thanks
James McDuffie
Bently Nevada, BHGE a GE Company
Senior Staff Software Architect
775-215-1076

It’s very good that you thought about making the model modifiable. Also, another necessity: you remembered to set Diagram.AllowDrop to true.

I don’t know what the problem might be. When dealing with tab controls, you might want to set Diagram.UnloadingClearsPartManager to false. In this case I would think it would treat it as if it were false, but maybe there’s something preventing that when using a ContentTemplate.

Tried it, Diagram.UnloadingClearsPartManager=“False”, doesn’t seam to effect anything. Thanks for the suggestion though! Unfortunately still unable to drag and drop onto the diagram. Any chance someone from your team could try the above code. I just duplicated my problem in a clean WPF application, which is the code that I shared. So it should be as easy as a new application, adding Prism WPF through NUGET, and copying the two files into the solution. Understanding what’s happening in the OnDrop handler is something that I’m not able to debug on my end.

Thanks!

I can reproduce the problem, which seems to be because DraggingTool.MayAcceptData is returning false. Why that is, I still don’t know.

What’s also odd is when I define a CustomDraggingTool class that overrides MayAcceptData, it never gets called because somehow the value of Diagram.DraggingTool reverts to the standard DraggingTool rather than keeping the CustomDraggingTool instance that I set in XAML. Don’t know why that is either. I’ll investigate more when I get more time.

Awesome! Thanks for the update, looking forward to the results!

The reason that DraggingTool.MayAcceptData returns false is because the Model.DataFormat doesn’t match what is being dragged. Basically, you want to make sure the Diagram.Models are of the same type. I think the data binding isn’t getting the right values when using your ContentTemplate.

It’s quite plausible that I am not reproducing your situation because I had to make changes to your code to get it to compile and to work at all. So I might be doing something different, and what I found might be completely unrelated to your problem.

Hmmmmm… Wasn’t sure what to set the data format too… but I tried setting the data format on the pallet, and on the model both to just a string “format”, and that didn’t seem to work. Do I need to set it on the node data as well? I didn’t see a format on the GraphLinksModelNodeData for setting the data format.

Yes, just the same strings, for Model.DataFormat only. The value defaults to the name of the model class. In my failure case the Palette.Model was a GraphLinksModel<NodeData, string, string, WireData> (sorry, I don’t remember exactly what the string was) and the target Diagram.Model was UniversalGraphLinksModel, which are different from each other.

Again, I suspect I might be initializing it differently from you, since you didn’t include that code.

Can I just email you my test project? The only missing code was DataContext = new RelayLogicViewModel(); in the MainWindow.xaml.cs file.

That was exactly the line that I had added. That’s good.

So we still haven’t figured out why the Palette.Model is of a different type than the Diagram.Model.

I had overridden the OnDrop handler for the diagram, and it seems that the pallet is setting the drag drop type as my new LogicModel, but the value is a DataCollection?

Not sure how I would convert that back to a LogicModel.

LogicModel at this point is just:
public class LogicModel : GraphLinksModel<NodeData, string, string, WireData> And it is set as the model for both my diagrams and my palette.

Here’s the implementation of DraggingTool.DoDragDrop, which is what starts the WPF drag-and-drop operation:

    protected virtual DragDropEffects DoDragDrop(IDataCollection data, DragDropEffects eff) {
      Diagram diagram = this.Diagram;
      if (diagram == null) return DragDropEffects.None;
      IDiagramModel model = diagram.Model;
      if (model == null) return DragDropEffects.None;
      DataObject dataobj = new DataObject(model.DataFormat, data);
      return DragDrop.DoDragDrop(diagram.Panel, dataobj, eff);
    }

And when handling the drag:

    public virtual bool MayAcceptData(IDataObject dataobj) {
      Diagram diagram = this.Diagram;
      if (diagram == null) return false;
      IDiagramModel model = diagram.Model;
      if (model == null) return false;
      return dataobj.GetDataPresent(model.DataFormat) || dataobj.GetDataPresent(model.GetNodeType());
    }

    public virtual IDataCollection AcceptData(IDataObject dataobj) {
      Diagram diagram = this.Diagram;
      if (diagram == null) return null;
      IDiagramModel model = diagram.Model;
      if (model == null) return null;
      IDataCollection data = dataobj.GetData(model.DataFormat) as IDataCollection;
      if (data == null) {  // also handle single node data, instead of an IDataCollection
        Object nodedata = dataobj.GetData(model.GetNodeType());
        if (nodedata != null) {
          data = model.CreateDataCollection();
          data.AddNode(nodedata);
        }
      }
      return data;
    }

I don’t know if this helps or not.

Yeah that helped me handle drag drop on the working model. But I still cant get any of that to work when I’m using the DataTemplate for the Tab Control. For our first release of this I only need a single tab, but I need to figure out either a solution or a work around to the TabControl data template issue sooner rather than later.

I was noticing that Even if the Databound tab control was handling the drag and drop effects, and even if I was adding the nodes to the model in the background, the nodes still weren’t getting displayed in the Templated Go Diagram control. It’s like the data binding isn’t updating.

Which Binding isn’t updating?

It looks like the binding to the Diagram.Model isn’t being updated. I drag and drop items and I accept the drop in my behavior class and add it to the current model .NodeSource.Add(). And it doesn’t update in the UI. I thought it might be not calling OnPropertyChanged, but I decompiled it and saw that Model is a DP… so it should be updating correctly.

You aren’t replacing the Diagram.Model with a new DiagramModel, are you? It sounds as if you are just adding an item (or more) to the IDiagramModel.NodesSource collection.

Is that collection in your app an ObservableCollection? It should be.

Do you execute such changes within a transaction? You really should do so.