DragLeave constructs DragEventArgs with coordinates relative to the new target instead of the event target
- 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
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