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

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

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

How to register to/listen to richtextbox command's?

I'm creating a simple editor within our application using the WPF RichTextBox. Above it I've added the reguslar buttons like Bold, Italic, etc. These buttons use the RichTextBox's commands to set these properties, but next to these buttons, the commands also get send with CTRL+B, CTRL+I, etc. I want these buttons to represent the current state of the RichTextBox at the cursor. I already found out how to get this state and it works when I update this state on the SelectionChanged event. This event ofcourse isn't fired when Bold is toggled so there is no direct feedback.
I would like to know if there is a way to listen to the commands being called, without affecting its original behaviour or some other ideas to solve my problems.
I tried listening to the command the following way:
CommandBinding boldBinding = new CommandBinding(EditingCommands.ToggleBold, CommandExecuted);
_richTextBox.CommandBindings.Add(boldBinding);
and
private void CommandExecuted(object sender, ExecutedRoutedEventArgs e) {
UpdateProperties();
e.Handled = false;
}
This did update the properties, but the RichTextBox didn't seem to receive the command anymore.
I also tried to make my own commands on the control containing the RichTextBox, but when CTRL+B is pressed when the RichTextBox has focus, the original RichTextBox commands are called instead of the new one.
Many thanks in advance!
Liewe
In order to listen to the commands being called, you can use the events raised by CommandManager: Executed or PreviewExecuted.
If you change your XAML to:
<RichTextBox x:Name="_richTextBox" ...
CommandManager:PreviewExecuted="OnRichTextBoxCommand" ... />
you get the OnRichTextBoxCommand method called right before the command is executed. Unfortunately, using the Executed attached event does not work.
This method is called for each event, so you have to filter them:
private void OnRichTextBoxCommand(object sender, ExecutedRoutedEventArgs e) {
if (e.Command == EditingCommands.ToggleBold) {
UpdateProperties();
}
}
It may be even a bit more complex, as the current selection may not have changed when this method is called, so you have to post yourself a message, e.g. like this:
Dispatcher.BeginInvoke(new Action(UpdateProperties));
(if you reference already System.Core, you have the Action type, otherwise define a delegate taking no parameter and returning void, and use in instead.)

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

Drag drop in 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

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

Resources