dotnet / dotnet/wpf

DragLeave constructs DragEventArgs with coordinates relative to the new target instead of the event target

Open
#11,774 1 comment 1 reaction 0 assignees View on GitHub
Bug
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

Description

### Description

In WPF's drag-and-drop, when the mouse moves from an [AllowDrop](https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.allowdrop?view=windowsdesktop-10.0) element to another element, [DragLeave](https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.dragleave?view=windowsdesktop-10.0) event will be raised for the previous element. However, the [DragEventArgs](https://learn.microsoft.com/en-us/dotnet/api/system.windows.drageventargs?view=windowsdesktop-10.0) passed contains Point that not calculated based on the event target (element which mouse left).
Case A: When the new element (which mouse gonna enter) is `AllowDrop="true"`, that Point is calculated based on the new element.
Case B: If the new element is `AllowDrop='false'`, that Point will be raw point in screen coordinates.

This bug causes [DragEventArgs.GetPosition(IInputElement)](https://learn.microsoft.com/en-us/dotnet/api/system.windows.drageventargs.getposition?view=windowsdesktop-10.0) to return meaningless Point when you get such a bad DragEventArgs. Since the `DragEventArgs._dropPoint` stored is not in the coordinate system of `DragEventArgs._target` that used to perform transformation.

Root cause:
https://github.com/dotnet/wpf/blob/6ebec6211a69e7d86e24116b29b9e5d571f8f9e3/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/DragDrop.cs#L955-L1035

As you can see, there are two way to raise a `DragDrop.DragLeaveEvent`, located in line 978 and line 1018 separately.
Both of them used variable `_lastTarget` and `targetPoint`.
However, `targetPoint` was calced with `target` by calling `GetCurrentTarget` in line 963, which means `targetPoint` is associated with `target`.
And that explains why Case A happened.

Then take a look at the function `GetCurrentTarget`.
https://github.com/dotnet/wpf/blob/6ebec6211a69e7d86e24116b29b9e5d571f8f9e3/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/DragDrop.cs#L1288-L1366

If the potential `target` is `AllowDrop="false"` (Line 1314), the function will set `target` to `null`. And when the `target` is `null`, the raw `targetPoint` will not be translated (Line 1360). This is the reason for Case B.

Btw, there is another way to raise a `DragLeave` event, which located in funciton `OleDragLeave`. But this is not part of our issue.

### Reproduction Steps (Case A for example)

Create a new .NET 10 WPF proj

MainWindow.xaml
```




```

MainWindow.xaml.cs
```c#
using System.Diagnostics;
using System.Reflection;
using System.Windows;

namespace DragLeaveFix
{
public partial class MainWindow : Window
{
public MainWindow() => InitializeComponent();
private readonly FieldInfo DragEventArgsField_dropPoint = typeof(DragEventArgs).GetField("_dropPoint", BindingFlags.Instance | BindingFlags.NonPublic)!;
private readonly FieldInfo DragEventArgsField_target = typeof(DragEventArgs).GetField("_target", BindingFlags.Instance | BindingFlags.NonPublic)!;
private void Rectangle_DragLeave(object sender, DragEventArgs e)
{
// if the event was raised by OleDragLeave, e.KeyStates will be DragDropKeyStates.None
// so make sure it was raised by OleDragOver
if (e.KeyStates != DragDropKeyStates.None)
{
// use "grid" to contain "rect" instead of using Window directly
// bcuz constrain size for the element inside the window doesn't equal to window's actual size
var gridRect = new Rect(0, 0, grid.ActualWidth, grid.ActualHeight);

var gridWidth = grid.ActualWidth;
var rectWidth = rect.ActualWidth;
var rectPosX = rect.TranslatePoint(new(0, 0), grid).X;
var e_dropPoint = (Point)DragEventArgsField_dropPoint.GetValue(e)!;
var e_target = (UIElement)DragEventArgsField_target.GetValue(e)!;
var e_PosInGrid = e.GetPosition(grid);

// As the issue said, e_dropPoint is based on "grid" (which your mouse gonna move into), not the e_target ("rect")
if (gridRect.Contains(e_dropPoint))
{
Debug.WriteLine("\n\n\n");
Debug.WriteLine("===== Element Info =====");
Debug.WriteLine($"'grid' Width: {gridWidth}");
Debug.WriteLine($"'rect' Width: {rectWidth}");
Debug.WriteLine($"'rect' Pos X (relative to 'grid'): {rectPosX}");

Debug.WriteLine("===== Args Info =====");
Debug.WriteLine($"e._dropPoint: {e_dropPoint}");
Debug.WriteLine($"e._target: {e_target}");
Debug.WriteLine($"e.GetPosition(grid).X: {e_PosInGrid.X}");

Debug.WriteLine("===== issue =====");
Debug.WriteLine($"e.GetPosition(grid) inside 'grid'? (Expected: true, Actual: false): {gridRect.Contains(e_PosInGrid)}");
Debug.WriteLine($"How do we get this: rectPosX + e._dropPoint.X == e.GetPosition(grid).X -> {rectPosX + e_dropPoint.X} == {e_PosInGrid.X}");
}
}
}
}
}
```

Run the App in debug mode, drag something into the Gray Area (e.g. files on your desktop). Then drag you mouse to the White Area without releasing mouse button.
You can see something like this in debug output:
```
===== Element Info =====
'grid' Width: 585.6
'rect' Width: 100
'rect' Pos X (relative to 'grid'): 485.6
===== Args Info =====
e._dropPoint: 481.6,228
e._target: System.Windows.Shapes.Rectangle
e.GetPosition(grid).X: 967.2
===== issue =====
e.GetPosition(grid) inside 'grid'? (Expected: true, Actual: false): False
How do we get this: rectPosX + e._dropPoint.X == e.GetPosition(grid).X -> 967.20 == 967.20
```

### Expected behavior

`DragLeave` should make sure that `_dropPoint` is based on `_target`.

### Actual behavior

`DragLeave`'s `_dropPoint` doesn't associated with `_target` most of the time.

### Regression?

No

### Known Workarounds

_No response_

### Impact

_No response_

### Configuration

_No response_

### Other information

_No response_

Contributor guide

Open the contributing guide

Research direction

Start in src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/DragDrop.cs around the DragLeaveEvent raises at lines 955-1035, then read GetCurrentTarget around lines 1288-1366. Reproduce both AllowDrop cases from the issue and verify that DragLeave's _dropPoint is associated with _target and that GetPosition returns the expected coordinates; OleDragLeave is outside this issue.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
desktop
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.