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
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);
}
}
}