Visitar URL original
fix(win32): Show the drag source's own image by mikernet · Pull Request #25874 · unoplatform/uno · GitHub
Skip to content

fix(win32): Show the drag source's own image - #25874

Open
mikernet wants to merge 1 commit into
unoplatform:masterfrom
mikernet:dev/mikernet/win32-drag-preview
Open

mikernet wants to merge 1 commit into
unoplatform:masterfrom
mikernet:dev/mikernet/win32-drag-preview

Conversation

@mikernet

@mikernet mikernet commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

GitHub Issue: closes #25873

PR Type:

🐞 Bugfix

What changed? 🚀

On Desktop (Windows), an external drag now shows the source app's own drag image over the window, as in WinUI, instead of a preview that Uno built from the dragged data.

Before. The Win32 drop target didn't use the shell's drag-image manager, so the source's drag image (Explorer's thumbnail, a browser's picture of the dragged image) disappeared over the window. CreateDragUIForExternalDrag built a preview instead:

  • it decoded a dragged bitmap (CF_DIB), which never showed, because it passed a raw DIB without a BMP file header to BitmapImage.SetSource;
  • otherwise, it loaded the first image file from disk;
  • otherwise, it extracted the first file's icon.

That cost a second GetData copy of the bitmap at every DragEnter, and file reads or icon extraction on the UI thread.

After.

  • The drop target creates the shell's drag-image manager (CoCreateInstance(CLSID_DragDropHelper), IDropTargetHelper) at DragEnter. The manager is apartment-threaded; it's created, used and released on the UI thread, where OLE calls the drop target.
  • The drop target forwards its calls to the manager:
    • DragEnter (with the window handle, the data object and the point) and DragOver, each with the effect the drop target returns;
    • DragLeave and Drop, first, before Uno processes them, as Chromium and WinForms do (WinForms only when the app sets DropImageType). Drop gets the effects the source allows, as in Chromium. The processing that follows can't freeze the source's image then (an asynchronous file drop can wait up to a second), and an exception in it can't keep the manager alive.
  • When the source set no drag image, the manager's DragEnter fails (DV_E_FORMATETC), but the manager still keeps a reference to the data object until DragLeave or Drop. So the drop target ignores DragEnter's result and forwards the rest anyway, as Chromium and WinForms do.
  • The drop target keeps a reference to the data object from DragEnter to DragLeave or Drop, as Chromium's drop target does, to enter the manager again in the middle of a drag (see below).
  • The manager and the data object are released at DragLeave or Drop, or at a DragEnter that comes without either.
  • The shell draws the source's drag image over the window, as it does over a WinUI app.
  • If the manager can't be created, a warning is logged once per window, and drags still work, without a drag image.
  • Uno no longer builds its own preview for an external drag:
    • CreateDragUIForExternalDrag and the image, icon and PNG helpers it used are removed;
    • so are the NativeMethods.txt entries that only they used, and DragUI's internal external-content members along with the image sizing that DragView applied to them.
  • The drop target passes no DragUI, as the macOS, X11 and WebAssembly hosts already do.

What happens to #21759's scenario. An image file dragged from Explorer still shows a thumbnail, now Explorer's own. A source that sets no drag image (WPF and WinForms apps by default, and many classic Win32 apps) now shows only the glyph and caption. For a file drag from such a source, Uno 6.8 drew the file's icon or thumbnail; on master, nothing showed, since the drag visual's image doesn't show there at the moment (#25869). WinUI builds no visual from the dragged data (see the issue), so it should show only the glyph and caption too; I haven't checked that in a WinUI app.

DragUIOverride. WinUI only changes the drag visual when the drop target sets something through DragUIOverride. The content it sets replaces the source's visual, and IsContentVisible = false hides the content. Here:

  • Caption and glyph: Uno's drag visual (DragView) still draws them near the pointer, as before.
  • Content set with SetContentFromBitmapImage, or IsContentVisible = false: the drop target forwards a DragLeave to the manager, which removes the source's image from the window, as when the pointer leaves it. DragView draws the app's content, as before, or nothing but the glyph and caption. If the drop target already does this in the DragOver that Uno raises within the window's DragEnter, the manager isn't entered at all.
  • Once the content is no longer replaced or hidden, the drop target forwards DragEnter again, with the data object, the current point and the current effect, and goes on forwarding DragOver. It uses the same manager, as when the pointer leaves the window and comes back.
  • The real DragLeave and Drop are only forwarded while the manager is entered.
  • IDropTargetHelper::Show isn't used. The docs only describe it for hiding the image while a window repaints, and it didn't hide the image in a manual test.
  • The leave and re-enter were confirmed in a manual test with a diagnostic log: the re-entering DragEnter returned S_OK, and the source's image came back.

Depends on #25872 (issue #25871). On master, an override that replaces or hides the content can outlive the drop target that set it. CoreDragUIOverride.Clear() resets IsContentVisible but not Content, and a drop target that the drag goes straight back to gets its saved override again, without a Clear(). So without #25872:

  • once the drag has crossed an area that replaces the content, the source's image doesn't come back for the rest of that drag;
  • after an area that hides the content, the image comes back over any other drop target, or over an area that accepts no drop, but not over a drop target that the drag goes straight back to.

#25872 resets the override per drop target, as WinUI does.

Related: #25870 (issue #25869). On master, the drag visual is never loaded, so content that the drop target sets with SetContentFromBitmapImage (like the sample's custom content area) doesn't show there. #25870 fixes that; it doesn't change what this PR does with the source's image.

Two things are still different from WinUI:

  • WinUI's drag visual draws the caption and glyph next to the source's image, as one visual. Here the shell draws the image where the source placed it relative to the pointer, and Uno draws the caption and glyph inside its window.
  • When the drag moves onto a drop target inside the window, Uno raises only DragEnter on it (see fix(dragdrop): Reset the drag UI override per drop target #25872's follow-up). So a drop target that sets the override in DragOver shows the source's image for one more OLE callback, with no accepted operation, before it's removed. The accepted operation has the same delay.

Platforms: Desktop (Windows) only. The macOS, X11 and WebAssembly hosts pass no DragUI either, and all three show the source's drag image.

Tests: Given_Win32ExternalDrag calls the window's real IDropTarget through its COM interface, as DoDragDrop does. It uses a shell data object (SHCreateDataObject) that offers a CF_DIB, with a drag image set through IDragSourceHelper::InitializeFromBitmap, as a drag source sets it, or without one. On Skia Desktop (Windows), on master at 1f2fc30:

Test master's Win32 host This PR
When_Bitmap_Dragged_Over_Then_No_Preview_Is_Generated fails: the drag visual has Uno-made content passes
When_Drop_Target_Overrides_DragUI_Then_Drag_Visual_Shows_It, no content override fails: Uno-made content (caption and glyph pass) passes: the manager is entered at DragEnter and stays entered
the same, with SetContentFromBitmapImage fails: there's no manager (Win32DragDropExtension has no field _dropTargetHelper; the app's content shows) passes: the app's content shows, and the manager is never entered
the same, with IsContentVisible = false fails: Uno-made content (caption, glyph and content visibility pass) passes: the manager is never entered
When_Content_Hidden_Then_Shown_Again_Then_Shell_Drag_Image_Manager_Is_Left_Then_Entered_Again, ended by DragLeave and by Drop (2 rows) fail: there's no manager (Win32DragDropExtension has no field _isDropTargetHelperEntered) pass: entered, left at the DragOver where the content is hidden (and still left on the next one), entered again at the one where it's shown, then released at the end, along with the data object
When_External_Drag_Ends_Then_Shell_Drag_Image_Manager_And_Data_Object_Are_Released, ended by DragLeave and by Drop, with and without a drag image (4 rows) fail: there's no manager (Win32DragDropExtension has no field _dropTargetHelper) pass: created at DragEnter, kept through DragOver, released at DragLeave or Drop, the data object's reference count is back to what it was before DragEnter, and the drop still reaches the drop target

The master column is master's Win32 host, DragUI and DragView, with this PR's tests and the new internal DragDropManager.IsContentReplacedOrHidden, which the tests call. These tests can't check what the shell draws, only what Uno does. I also ran Given_Win32DragDrop, Given_DragView, and the drag-and-drop tests of Given_UIElement, Given_ListViewBase and Given_TreeView: 34 tests, the 10 new ones included, all passing, after a build with the analyzers on.

Manual test: the new sample DragDrop_ExternalDragVisual (Drag and drop) has four drop areas: default, custom caption without glyph, custom content, and hidden content. A drop lists the formats and the data.

Results, on Desktop (Windows), with a build of this PR plus #25872 and #25870:

  • over the default and custom caption areas, the source's own image follows the pointer, with Uno's glyph and caption ("Custom caption" without the glyph over the second);
  • over the custom content area, the app's content (the sample's 100×100 test image, Assets/test_image_100_100.png) replaces the source's image;
  • over the hidden content area, there's no image, only the glyph and caption;
  • this stays consistent as the drag moves between the areas, including back to areas that it was over before.

Backport: please consider backporting this to servicing/6.8. A Mergify cherry-pick will conflict, but only mechanically: the three deleted files are in another folder on 6.8 (and one of them differs a little), and the new sample and test files go in other folders too. We can open the backport ourselves if you prefer. We ran the port's tests on our own 6.8-based branch. In the files this change touches, it differs from servicing/6.8 only in the asynchronous file drop code of Win32DragDropExtension.cs (the backport in #25865), which this change doesn't touch. There, Given_ListViewBase.When_UpdateLayout_In_DragDropping fails intermittently, with and without this change, and passes when run again; the other 33 tests pass. As on master, it needs #25872 there too, which asks for the same backport.

PR Checklist ✅

@github-actions github-actions Bot added area/skia ✏️ Categorizes an issue or PR as relevant to Skia area/automation Categorizes an issue or PR as relevant to project automation kind/documentation labels Nov 1, 2026
@MartinZikmund

Copy link
Copy Markdown
Member

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Sample App stage site is ready! Visit it here:

@nventive-devops

Copy link
Copy Markdown
Contributor

The build 242763 found UI Test snapshots differences: skia-linux-screenshots: 2458, skia-windows-screenshots: 2458

Details
  • skia-linux-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • Accessibility_ScreenReader.png
    • AnimatedBackVisualSourceSamplePage.png-dark
    • AnimatedIconPage.png
    • AppBarButtonTest.png-dark
    • AppBarButtonWithIconTest.png
    • AnimatedVisualsTestbedPage.png-dark
    • AppBar_KeyBoard.png-dark
    • ApplicationViewMode.png-dark
    • ApplicationViewSpanningRectsPage.png-dark
    • ApplicationViewSpanningRectsPage.png
    • AppWindowClosing.png
    • AppWindowPositionAndSize.png
    • AppWindowFeatures.png
    • AppXamlDefinedResources.png-dark
    • ArcSegment.png
    • AppWindowTitleBar Properties.png-dark
    • Attributed_text_FontSize_Changing.png-dark
    • AuthenticationBroker_Cancel.png-dark
    • Attributed_text_Simple.png-dark
    • AuthenticationBroker_Demo.png
  • skia-windows-screenshots: 2458 changed over 2492

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AnimatedIconPage.png
    • AnimatedVisualsTestbedPage.png-dark
    • AppBarButtonTest.png
    • ApplicationViewMode.png
    • Application_DispatcherShutdownMode.png-dark
    • AppWindowPresenters.png
    • Arrange_Performance01.png-dark
    • Attributed_text_Simple.png
    • AutoBorderStretchwithbottommargin.png
    • AutoBorderStretchwithleftmargin.png-dark
    • AutomationProperties_StructureAndProps.png-dark
    • AutomationProperties_StructureAndProps.png
    • AutoSuggestBox_Description.png-dark
    • AutoSuggestBox_Description.png
    • BackGesture.png-dark
    • BackGesture.png
    • BasicAutoSuggestBox.png-dark
    • BasicAutoSuggestBox.png
    • BasicThemeResources.png-dark
    • BasicThemeResources.png

@unodevops

Copy link
Copy Markdown
Contributor

⚠️⚠️ The build 242763 has failed on Uno.UI - CI.

@mergify

mergify Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue). If it does not match the queue conditions yet, it enters the queue once it does.

  • Queue this pull request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/automation Categorizes an issue or PR as relevant to project automation area/skia ✏️ Categorizes an issue or PR as relevant to Skia kind/documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Win32] External drags don't show the source app's drag image

4 participants