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;
}
}
Related
How do I repetitively perform an action when the mouse left button is pressed and held down in WPF?
The following event handler for the UIElement.PreviewMouseLeftButtonDown event does not get the job done:
private void BaseButtonRight_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// keep performing action while mouse left button is pressed.
// Checking e.ButtonState works only for one click
}
Execution does not even get into the while loop and handler is called when the left mouse button is released!
Why not use a RepeatButton?
http://msdn.microsoft.com/en-us/library/ms746649(v=vs.85).aspx
Start a BackroundWorker which exits when the mouse has been released. Set a flag with the mouse up event and also check periodically in the BackgroundWorker DoWork function. Make sure you use lock { } around accessing the flag.
Edit: in case you want to access something on the UI thread, use Dispatcher.BeginInvoke, for example:
Dispatcher.BeginInvoke(new ThreadStart(delegate
{
ComboBoxItem selItem = ComboboxConnectString.SelectedItem as ComboBoxItem;
if (selItem != null && selItem.Tag is string)
ComboboxConnectString.Text = (string)selItem.Tag;
}));
Run a thread until MouseLeftButtonUp is fired.
How to remove all the associated in built events of a control in WPF?
There is no easy way to remove all events queued on the Dispatcher. Even if there were, you wouldn't want to do so because it would break many WPF controls that rely on background events to update their data structures and UI.
However, there is an easy way to discard selected input events from the queue, such as keyboard, mouse and stylus events:
Create a "FlushInputQueue" method that registers an event handler with InputManager.PreprocessInput, invokes a DispatcherOperation at Input priority, then removes the event handler.
When the PreprocessInput event fires, check the input message and set the Handled flag to true if you want to discard it.
Here is some code to get you started:
public void FlushInputQueue()
{
PreProcessInputEvent handler = (obj, e) =>
{
if(ShouldFlushEvent(e.StagingItem.Input))
e.StagingItem.Input.Handled = true;
};
InputManager.PreProcessInput += handler;
Dispatcher.Invoke(DispatcherPriority.Input, new Action(() => {}));
InputManager.PreProcessInput -= handler;
}
private bool ShouldFlushEvent(InputEventArgs e)
{
// Example only:
return e is MouseEventArgs || e is KeyboardEventArgs || e is StylusEventArgs;
}
The ShouldFlushEvent method should probably be customized for your particular scenario to avoid throwing out events that should be kept.
One last thought: Have you considered doing your long-running operation on a background thread so the UI remains responsive? Many times this is a better solution than locking the UI when an item is clicked on, and removes any reason for wanting to flush the queue.
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
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
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;
}