dotnet / winforms

Windows Forms is a .NET UI framework for building Windows desktop applications.
MIT License
4.41k stars 984 forks source link

support drag and drop with icon and text label #5884

Closed ghost closed 2 years ago

ghost commented 3 years ago

💥 💥 ⬇️⬇️ Scroll to the API proposal ⬇️⬇️💥 💥


About Winform drag and drop: https://docs.microsoft.com/en-us/dotnet/desktop/winforms/input-mouse/drag-and-drop

Winforms supports drag and drop, but it displays the Windows 2000/XP old school style.

003

Unlike win10, win10 can provide richer display effects. If you drag and drop a file on Win10, Win10 will display an icon and a text label. Then you can know your specific operation through the text label.

Picture 1: Drag a file:

001

Picture 2: When you hold down the Control or Alt key, the icon and text will also be changed.

002-2

Picture 3: Drag a file to application shortcut, it will display:

004

So if the drag and drop can provide an Icon and Text properties, the application can display a more detailed operating instruction to users, and the application will be more modern.

RussKie commented 3 years ago

What exactly are you asking for?

ghost commented 3 years ago

I have updated the post to more detailed.

wjk commented 3 years ago

Drag images can be created today via the IDragSourceHelper interface. Similarly, drop text can also be created today using the DROPDESCRIPTION structure and clipboard format, as demonstrated here. Use of either requires a custom IDataObject implementation because of limitations in the standard Windows Forms one; the latter link provides an example. It would be quite nice if this functionality could be rolled into Windows Forms core.

Note that the drag source supplies the image, but the drop destination supplies the text and icon. You cannot provide label text from a drag source.

ghost commented 3 years ago

Drag images can be created today via the IDragSourceHelper interface. Similarly, drop text can also be created today using the DROPDESCRIPTION structure and clipboard format, as demonstrated here. Use of either requires a custom IDataObject implementation because of limitations in the standard Windows Forms one; the latter link provides an example. It would be quite nice if this functionality could be rolled into Windows Forms core.

Note that the drag source supplies the image, but the drop destination supplies the text and icon. You cannot provide label text from a drag source.

I can see this API from microsoft website.

IDragSourceHelper2::SetFlags with flag DSH_ALLOWDROPDESCRIPTIONTEXT

https://docs.microsoft.com/en-us/windows/win32/api/shobjidl/nf-shobjidl-idragsourcehelper2-setflags

It will display the drag image and text at the same time.

RussKie commented 3 years ago

Would you like to do a formal API proposal so we can take it to the next level? If you have a POC - it would greatly aid in discussions.

ghost commented 3 years ago

@wjk Can you help me to fill the formal API proposal? My English is not very good. Thank you very much.

willibrandon commented 3 years ago

Good idea.

willibrandon commented 3 years ago

New school style drag and drop.

DragImageText

ghost commented 3 years ago

Yes, that's a perfect effect.

In this way, more data and hints can be displayed to users.

willibrandon commented 3 years ago

@roland5572 - you have really good ideas.

ghost commented 3 years ago

Thank you. I also have some ideas about WPF, if you are interested, you can check it out.

https://github.com/dotnet/wpf/issues/5565 https://github.com/dotnet/wpf/issues/5566

willibrandon commented 3 years ago

I will check those out.

For this issue, has any progress been made on a proposal?

willibrandon commented 3 years ago

Okay then if nobody minds, I will take a closer look. I need to learn quite a bit more about drag and drop before I would be comfortable suggesting anything but I think with a little focus it is doable. Drag image and text.

RussKie commented 3 years ago

👍

willibrandon commented 3 years ago

Alpha blended drag image.

nyancat_alphablended

willibrandon commented 3 years ago

Drag image with drop description proof of concept.

https://user-images.githubusercontent.com/5017479/140713761-1b35f272-4818-4fcd-9b20-494691d2307d.mp4

merriemcgaw commented 3 years ago

This is awesome stuff! Have you thought about whether we should support for dragging something with a drag source outside of the application? Like picking up a file from Windows to copy into your app.

willibrandon commented 3 years ago

Thanks, its been really fun. I think adding support for drag images and drop descriptions for drag sources outside of the application would be really helpful. It would allow the application to display more details to the user regarding the drag-and-drop operation.

willibrandon commented 3 years ago

Drag images and drop descriptions from Explorer.

https://user-images.githubusercontent.com/5017479/141087491-b7d0002f-4362-472f-b919-811fea963dbd.mp4

RussKie commented 3 years ago

Awesome! Standing by for your API proposal. Feel free to open a draft PR to accompany it as well.

willibrandon commented 3 years ago

This will be a stretch for me but I'll start putting it together. I believe there are some other subtler features I haven't looked at yet such as the mouse cursor offset in relation to the drag image and how that allows for some cool things like insertion points with previews.

willibrandon commented 3 years ago

Note that the outer edges of the drag image are blended out if the image width or height exceeds 300 pixels.

DragImageEdgeBlend

willibrandon commented 3 years ago

I've hit a little bit of a snag. Drag images don't seem to work well with the RichTextBox and I need some time to figure out why.

willibrandon commented 2 years ago

Just an update that I'm still researching this issue. Just received 4 books (all released in the 90's) I'm looking through now trying to learn everything I can about drag and drop.

willibrandon commented 2 years ago

I hope everyone can understand for me there is a big difference between getting some sample code working good enough for a demo versus having a complete understanding of the feature in order to confidently propose a change and see it through. This one could take me a little while to get there.

RussKie commented 2 years ago

No worries at all. According to the animations you've made a terrific progress. Feel free to open a draft PR with a sample in our test harness project WinformsControlsTest, it could help in understanding the problem area better and promote discussions.

willibrandon commented 2 years ago

Will do. I'll start putting together a sample in the test harness.

So far the demos that I've shown come from sample code contained in Adam Root's series on shell style drag and drop. I didn't have to integrate the sample code into Winforms to make the demos work, which was nice since it didn't require me to know how everything works but I could still observe the drag image and drop description behavior and use cases.

Now that I've had some time to study, I'm ready to dive back in. I've started implementing the proof of concept and will open a draft PR when I finish or if I get stuck.

willibrandon commented 2 years ago

This is out of scope for this issue but I'm noting it here for future reference: Asynchronous drag and drop.

willibrandon commented 2 years ago

Test harness, using the IDropTargetHelper interface to display the drag image from Explorer.

https://user-images.githubusercontent.com/5017479/144701231-942d3b5c-9232-4ae0-bc77-e606511d3e39.mp4

willibrandon commented 2 years ago

It seems a little more within reach.

willibrandon commented 2 years ago

Test harness, using the IDragSourceHelper2 interface to initialize the drag image and the IDropTargetHelper interface to display the drag image. When the drag-and-drop helpers are used, they call IDataObject::SetData and IDataObject::GetData to load and retrieve private data formats for cross-process support. To facilitate this process, I use the default IDataObject implementation provided by SHCreateDataObject which has support for arbitrary data formats.

The new data object is intended to be used in operations such as drag-and-drop, in which the data is stored in the clipboard with a given format.

https://user-images.githubusercontent.com/5017479/144987285-b21186df-35ac-49c0-b86c-415875280461.mp4

willibrandon commented 2 years ago

I've observed the following data formats used by the drag-and-drop helpers:

Shell IDList Array DragImageBits DragContext DragSourceHelperFlags UsingDefaultDragImage DisableDragText DropDescription DragWindow IsComputingImage IsShowingLayered IsShowingText UntrustedDragDrop

RussKie commented 2 years ago

This is really cool!

willibrandon commented 2 years ago

It's getting better. I discovered how to respond to data requests from Windows Explorer.

Dragging from Explorer to the application, and back. Somebody pinch me.

https://user-images.githubusercontent.com/5017479/145157013-6eb7a0b3-fdc5-45b3-bcbd-08a6eab03720.mp4

willibrandon commented 2 years ago

So far I've observed the following data formats used by Explorer:

Preferred DropEffect FileDrop UniformResourceLocator FileGroupDescriptorW TargetCLSIDfRelease TargetCLSID AsyncFlag Logical Performed DropEffect Performed DropEffect Shell IDList Array FileNameMapW FileOpFlags DropEffectFolderList

willibrandon commented 2 years ago

Setting UsingDefaultDragImage to true causes the drag-image manager to display the image like it would when dragged from Explorer, with a layered image background (DD_IMAGEBG) and size of 96 x 96.

UsingDefaultDragImage (true):

UsingDefaultDragImage_true

UsingDefaultDragImage (false):

UsingDefaultDragImage_false

Setting UsingDefaultDragImage to true makes the drag image look very good in my opinion. It also seems to be poorly documented territory.

willibrandon commented 2 years ago

As noted in DROPDESCRIPTION, some UI coloring is applied to the text in Insert if used by specifying %1 in Message.

Without UI coloring.

e.Message = "Copy cat from Explorer";
e.Insert = string.Empty;

DropDescription_no_color

With UI coloring.

e.Message = "Copy cat from %1";
e.Insert = "Explorer";

DropDescription_color

A double percent character %% format marker can be used to output a single percent sign when applying coloring to a drop description message.

e.Message = "Copy cat from %1 100%%";
e.Insert = "Explorer";

DropDescription_color_singlepercentoutput

merriemcgaw commented 2 years ago

We probably want to consider colors of the drag text when the user is using High Contrast. But this is super exciting to see your progress @willibrandon!! I'm very excited.

willibrandon commented 2 years ago

Thanks @merriemcgaw that is super motivating to hear that. It has been a true test in patience and understanding.

I've just recently got the drag images and drop descriptions working with the RichTextBox.

https://user-images.githubusercontent.com/5017479/146505508-4d4b47bf-6d9c-4ce6-8a2f-6f1ba2eb2363.mp4

willibrandon commented 2 years ago

Dragging multiple files from Explorer causes the drag-image manager to display a multi-file layered image DD_IMAGEBG with the number of files displayed in the middle DD_TEXTBG.

DragDropMultiFile_FromExplorer

willibrandon commented 2 years ago

We probably want to consider colors of the drag text when the user is using High Contrast.

Drag text without UI coloring in the Aquatic high contrast theme.

e.Message = "Copy cat from Explorer";
e.Insert = string.Empty;

DragText_WithoutUIColoring_HighContrast

Drag text with UI coloring in high contrast. The word "Explorer" has coloring applied.

e.Message = "Copy cat from %1 100%%";
e.Insert = "Explorer";

DragText_WithColoring_HighContrast

Here's an example of the drag image in high contrast when UsingDefaultDragImage is set to false, which removes the DD_IMAGEBG and seems to also remove the image alpha-blending.

DragFromExplorer_HighContrast_DefaultDragImage_false_409x232

Multi-file drag from Explorer in high contrast when UsingDefaultDragImage is set to true.

DragFromExplorer_MultipleFiles_HighContrast_DefaultDragImage_true_409x232

Multi-file drag from Explorer in high contrast when UsingDefaultDragImage is set to false.

DragFromExplorer_MultipleFiles_HighContrast_DefaultDragImage_false_409x232

willibrandon commented 2 years ago

@merriemcgaw - This is a good example of why this could take me a while. High contrast themes wasn't something I was aware of.

willibrandon commented 2 years ago

I'm taking a look at the ToolStripItem next as it has its own DropTarget/DropSource implementation. Please bear with me.

Also I'm starting to think about what automated unit and integration tests could and should look like. Drag-and-drop doesn't seem to be a well tested area at the moment. I only see 2 Control tests and 5 ToolStripItem tests but they are skipped right now due to https://github.com/dotnet/winforms/issues/3336 .

RussKie commented 2 years ago

Have a look at the UIIntegration tests, those use InputSimulator to control the mouse and the keyboard.

willibrandon commented 2 years ago

Have a look at the UIIntegration tests, those use InputSimulator to control the mouse and the keyboard.

@RussKie - Amazing! The mouse drag approach combined with calling DoDragDrop from MouseDown definitely alleviates the issue described in https://github.com/dotnet/winforms/issues/3336 and allows the system to carry on and call IDropTarget::DragEnter when the cursor passes over a drop area. Time to write some tests.

Thank you very much. I could have spent a lot of time looking for that. This should allow us to test every nook and cranny.

willibrandon commented 2 years ago

As noted in DROPDESCRIPTION and DROPIMAGETYPE, a drop image type can be specified which allows for new and improved drop icons to be used.

DROPIMAGE_INVALID

No drop image preference; use the default image.

    e.Effect = DragDropEffects.None;
    e.DropIcon = DropIconType.Invalid;

DropImageType Invalid

DROPIMAGE_NONE (DD_NONE)

A red bisected circle such as that found on a "no smoking" sign.

    e.Effect = DragDropEffects.None;
    e.DropIcon = DropIconType.None;

DropImageType None

DROPIMAGE_COPY (DD_COPY)

A plus sign (+) that indicates a copy operation.

    e.Effect = DragDropEffects.Copy;
    e.DropIcon = DropIconType.Copy;

DropImageType Copy

DROPIMAGE_MOVE (DD_MOVE)

An arrow that indicates a move operation.

    e.Effect = DragDropEffects.Move;
    e.DropIcon = DropIconType.Move;

DropImageType Move

DROPIMAGE_LINK (DD_CREATELINK)

An arrow that indicates a link.

    e.Effect = DragDropEffects.Link;
    e.DropIcon = DropIconType.Link;

DropImageType Link

DROPIMAGE_LABEL (DD_UPDATEMETADATA)

A tag icon that indicates that the metadata will be changed.

    e.Effect = DragDropEffects.Move;
    e.DropIcon = DropIconType.Label;

DropImageType Label

DROPIMAGE_WARNING (DD_WARNING)

A yellow exclamation mark that indicates that a problem has been encountered in the operation.

    e.Effect = DragDropEffects.Move;
    e.DropIcon = DropIconType.Warning;

DropImageType Warning

DROPIMAGE_NOIMAGE

Windows 7 and later. Use no drop image.

    e.Effect = DragDropEffects.Move;
    e.DropIcon = DropIconType.NoImage;

DropImageType NoImage

willibrandon commented 2 years ago

This is starting to look really good and the drag images and drop descriptions result in a very nice drag-and-drop experience. It makes drag-and-drop more discoverable and easier to use.

I can't help but wonder why it doesn't already exist in Winforms?

willibrandon commented 2 years ago

I can imagine drag images and drop descriptions being implemented in a few different ways and before I go too far in a certain direction, I suggest we start by first adding support for DropTargets to display the drag image (if one is available) and to specify a drop description. Its a little easier to achieve compared to adding support for DropSources, or more specifically, adding support to specify the drag image.

This is open to change but I'll start the API proposal by exposing the behavior through some additional DragEventArgs, allowing the application to specify a drop description during DragEnter by setting the DropIcon, Message, and Insert arguments. This seems like the least invasive approach and a good place to start.

I'll work on the proposal now and should have a draft ready to go soon, which should help promote the discussion. We can decide which direction to go from there.

RussKie commented 2 years ago

👍

willibrandon commented 2 years ago

I had been wondering if it was possible to extract the drag image itself from the global memory object "DragImageBits" format while in flight so that it could then be passed on to the application to do something cool with it, but I hadn't seen that mentioned in the documentation.

https://user-images.githubusercontent.com/5017479/148196220-b35c265c-2ca3-4f85-9acd-b908c96951a3.mp4

I thought initially I could just extract the drag image bitmap handle from the data object and voila, I have a bitmap. But I kept receiving generic GDI+ errors every time I tried to use the bitmap handle, presumably because it was in use. Fortunately the drag image RGBQUAD array is stored right after the drag image structure in global memory and can be extracted with some knowledge about bottom-up device-independent bitmaps. A good description and diagram illustrating bottom-up DIBs can be found in Top-Down vs. Botton-Up DIBs.

This opens up the possibility to not only specify the drag image, but retrieve it as well.