Drag/drop effects: The little drop information box

Raymond Chen

Raymond

Explorer’s drag/drop effects include a little message information box as part of the drop effect. For example, it will start with the message Move to ⸢folder⸥, but if you hold the Ctrl key, the message changes to Copy to ⸢folder⸥. How can you get a piece of that action?

As we saw last time, the visual drag/drop feedback effects used by Explorer are available for general use, via an object know as the shell drop target helper.

Let’s take our scratch program and teach it how to add the fancy information box.

The information box is configured by the DROP­DESCRIPTION structure which is associated with the CFSTR_DROP­DESCRIPTION clipboard format.

#include <strsafe.h>

HRESULT SetDropDescription(
    IDataObject* dataObject, DROPIMAGETYPE type,
    PCWSTR message = nullptr, PCWSTR insert = nullptr)
{
    wil::unique_hglobal_ptr<DROPDESCRIPTION> data(
        reinterpret_cast<DROPDESCRIPTION*>(
            GlobalAlloc(GPTR, sizeof(DROPDESCRIPTION))));
    RETURN_IF_NULL_ALLOC(data);
    data->type = type;
    RETURN_IF_FAILED(StringCchCopyEx(data->szMessage, ARRAYSIZE(data->szMessage),
        message, nullptr, nullptr, STRSAFE_IGNORE_NULLS));
    RETURN_IF_FAILED(StringCchCopyEx(data->szInsert, ARRAYSIZE(data->szInsert),
        insert, nullptr, nullptr, STRSAFE_IGNORE_NULLS));
    FORMATETC fmte = { (CLIPFORMAT)RegisterClipboardFormat(CFSTR_DROPDESCRIPTION),
        nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
    STGMEDIUM med = { TYMED_HGLOBAL };
    med.hGlobal = data.get();
    RETURN_IF_FAILED(dataObject->SetData(&fmte, &med, true));
    data.release();
    return S_OK;
}

We allocate a DROP­DESCRIPTION structure as an HGLOBAL and copy the drop image type, optional message, and optional message insertion into the structure before setting the data into the data object.

You can read the documentation for DROP­DESCRIPTION for details on how the message and message insertion interact. For this example, we’ll just use a simple message with no insertions.

struct SimpleDropTarget
{
    ...

    STDMETHOD DragLeave()
    {
        if (dto) SetDropDescription(dto.get(), DROPIMAGE_INVALID);
        dto = nullptr;
        return helper->DragLeave();
    }


    HRESULT CalculateFeedback(
        DWORD grfKeyState,
        DWORD* pdwEffect)
    {
        if (grfKeyState & MK_CONTROL) {
            *pdwEffect = DROPEFFECT_COPY;
            SetDropDescription(dto.get(), DROPIMAGE_COPY,
                L"Copy with awesomesauce.");
        }
        else {
            *pdwEffect = DROPEFFECT_MOVE;
            SetDropDescription(dto.get(), DROPIMAGE_MOVE,
                L"Move with awesomesauce.");
        }
        return S_OK;
    }

};

When we calculate the feedback, we also set the drop description to tell the user what will happen when the drop happens. We also clear the drop description (by setting it to “invalid” with an empty message)¹ when the user decided not to drop it onto our drop target after all. This ensures that our custom description doesn’t hang around and accidentally get shown for other drop targets that don’t set a description.

So there you have it, drag feedback done the same way that Explorer does it.

¹ There is no way to remove data from a data object, so the best you can do is replace the data with new data that says “Nevermind.”

0 comments

Comments are closed. Login to edit/delete your existing comments

  • Stuart Ballard
    Stuart Ballard

    Does that mean it’s up to every application to associate semantics with particular modifier keys – they have to ask “is the ctrl key pressed?” and know the convention that ctrl means copy, rather than being able to ask the framework “is this a copy operation?”

    That would seem to pose some interesting future-compatibility challenges: if the touchscreen UI wanted to adopt a convention that two-finger drag means copy[1], or a Minority Report style VR gesture-based UI wanted to base it on clenched fist vs open palm, would the OS need to fake the ctrl key being pressed in those situations, or would legacy apps just be unable to detect the new convention without a code update?

    [1] I presume that something like this actually had to be addressed when Windows got touch support, but I’ve never used a touch-based Windows device so I don’t know what convention is actually used, if any.

    • Avatar
      Andrew Cook

      The touch convention is that immediate drag is treated as left-click drag and tap-and-hold then drag is treated as right-click drag. Standard keyboard modifiers apply. If you’re on a device without a keyboard, you’re not out of luck, since all of the options available with modifier keys are typically (and required by the Windows UI design guidelines) also available on the context menu after a right-click drag.

      Not every application has the same verbs for a drag-drop operation — “move” and “copy” might be nonsensical in context — so of course it should be up to every application to decide what combination of modifiers from different sources is best for them. As a convenience, Explorer provides additional UI affordances for “add to” kinds of verbs, and because in Explorer that’s used for copy operations they call that additional UI affordance DROPIMAGE_COPY.

    • Avatar
      Chris Iverson

      Yes. The documentation for CFSTR_DROPDESCRIPTION specifically defines the data structure as being an HGLOBAL, which needs to come from GlobalAlloc.

      More specifically, the STGMEDIUM(storage medium) parameter is defined to hold TYMED_HGLOBAL, which the TYMED doc lists as

      “The storage medium is a global memory handle (HGLOBAL). Allocate the global handle with the GMEM_MOVEABLE flag. If the punkForRelease member of STGMEDIUM is NULL, the destination process should use GlobalFree to release the memory.”

      • Avatar
        紅樓鍮

        I’m tentative about whether that requirement is actually enforced in Windows NT, because in the given code snippet GPTR (which implies GMEM_FIXED) is passed to GlobalAlloc, whereas TYMED_HGLOBAL accordingly requires GMEM_MOVEABLE. GMEM_FIXED causes GlobalAlloc to return a pointer, whereas GMEM_MOVEABLE causes it to return an opaque handle, so if TYMED_HGLOBAL does work the way it is described, the given code should not work because the incorrect type of argument is passed.

        • skSdnW
          skSdnW

          GlobalAlloc is absolutely required and the example code is correct. Think about what the data object is going to do; GlobalLock, use data, GlobalUnlock, GlobalFree. The trick here is that GlobalLock on a fixed HGLOBAL is legal.

          The bug potential is in custom data objects. If they forget to call Lock they will seem to work fine when given fixed memory but break when given movable memory.

          See also: Search this blog for GlobalAlloc to find the series on the Global/Local memory and their history.

  • Avatar
    M. W.

    Does each application get its own DnD instance or is there a global shared one? I’ve seen it get stuck on numerous occasions where for example, there is a ghost image of a file or folder stuck on the desktop and drag-and-drop no longer works and I have to jump through hoops to try to get Explorer to let me drag something again (which usually doesn’t work at all until I restart it or reboot). I’ve also seen many occasions where Explorer simply won’t updating to reflect changes when files and folders are moved or even where the cut/copy/paste function no longer works and files and folders can’t be copied or moved. I can’t help but wonder if they’re all related. 🤔 If there’s a global feature, then it might be a bad program that messed it up (I recall a long time ago, doing a job-application online that included a piece of Flash in the application page that would rapidly clear the clipboard in a tight loop to prevent copying and pasting for whatever reason, which affected the whole system—I was furious that it could have made me lose data if I had copied something big to the clipboard and it took the liberty of clearing it behind my back without warning), so it’s certainly possible for programs to affect the whole system.