Top / Programming / C++Builder / エクスプローラにドラッグアンドドロップする

エクスプローラにドラッグアンドドロップする

エクスプローラへドラッグアンドドロップする方法を紹介します。

DoDragDrop

ドラッグアンドドロップは、ドラッグアンドドロップ元(ソース)からドラッグアンドドロップ先(ターゲット)へデータオブジェクト(IDataObject)を転送する手続きです。

ドラッグアンドドロップを行うには、OLEのDoDragDropを呼び出します。

HRESULT DoDragDrop(IDataObject* pDataObj, IDropSource* pDropSrc, 
                   DWORD dwOKEffect, DWORD* pdwEffect)

引数pDataObjは、転送用のデータオブジェクトを指定します。

引数pDropSrcは、操作中のユーザーへのフィードバックを返すドロップソースオブジェクトを指定します。

引数dwOKEffectは、ソースが認めるエフェクトです。以下の値の論理和(OR)を指定します。

引数pdwEffectは、DoDrawDropが制御を返したときの最後のエフェクトを受け取る出力引数です。

コードの例です。

OleInitialize(NULL);

IDataObject* dataObject = …;
TDropSource* source = …;
DWORD dropEffect;
HRESULT hr = DoDragDrop(dataObject, source, DROPEFFECT_COPY | DROPEFFECT_MOVE, &dropEffect);
if (dropEffect == DROPEFFECT_MOVE) {
  //移動したときは元データを削除
}

OleUninitialize();

IDropSource

ドロップソースオブジェクトは、IDropSourceを実装します。

IUnknownの仮想関数に加えて、QueryContinueDrag関数とGiveFeedback関数を実装します。

QueryContinueDrag関数は、ドラッグアンドドロップ処理を継続するか終了するかを返します。

HRESULT __stdcall QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)

引数fEscは、ESCキーの状態を示します。ESCが押されているときはTRUEになります。

引数grfKeyStateは、Ctrlキー・Altキー・Shiftキーの状態と、左・中・右マウスボタンの状態を示します。

QueryContinueDrag関数の実装例です。

HRESULT __stdcall QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
{
  // ESCが押されたか、両方のボタンが押されている場合は中止する
  if (fEsc ||
      (MK_LBUTTON | MK_RBUTTON) == (grfKeyState & (MK_LBUTTON | MK_RBUTTON))) {
    return DRAGDROP_S_CANCEL;
  }

  //マウスの左ボタンが離された場合はドロップ処理へ
  if ((grfKeyState & (MK_LBUTTON | MK_RBUTTON)) == 0) {
    return DRAGDROP_S_DROP;
  }

  //それ以外はD&D継続
  return S_OK;
}

GiveFeedback関数は、マウスカーソルの形状を設定し、ユーザーにフィードバックを提供します。

DRAGDROP_S_USEDEFAULTCURSORSを返すと、標準のマウスカーソルになります。

GiveFeedback関数の実装例です。

HRESULT __stdcall GiveFeedback(DWORD dwEffect)
{
  return DRAGDROP_S_USEDEFAULTCURSORS;
}

IDropSourceの実装例です。

class TDropSource : public IDropSource
{
public:
  TDropSource() : FRefCount(0) {
  }
  HRESULT __stdcall QueryInterface(REFIID riid, void** ppv)
  {
    IUnknown *punk = NULL;
    if (riid == IID_IUnknown) {
      punk = static_cast<IUnknown*>(this);
    } else if (riid == IID_IDropSource) {
      punk = static_cast<IDropSource*>(this);
    }

    *ppv = punk;
    if (punk) {
      punk->AddRef();
      return S_OK;
    } else {
      return E_NOINTERFACE;
    }
  }
  ULONG __stdcall AddRef()
  {
    InterlockedIncrement(&FRefCount);
    return (ULONG)FRefCount;
  }
  ULONG __stdcall Release()
  {
    ULONG ret = (ULONG)InterlockedDecrement(&FRefCount);
    if (ret == 0) {
      delete this;
    }
    return ret;
  }

  /**
   * ドラッグアンドドロップ処理を継続するか終了するかを返します。
   */
  HRESULT __stdcall QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
  {
    // ESCが押されたか、両方のボタンが押されている場合は中止する
    if (fEsc ||
        (MK_LBUTTON | MK_RBUTTON) == (grfKeyState & (MK_LBUTTON | MK_RBUTTON))) {
      return DRAGDROP_S_CANCEL;
    }

    //マウスの左ボタンが離された場合はドロップ処理へ
    if ((grfKeyState & (MK_LBUTTON | MK_RBUTTON)) == 0) {
      return DRAGDROP_S_DROP;
    }

    //それ以外はD&D継続
    return S_OK;
  }

  /**
   * マウスカーソルの形状を設定し、ユーザーにフィードバックを提供します。
   */
  HRESULT __stdcall GiveFeedback(DWORD dwEffect)
  {
    //デフォルトのカーソルを使う
    return DRAGDROP_S_USEDEFAULTCURSORS;
  }
private:
  LONG FRefCount;
};

IDataObject

データオブジェクトはIDataObjectを実装します。

ドラッグアンドドロップ処理の場合は、IShellFolder::GetUIObjectOfを使用して、 対象ファイルの項目識別子から、IDataObjectを得ます。

IDataObjectを取得する例です。

/**
 * ファイル名からIDataObjectインターフェイスを取得
 * @param FileName ファイル名
 * @return 失敗したときはNULL
 */
IDataObject* GetFilePathDataObject(const UnicodeString& FileName)
{
  //ファイル名をフォルダをファイル名に分けておく
  UnicodeString dir = ExtractFileDir(FileName);
  UnicodeString file = ExtractFileName(FileName);

  //デスクトップのIShellFolderインターフェイスを取得
  IShellFolder* desktop = NULL;
  if (FAILED(SHGetDesktopFolder(&desktop))) { return NULL; }

  //対象ファイルの親フォルダの項目識別子を得る
  ITEMIDLIST* dirIDList;
  DWORD eaten = 0, attributes = 0;
  if (FAILED(desktop->ParseDisplayName(NULL, NULL, dir.c_str(), &eaten,
                                       &dirIDList, &attributes))) {
    desktop->Release();
    return NULL;
  }

  //親フォルダのIShellFolderインターフェイスを取得
  IShellFolder* targetFolder;
  if (FAILED(desktop->BindToObject(dirIDList, NULL, IID_IShellFolder,
                                   (void**)&targetFolder))) {
    desktop->Release();
    return NULL;
  }

  //対象ファイルの項目識別子を得る
  LPITEMIDLIST filePidl;
  if (FAILED(targetFolder->ParseDisplayName(NULL, NULL, file.c_str(), &eaten,
                                            &filePidl, &attributes))) {
    targetFolder->Release();
    desktop->Release();
    return NULL;
  }

  //対象ファイルの項目識別子から、IDataObjectインターフェイスを得る
  IDataObject* dataObject = NULL;
  targetFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST*)&filePidl,
                              IID_IDataObject, NULL, (void**)&dataObject);
  targetFolder->Release();
  desktop->Release();
  return dataObject;
}

サンプルプログラム

サンプルは何もないフォーム上からエクスプローラにドラッグアンドドロップすることで、ファイルをコピーします。

Uni1.h

class TForm1 : public TForm
{
__published:    // IDE 管理のコンポーネント
  void __fastcall FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
          int X, int Y);
  void __fastcall FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y);
  void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
private:    // ユーザー宣言
  TPoint FMouseDownPt;
public:     // ユーザー宣言
  __fastcall TForm1(TComponent* Owner);
};

Unit1.cpp

// ---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
#include <shlobj.h>
// ---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
// ---------------------------------------------------------------------------
class TDropSource : public IDropSource
{
public:
  TDropSource() : FRefCount(0) {
  }
  HRESULT __stdcall QueryInterface(REFIID riid, void** ppv)
  {
    IUnknown *punk = NULL;
    if (riid == IID_IUnknown) {
      punk = static_cast<IUnknown*>(this);
    } else if (riid == IID_IDropSource) {
      punk = static_cast<IDropSource*>(this);
    }

    *ppv = punk;
    if (punk) {
      punk->AddRef();
      return S_OK;
    } else {
      return E_NOINTERFACE;
    }
  }
  ULONG __stdcall AddRef()
  {
    InterlockedIncrement(&FRefCount);
    return (ULONG)FRefCount;
  }
  ULONG __stdcall Release()
  {
    ULONG ret = (ULONG)InterlockedDecrement(&FRefCount);
    if (ret == 0) {
      delete this;
    }
    return ret;
  }

  /**
   * ドラッグアンドドロップ処理を継続するか終了するかを返します。
   */
  HRESULT __stdcall QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
  {
    // ESCが押されたか、両方のボタンが押されている場合は中止する
    if (fEsc ||
        (MK_LBUTTON | MK_RBUTTON) == (grfKeyState & (MK_LBUTTON | MK_RBUTTON))) {
      return DRAGDROP_S_CANCEL;
    }

    //マウスの左ボタンが離された場合はドロップ処理へ
    if ((grfKeyState & (MK_LBUTTON | MK_RBUTTON)) == 0) {
      return DRAGDROP_S_DROP;
    }

    //それ以外はD&D継続
    return S_OK;
  }

  /**
   * マウスカーソルの形状を設定し、ユーザーにフィードバックを提供します。
   */
  HRESULT __stdcall GiveFeedback(DWORD dwEffect)
  {
    //デフォルトのカーソルを使う
    return DRAGDROP_S_USEDEFAULTCURSORS;
  }
private:
  LONG FRefCount;
};
// ---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
  OleInitialize(NULL);
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
  OleUninitialize();
}
// ---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift,
          int X, int Y)
{
  if (Button == mbLeft) {
    FMouseDownPt = TPoint(X, Y);
  }
}
//---------------------------------------------------------------------------
/**
 * ファイル名からIDataObjectインターフェイスを取得
 * @param FileName ファイル名
 * @return 失敗したときはNULL
 */
IDataObject* GetFilePathDataObject(const UnicodeString& FileName)
{
  //ファイル名をフォルダをファイル名に分けておく
  UnicodeString dir = ExtractFileDir(FileName);
  UnicodeString file = ExtractFileName(FileName);

  //デスクトップのIShellFolderインターフェイスを取得
  IShellFolder* desktop = NULL;
  if (FAILED(SHGetDesktopFolder(&desktop))) { return NULL; }

  //対象ファイルの親フォルダの項目識別子を得る
  ITEMIDLIST* dirIDList;
  DWORD eaten = 0, attributes = 0;
  if (FAILED(desktop->ParseDisplayName(NULL, NULL, dir.c_str(), &eaten,
                                       &dirIDList, &attributes))) {
    desktop->Release();
    return NULL;
  }

  //親フォルダのIShellFolderインターフェイスを取得
  IShellFolder* targetFolder;
  if (FAILED(desktop->BindToObject(dirIDList, NULL, IID_IShellFolder,
                                   (void**)&targetFolder))) {
    desktop->Release();
    return NULL;
  }

  //対象ファイルの項目識別子を得る
  LPITEMIDLIST filePidl;
  if (FAILED(targetFolder->ParseDisplayName(NULL, NULL, file.c_str(), &eaten,
                                            &filePidl, &attributes))) {
    targetFolder->Release();
    desktop->Release();
    return NULL;
  }

  //対象ファイルの項目識別子から、IDataObjectインターフェイスを得る
  IDataObject* dataObject = NULL;
  targetFolder->GetUIObjectOf(NULL, 1, (LPCITEMIDLIST*)&filePidl,
                              IID_IDataObject, NULL, (void**)&dataObject);
  targetFolder->Release();
  desktop->Release();
  return dataObject;
}
void __fastcall TForm1::FormMouseMove(TObject *Sender, TShiftState Shift, int X, int Y)
{
  //左ボタンが押された状態で、マウスの位置が閾値を超えたらドラッグ開始
  if (ControlState.Contains(csLButtonDown ) == false ||
      ((abs(X - FMouseDownPt.x) < Mouse->DragThreshold)) ||
       (abs(Y - FMouseDownPt.y) < Mouse->DragThreshold)) {
    return;
  }

  Perform(WM_LBUTTONUP, 0, 0);

  // OLEドラッグ&ドロップ開始
  IDataObject* dataObject = GetFilePathDataObject(L"C:\sample\test.txt");
  if (dataObject == NULL) {
    return;
  }

  DWORD dropEffect;
  TDropSource* dropSource = new TDropSource();
  dropSource->AddRef();
  DoDragDrop(dataObject, dropSource, DROPEFFECT_COPY, &dropEffect);
  dataObject->Release();
  dropSource->Release();
}

関連

更新履歴