Using Explorer’s fancy drag/drop effects in your own programs

Raymond Chen

Raymond

The default drag/drop effect is very plain. Let’s try it out. Take the scratch program and make these changes. The COM smart pointer library for today is (rolls dice) C++/WinRT. I will also be using wil for error handling and RAII types.

#include <winrt/base.h>
#include <wil/result_macros.h>
#include <wil/resource.h>

struct SimpleDropTarget :
    winrt::implements<SimpleDropTarget, IDropTarget>
{
    SimpleDropTarget(HWND hwnd) : hwndOwner(hwnd)
    {
    }

    STDMETHODIMP DragEnter(IDataObject* pDataObject,
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        dto.copy_from(pDataObject);
        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        return S_OK;
    }
    STDMETHODIMP DragOver(
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        return S_OK;
    }

    STDMETHODIMP Drop(IDataObject* pDataObject,
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        dto.copy_from(pDataObject);
        auto cleanup = wil::scope_exit([&] { DragLeave(); });

        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        if (*pdwEffect != DROPEFFECT_NONE) {
            // Do something cool.
        }
        return S_OK;
    }

    STDMETHODIMP DragLeave()
    {
        dto = nullptr;
        return S_OK;
    }

private:
    HWND hwndOwner;
    winrt::com_ptr<IDataObject> dto;

    HRESULT CalculateFeedback(
        DWORD grfKeyState,
        DWORD* pdwEffect)
    {
        if (grfKeyState & MK_CONTROL) {
            *pdwEffect = DROPEFFECT_COPY;
        } else {
            *pdwEffect = DROPEFFECT_MOVE;
        }
        return S_OK;
    }
};

This drop target does nothing particularly fancy. The only special thing is that holding the Ctrl key provides copy feedback rather than move feedback.

The Drop method processes the new data object and either performs the drop or defers to the Drag­Leave feedback if it turns out nothing is being dropped after all. Regardless of how the drop plays out, we call our own Drag­Leave() to clean up. (We use the wil::scope_exit function to create a one-off RAII type that performs some action at destruction.)

Let’s hook up this drop target to our window.

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
    RegisterDragDrop(hwnd,
        winrt::make<SimpleDropTarget>(hwnd).get());

    return TRUE;
}

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
    LPSTR lpCmdLine, int nShowCmd)
{
    ...
    if (SUCCEEDED(OleInitialize(NULL))) {
        ...
    }
    ...
}

In the main program, we use OleInitialize instead of CoInitialize because we are using OLE drag/drop, which is an OLE feature.

And when the window is created, we register our drop target.

When you run this program, the drag/drop feedback is just an empty gray box, possibly with a + sign to indicate that the resulting operation is a copy.

Let’s add fancy Explorer-style drag/drop feedback.

struct SimpleDropTarget :
    winrt::implements<SimpleDropTarget, IDropTarget>
{
    SimpleDropTarget(HWND hwnd) : hwndOwner(hwnd),
        helper(winrt::create_instance<IDropTargetHelper>(
            CLSID_DragDropHelper))
    {
    }

    STDMETHODIMP DragEnter(IDataObject* pDataObject,
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        dto.copy_from(pDataObject);
        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        POINT point{ pt.x, pt.y };
        RETURN_IF_FAILED(helper->DragEnter(hwndOwner, dto.get(),
            &point, *pdwEffect));
        return S_OK;
    }

    STDMETHODIMP DragOver(
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        POINT point{ pt.x, pt.y };
        RETURN_IF_FAILED(helper->DragOver(&point, *pdwEffect));
        return S_OK;
    }

    STDMETHODIMP Drop(IDataObject* pDataObject,
        DWORD grfKeyState, POINTL pt, DWORD* pdwEffect)
    {
        dto.copy_from(pDataObject);
        auto cleanup = wil::scope_exit([&] { DragLeave(); });

        RETURN_IF_FAILED(CalculateFeedback(grfKeyState, pdwEffect));
        POINT point{ pt.x, pt.y };
        RETURN_IF_FAILED(helper->Drop(dto.get(), &point, *pdwEffect));
        if (*pdwEffect != DROPEFFECT_NONE) {
            // Do something cool.
        }
        return S_OK;
    }

    STDMETHODIMP DragLeave()
    {
        dto = nullptr;
        RETURN_IF_FAILED(helper->DragLeave());
        return S_OK;
    }

private:
    HWND hwndOwner;
    winrt::com_ptr<IDataObject> dto;
    winrt::com_ptr<IDropTargetHelper> helper;

    HRESULT CalculateFeedback(
        DWORD grfKeyState,
        DWORD* pdwEffect)
    {
        if (grfKeyState & MK_CONTROL) {
            *pdwEffect = DROPEFFECT_COPY;
        } else {
            *pdwEffect = DROPEFFECT_MOVE;
        }
        return S_OK;
    }
};

We create a shell drop target helper and forward all of our drop target methods into it. There is a bit of frustration here due to the impedance mismatch between the way IDropTarget and IDropTargetHelper represent the point.

With these changes, the drop feedback is much more Explorer-like: The drag image matches the Explorer drag image, showing a thumbnail of the item being dragged, or a collection of items with a numeric badge showing how many items are being dragged. There’s also an information box below the image that says what the resulting operation will be.

That’s a good start. Next time, we’ll add another feature.

4 comments

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

  • Avatar
    Paulo Pinto

    I would be curious if the end result can be made to match the productivity that can be achieved using C++/CX or C++ Builder instead, regarding the amount of required boilerplate code to deal with COM.

  • César Angel Martínez Muro
    César Angel Martínez Muro

    Why does Explorer use the term KB instead of KiB? (June 11th, 2009)
    We don´t use it because anybody use it, what a mediocre excuse.
    The purpose is not feeling superior, is to use the correct term, to teach or educate people.

    I will give you an example of why its a bad ide to ignore wrong usage of information:
    In one job training I had some years ago, the trainer took us a math exam, and in 2 excercises the correct option wasn´t there (like this 5-4×3+8/6), I made an observation to the trainer about my recent discovery, emphasizing that in Math, there´s a law called Precedence, which dictates that multiplication and division have higher priority to the addition and substraction operations, and what i got as an answer??, “THIS IS AN ”’EASY”’ EXAM, MATH DOESN´T MATTER HERE”, ¿¿wait, what??, there is nothing in the world that invalidates math, no excuse is enough to invalidate math, that´s an excuse for mediocre people, and for me, your “anybody uses” and “superiority” arguments, are practically the same.

    And after all, I´ll give you an excuse:
    Today, Apple and Linux uses the binary units, so, why Windows, 12 YEARS AFTER YOUR ORIGINAL COMMENT!!, has not implemented it yet?

    Yes, I looked for another recent blog of yours just to reply the oldest one, I just can´t stop the urge to comment my concerns.
    I don´t know you, and maybe you´re a great developer and person, but I can´t bear this kind of “ARGUMENTS”.
    Regards from México.

    • Avatar
      M. W.

      It’s for backwards-compatibility, and because of tradition and convention. Take your pick. Binary units came around much later to differentiate things to stop non-tech people from being confused as to why their storage has less space than advertised or why their network connections were slower than promised. Tech people have been using and understanding the context for decades without problem (incidentally, even a “byte” wasn’t eight bits for a while, it has had various values, in fact, IBM originally defined it as seven bytes).

      Also,when binary units were standardized, storage and screen space were still at a premium, so putting that extra ‘i’ in every unit was too costly to bother when most people got by just fine without it (again, tech people knew what it meant). It’s the same reason that two-digit years were being used, leading to the Y2K bug.

      (That math problem smacks of “viral math problems” that are another example of why social-media was a mistake. 😒)

  • Avatar
    Not Sure

    But there are easier helpers DAD_DragEnterEx2, DAD_DragMove, DAD_DragLeave