Drag drop in WPF - wpf

I have WPF application with drag-n- drop inmplementation...
Whenever I drag tree item on a Grid it is processed by DragDrop Event of that Grid, but every time it get fired twice what could be the reason?
Below is code for implementing drag drop on a TreeView:
void treeViewGroups_MouseMove(object sender, MouseEventArgs e)
{
try
{
if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
{
Point position = e.GetPosition(null);
if (Math.Abs(position.X - this.startPoint.X) > SystemParameters.MinimumHorizontalDragDistance || Math.Abs(position.Y - this.startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
{
DataRowView treeViewItem = this.treeViewGroups.SelectedItem as DataRowView;
if (treeViewItem != null)
if ((treeViewItem.Row.Table.TableName == "TableGroup"))
{
ViewTaxSCConstants.dragElement = treeViewItem;
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render, new System.Threading.ParameterizedThreadStart(DoDragDrop), treeViewItem);
}
}
}
}

I had nearly the same problem: I started the drag event on MouseMove and had a drop event on certain TreeViewItems. After the drop event fired first, it'd fire a second time but the target would be a different element (in my case, the parent of the one that was my target).
To solve it, I had to set e.Handled = true in the Drop event.

I think this is a good method for Drag & Drop
A good way for darg and drop are explained as
Detect a drag as a combinatination of MouseMove and MouseLeftButtonDown
Find the data you want to drag and create a DataObject that contains the format, the data and the allowed effects.
Initiate the dragging by calling DoDragDrop()
Set the AllowDrop property to True on the elements you want to allow dropping.
Register a handler to the DragEnter event to detect a dragging over the drop location. Check the format and the data by calling GetDataPresent() on the event args. If the data can be dropped, set the Effect property on the event args to display the appropriate mouse cursor.
When the user releases the mouse button the DragDrop event is called. Get the data by calling the GetData() method on the Data object provided in the event args.
You can find the complete article here

Related

How to detect mouse release during drag-drop

I have a custom WPF control which handles drag/drop. I override OnDragOver so that the control will not accept the dropped object if it is busy doing something else:
protected override void OnDragOver(DragEventArgs e)
{
base.OnDragOver(e);
if (isBusy)
e.Effects = DragDropEffects.None;
else
e.Effects = DragDropEffects.Move;
e.Handled = true;
}
In another control which initiates the drag&drop, there is some UI element which is disabled when the operation starts and supposed to be enabled if the operation is cancelled or when the mouse is released on the target while the above target says the operation is not allowed.
What events can I use on the source control to check for the 2nd condition?
As Hans Passant answered in a comment, to check whether the the operation was cancelled you can use the return value, DragDropEffects, of DragDrop.DoDragDrop().
None: The drop target does not accept the data.
Copy: The data is copied to the drop target.
Move: The data from the drag source is moved to the drop target.
Link: The data from the drag source is linked to the drop target.
Scroll: Scrolling is about to start or is currently occurring in the
drop target.
All: The data is copied, removed from the drag source, and scrolled in
the drop target.
None is the value you are interested in. When the mouse is released, the operation will be cancelled, and DoDragDrop() will return None.
While WPF Drag & Drop is in progress, the GiveFeedback event is continuously being fired on the drag source, you can check the event arguments state & update the drag source accordingly.
Here is a code sample: (assuming the element being dragged is called dragSource)
// Attach the event handler
dragSource += OnDragSourceGiveFeedback;
// Event Handler
private void OnDragSourceGiveFeedback(object sender, GiveFeedbackEventArgs e)
{
if (e.Effects == DragDropEffects.None)
{
// Drop is not allowed on the Drop Target
dragSource.IsEnabled = false;
}
}

WPF drag and drop from a ListBox that has SelectionMode=Extended

I have a ListBox and want the selection-mode to be extended. Also I want have to implement drag and drop functionality. The problem now is, that if the mouse is clicked on a selected item, it will be immediately be selected as single selection instead of waiting to the mouse-up-event for doing this.
Due to this behaviour, start dragging multiple items is for the user quasi impossible because always he clicks on the selection to start dragging, the selection changes to the item that is under the mouse and starts the drag-operation with this item.
Is there a good workaround for this problem or does even exists an official solution?
Here's what I've done. In your DragDrop code, subscribe to the PreviewMouseLeftButtonDown. If the item you are already clicking on is selected, then set e.Handled to true.
In my sample below, I identify a part of the list box item as a drag grip (with bumps) so that I can distinguish between the item and a drag surface. I just had to get the list box item data template and the drag and drop behavior to agree on a name of the drag grip element.
The PreviewMouseLeftButtonDown from my work in progress:
private void ItemsControl_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
dragStartPoint = e.GetPosition(null);
ItemsControl itemsControl = this.AssociatedObject as ItemsControl;
if (itemsControl != null)
{
this.sourceItemContainer = itemsControl.ContainerFromElement((Visual)e.OriginalSource) as FrameworkElement;
}
// If this is an multiple or extended selection list box, and on a drag grip, then ensure the item being hit is selected
// This prevents the ItemsControl from using this MouseDown to change selection, except over a selected item's drag grip.
if ((this.IsMultipleSelectionListBox() == true) && (this.IsOriginalSourceDragGrip(e) != false) && (this.IsSourceListBoxItemSelected() == true))
{
e.Handled = true;
}
}
The easiest workaround i can think of would be to change the ListBoxItem to select on MouseUp not Down like so and change the ContainerGenerator to serve your custom ListBoxItems:
public class CustomListBoxItem : ListBoxItem
{
protected override void OnMouseLeftButtonDown( MouseButtonEventArgs e )
{
//do nothing
}
protected override void OnMouseLeftButtonUp( MouseButtonEventArgs e )
{
base.OnMouseLeftButtonDown( e );
}
}
You might need some MouseLeave/LeftButtonDown logic if you want to prevent different items selecting when moving through the List while holding the mouse button down.
Use PreviewMouseLeftButtonDown to add the selected items for the drag operation.

Getting type of control on mouseover

I wanted to get the type of the control on mouseover. Please help
You can get the type of the UIElement over which the mouse is currently moving using the MouseMove event. Since this is a bubbling event you can attach a handler to the container such as a Canvas.
The UIElement over which the mouse is currently moving can be aquired from the the MouseEventArgs OriginalSource property.
Hence to determine the type over which the mouse is moving you could use code like this:-
void Canvas_MouseMove(object sender, MouseEventArgs e)
{
Type currentType = e.OriginalSource.GetType();
// Make decisions based on value of currentType here
}
However you need be careful, MouseMove fires frequently as the user moves the mouse so you might want to defer any heavy work until there is some time period after the last mouse move.
There is unfortunately no bubbling mouse over event.
The other alternative is to attach the same MouseEnter handler to each child UIElement you add to the Canvas. You could use sender instead of e.OriginalSource in that case. You would have to be careful to remove the handler if the element is removed from the Canvas, else you can create what would appear to be a memory leak.
Add mouse_enter event to the control.
You can get the type with a line of code as follow
var x = sender.GetType();
You can then compare it using something like:
if (x.Equals(typeof(TreeView)))

How to update the position of a drag adorner during WPF drag-n-drop?

I'm using an adorner to show a 'ghost' of the element being dragged...
var adornerLayer = AdornerLayer.GetAdornerLayer(topLevelGrid);
dragAdorner = new DragAdorner(topLevelGrid, itemToDrag);
adornerLayer.Add(dragAdorner);
dragAdorner.UpdatePosition(e.GetPosition(topLevelGrid));
DragDrop.DoDragDrop(sourceItems, viewModel, DragDropEffects.Move);
adornerLayer.Remove(dragAdorner);
itemToDrag = null;
...but I can't find a nice way to update the position of the adorner during the drag. The closest I've got is by setting AllowDrop="true" on the top level grid and giving it a DragOver handler...
private void TopLevelGrid_OnDragOver(object sender, DragEventArgs e)
{
dragAdorner.UpdatePosition(e.GetPosition(topLevelGrid));
}
But this means I don't get the proper DragDropEffects feedback on the cursor, i.e., it always shows the DragDropEffects.Move cursor instead of DragDropEffects.None until I'm over an actual drop target.
Does anyone know a better way to update the adorner position?
There's this (unfortunately only available as a cached version) pretty old blog post from Bea Stollnitz, which pretty much covers your question. It has a nice implementation of drag n drop with an adorner showing a "ghost image".
Basically drag and drop in WPF is quite the complicate procedure that - if you want some custom DragAdorners - involves adding a bunch of attached dependency properties for handling setup of all the events involved and especially for displaying the adorner in a way that doesn't interfere with the dropping code.
Bea's code works by having a helper class that sets the owning Window's DragOver event handler and AllowDrop right before the actual drag drop operation, that way you can control all the moving in between the actual drag source and the drop target.
So, looking closer at Bea's code that redoced was referring to...
I still set AllowDrop="true" on the top level grid and give it a DragOver handler where I can update the adorner position, but I also set the DragDropEffects to None here. Then I just need to add a DragOver handler to the actual drop target to also update the adorner position...and making sure to set e.Handled = true so that the top level grid's handler doesn't just set the effects back to None when I'm over a drop target...
private void TopLevelGrid_OnDragOver(object sender, DragEventArgs e)
{
UpdateDragAdornerPosition(e.GetPosition(topLevelGrid));
e.Effects = DragDropEffects.None;
e.Handled = true;
}
private void DropTarget_OnDragOver(object sender, DragEventArgs e)
{
UpdateDragAdornerPosition(e.GetPosition(topLevelGrid));
e.Handled = true;
}
I know this is an old question, but I ended up asking the same thing recently and then had to answer it myself. I used hooks via p/invoke to get at the native window messages before they were consumed by the drag and drop operation. This let me track the mouse even during drag and drop and without having to set AllowDrop where I didn't want it.
For the full answer (including most of the code I used) you can check out my question:
WPF - Track mouse during Drag & Drop while AllowDrop = False

Wpf dragdrop, how to visually reject the drop during the drag?

During a drag in Wpf, how can the mouse cursor (or perhaps using an adorner) be changed to indicate that the droptarget will not accept the dragged item?
I've tried to set e.Effects = DragDropEffects.None during the DragEnter event but this isn't working and I suspect that I've misunderstood what that feature should be used for. I've tried using the GiveFeedback event but don't see how the droptarget can influence it.
Just setting the DragDropEffects in DragEnter of the drop target should work. Is your DragEnter even getting called. Have you set AllowDrop on the drop target control?
This is the sequence of events during a drag & drop in WPF (taken from MSDN) which might help work out what's going on...
Dragging is initiated by calling the DoDragDrop method for the source control.
The DoDragDrop method takes two parameters:
* data, specifying the data to pass
* allowedEffects, specifying which operations (copying and/or moving) are allowed
A new DataObject object is automatically created.
This in turn raises the GiveFeedback event. In most cases you do not need to worry about the GiveFeedback event, but if you wanted to display a custom mouse pointer during the drag, this is where you would add your code.
Any control with its AllowDrop property set to True is a potential drop target. The AllowDrop property can be set in the Properties window at design time, or programmatically in the Form_Load event.
As the mouse passes over each control, the DragEnter event for that control is raised. The GetDataPresent method is used to make sure that the format of the data is appropriate to the target control, and the Effect property is used to display the appropriate mouse pointer.
If the user releases the mouse button over a valid drop target, the DragDrop event is raised. Code in the DragDrop event handler extracts the data from the DataObject object and displays it in the target control.
I had a similar problem because I changed the cursor in the GiveFeedback handler.
This cursor was used even the drop target did reject the data.
After switching back to the default cursor (e.UseDefaultCursors = true) the cursor shape did change propely to "not allowed".
You didn't say if you use the DragOver even. Maybe you're setting e.Effect = DragDropEffects.All; in this even and it will be fired repeatedly after you enter the target control instead of DragEnter that will be fired just once.
private void arbol_DragOver(object sender, DragEventArgs e)
{
if (some_reason)
e.Effect = DragDropEffects.None;
else
e.Effect = DragDropEffects.All;
}
If you didn't use this event or didn't modify e.Effect within, then it's hard to say. Code is needed.
If you want the app to take your change in account,
you should set Handled property to true:
private void OnDragOver(object sender, DragEventArgs e)
{
if (your_test)
e.Effects = DragDropEffects.Link;//or other effect you want
else
e.Effects = DragDropEffects.None;
e.Handled = true;
}

Resources