Search code examples
wpfxamleventsdrag-and-dropevent-handling

WPF Drop event is firing twice


2nd Edit

so removing the Panel.ZIndex properties from the control template resolved this issue for me, giving me 1 drop event. including them triggers two drop events. can any one answer me why though? id love to know why z index ?

Original Question :

I am trying to add a custom object (state) to a canvas called MainCanvas on the MainWindow. I am trying to drag a state object from a wrap panel and drop it onto the canvas.

the code works but there are two items being added. I know there are two because I can move the two item around the canvas.

I have searched existing answers and added e.Handled=true, but still adds two items I tried using Drop event and PreviewDrop Event on MainCanvas, no difference.

Cam someone help as to how I can make it so that only 1 item gets added?

the maincanvas exists at design time a new state is created at runtime at the drop event.

Here is the OnMouseMove handler for the state

protected override void OnMouseMove(MouseEventArgs e)
{
    base.OnMouseMove(e);
    if (e.LeftButton == MouseButtonState.Pressed)
    {
        var parent = VisualTreeHelper.GetParent(this);
        if (parent as WrapPanel != null)
        {
            DataObject dragData = new DataObject();
            dragData.SetData(DataFormats.StringFormat, this.ItemType);
            DragDrop.DoDragDrop(this, dragData, DragDropEffects.Copy);
            

        }
    }
    e.Handled = true;
}

Within the Code Behind I have set the following events for the canvas:

private void MainCanvas_DragEnter(object sender, DragEventArgs e)
{
    if (e.Data.GetDataPresent(DataFormats.Text))

        e.Effects = DragDropEffects.Copy;
    else
        e.Effects = DragDropEffects.None;
    e.Handled = true;
}

private void MainCanvas_Drop(object sender, DragEventArgs e)
{
    var itemType = e.Data.GetData(typeof(string));

    switch (itemType)
    {
        case "state":
            var pos = e.GetPosition(this.MainCanvas);
            State item = new State();
            item.Template = (ControlTemplate)FindResource("StateViewModelControlTemplate");
            this.MainCanvas.Children.Add(item);
            Canvas.SetLeft(item, pos.X);
            Canvas.SetTop(item, pos.Y);
            e.Handled = true;
            break;

        default:
            break;
    }
    e.Handled = true;
}

Finally here is the xaml for the Main Canvas

<Canvas x:Name="MainCanvas" x:Name="MainCanvas"
                                    DockPanel.Dock="Top" 
                                    Background="#666" 
                                    Height="600" 
                                    Margin="4" 
                                    AllowDrop="True" 
                                    DragEnter="MainCanvas_DragEnter" 
                                    Drop="MainCanvas_Drop"/>

Edit:

ok so after lupus' response i went back and reconstructed everything from scratch in a separate temp project

<ControlTemplate x:Key="StateViewModelControlTemplate" TargetType="{x:Type vm:State}">
    <Grid Width="100" Height="60">
    
    <!--
    If I comment out the following Thumb 
    the drop event will only trigger once
    If i leave it in then it triggers twice
    
    Move Thumb is derived from thumb
    -->
        <local:MoveThumb Panel.ZIndex="99"
                         x:Name="StateViewModelMoveThumb" 
                         DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" 
                         Opacity="0"/>

        <Border Panel.ZIndex="98" 
                Margin="4" 
                Padding="4" 
                BorderBrush="white" 
                BorderThickness="2" 
                CornerRadius="5">
            <Border.Background>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FF59C7D4" Offset="0.5"/>
                    <GradientStop Color="#FF075A64" Offset="0"/>
                    <GradientStop Color="#FF00626E" Offset="1"/>
                </LinearGradientBrush>
            </Border.Background>
            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding StateName}"/>
        </Border>
    </Grid>

</ControlTemplate>

By the way what I trying to do is very loosely based on the following article: https://www.codeproject.com/Articles/22952/WPF-Diagram-Designer-Part-1


Solution

  • So I eventually got this working and here is how

    Here is the front end xaml on the mainwindow

        <cc:DiagramCanvas x:Name="MainCanvas"                         
                            DockPanel.Dock="Top" 
                            Margin="0" 
                            MinHeight="450"
                            AllowDrop="True" Background="White">
                    </cc:DiagramCanvas>
    

    Here is the custom canvas object and the drag drop handler

        public class DiagramCanvas : Canvas
        {
            public DiagramCanvas()
            {
                this.Drop += DoDrop;
                this.DragEnter += MainCanvas_DragEnter;
            }
    
            readonly MainWindow mainWin = (MainWindow)Application.Current.MainWindow;
    
            #region works dont touch
            public void DoDrop(object sender, DragEventArgs e)
            {
                var mainWin = (MainWindow)App.Current.MainWindow;
                DragDropHandler<StateVM>.Instance.Drop(sender, e);
            }
    
    
            private void MainCanvas_DragEnter(object sender, DragEventArgs e)
            {
                if (e.Data.GetDataPresent(DataFormats.Text))
    
                    e.Effects = DragDropEffects.Copy;
                else
                    e.Effects = DragDropEffects.None;
                e.Handled = true;
            }
            #endregion
        //other code here
        }
    

    Here is the drag drop handler singleton

        public sealed class DragDropHandler<T> where T : Control
        {
            #region singleton
            private static DragDropHandler<T> instance = null;
            private static readonly object padlock = new object();
    
            DragDropHandler()
            {
    
            }
    
    
            public static DragDropHandler<T> Instance
            {
                get
                {
                    lock (padlock)
                    {
                        if (instance == null)
                        {
                            instance = new DragDropHandler<T>();
                        }
                        return instance;
                    }
                }
                private set
                {
                    instance = value;
                }
            }
            #endregion
    
            public static bool IsDragging { get; set; }
            public static WrapPanel AllowedDragSource { get; set; }
            readonly MainWindow mainWin = (MainWindow)Application.Current.MainWindow;
    
    
            public static void CreateInstance(WrapPanel allowedSource)
            {
                if (DragDropHandler<T>.IsDragging == false)
                {
                    instance = new DragDropHandler<T>();
                    DragDropHandler<T>.AllowedDragSource = allowedSource;
                }
            }
    
            public void Drag(object sender, MouseEventArgs e)
            {
                if (sender as T == null
                    || mainWin.Radio_EditStates.IsChecked == false
                    || e.LeftButton != MouseButtonState.Pressed
                    || IsDragging == true)
                {
                    e.Handled = true;
                    return;
                }
    
                var item = (T)sender;
    
                if (Control.ReferenceEquals(item.Parent, AllowedDragSource) == false)
                {
                    e.Handled = true;
                    return;
                }
    
                IsDragging = true;
                DragDrop.DoDragDrop(((StateVM)sender), new DataObject(((StateVM)sender)), DragDropEffects.Copy);
                IsDragging = false;
    
                e.Handled = true;
            }
    
            public void Drop(object sender, DragEventArgs e)
            {
                var mainWin = (MainWindow)App.Current.MainWindow;
    
                if (IsDragging)
                {
                    //TODO: Switch here to handle different shapes               
    
                    var pos = e.GetPosition(mainWin.MainCanvas);
                    //
                    
    
    
    
    
                    Canvas.SetLeft(item, pos.X.RoundDownTo10());
                    Canvas.SetTop(item, pos.Y.RoundDownTo10());
                    //update  main win observalbe collections to inclue the item dropped
                    
                    IsDragging = false;
                    e.Handled = true;
                    DestroyInstance();
                }
            }
    
            private static void DestroyInstance()
            {
                DragDropHandler<T>.Instance = null;
            }
        }
    

    Here is the on mouse move code for the item you are dragging

        protected override void OnMouseMove(MouseEventArgs e)
            {
                base.OnMouseMove(e);
                if (e.LeftButton == MouseButtonState.Pressed && Control.ReferenceEquals(this.Parent, mainWin.libraryContainer))
                {
                    DragDropHandler<StateVM>.CreateInstance(mainWin.libraryContainer);
                    if (DragDropHandler<StateVM>.Instance != null)
                    {
                        DragDropHandler<StateVM>.Instance.Drag(this, e);
                    }
    
                }
            }