C言語の API で File List を Drag & Drop する



C言語(C++以前)の API を使って、Windows エクスプローラと相互に File List を Drag & Drop します。
クリップボードの扱い方が Copy & Paste と同じなので「Clipboard のプログラム」と合わせて参照して下さい。
このプログラムの作成にあったて Tomoaki Nakashima 氏のホームページ のお世話になりました。

前田稔(Maeda Minoru)の超初心者のプログラム入門

プロジェクトの設定

  1. 新規プロジェクト(DragDrop)で空のプロジェクトを作成して下さい。
  2. ページ先頭の画面を参考にして DialogBox に[ListBox]を配置して下さい。
    IDを次のように設定して下さい。
    複数の項目を選択するので、ListBox のプロパティから「マルチ」を設定して下さい。
    BOX ID
    DialogBox IDD_DIALOG_MAIN
    ListBox IDC_LIST_FILE
  3. [ビルド]から[実行]を選択するとビルドに続いて、エラーが無ければプログラムが実行されます。
    エクスプローラで幾つかのファイルを選択して、List Box にドラッグして下さい。
    ドラッグされたファイルリストが ListBox に追加されます。
    ただし ListBox に追加されるだけで、ファイルが実際にコピーされる訳ではありません。
  4. List Box にドラッグした幾つかのファイルを選択して、エクスプローラにドラッグするとリストに追加されます。
    こちらはファイルが実際にコピー(移動) されるので、操作したファイルを紛失しないように注意して下さい。

プログラムの説明

Drag&Drop のプログラムは OLE(Object Linking and Embedding) を使用します。
今回のプログラムではC言語ベースの OLE の API を使用しています。
  1. SHLOBJ.H を取り込んで下さい。
    プログラムの最初に OLE で使用する構造体と領域を定義します。
    "OleDragDrop.h" はCベースのヘッダーファイルなので、C++に合わすために必要なコードを抜き出しました。
    extern "C" は、C言語の関数をC++で使用するときの宣言です。
        #include    <windows.h>
        #include    <shlobj.h>
        #include    "resource.h"
        //#include    "OleDragDrop.h"
        #define IDROPTARGET_NOTIFY_DRAGENTER    0
        #define IDROPTARGET_NOTIFY_DRAGOVER     1
        #define IDROPTARGET_NOTIFY_DRAGLEAVE    2
        #define IDROPTARGET_NOTIFY_DROP         3
    
        typedef struct _IDROPTARGET_NOTIFY
        {   POINTL *ppt;                    //マウスの位置
            DWORD dwEffect;                 //ドラッグ操作で、ドラッグされる対象で許される効果
            DWORD grfKeyState;              //キーの状態
            UINT cfFormat;                  //ドロップされるデータのクリップボードフォーマット
            HANDLE hMem;                    //ドロップされるデータ
            LPVOID pdo;                     //IDataObject
        } IDROPTARGET_NOTIFY , *LPIDROPTARGET_NOTIFY;
        extern  "C" /* Assume C declarations for C++ */
        {   BOOL APIPRIVATE OLE_IDropTarget_RegisterDragDrop(HWND hWnd, UINT uCallbackMessage, UINT *cFormat, int cfcnt);
            void APIPRIVATE OLE_IDropTarget_RevokeDragDrop(HWND hWnd);
            int APIPRIVATE OLE_IDropSource_Start(HWND hWnd, UINT uCallbackMessage, UINT *ClipFormtList, int cfcnt, int Effect);
        }
    
        #define WM_DRAGDROP     (WM_APP + 100)
        #define WM_GETDATA      (WM_APP + 101)
        
  2. 関数の Prototype 宣言と WinMain() です。
    WinMain() では OLE を初期化して DialogBox を表示します。
        // Function Prototype
        BOOL CALLBACK MainProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
        static HDROP APIPRIVATE CreateDropFileMem(char **FileName,int cnt,BOOL fWide);
    
        //☆ WinMain
        int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
        {
            // OLEの初期化を行います
            if (OleInitialize(NULL) != S_OK)
            {   MessageBox(NULL,"OLEの初期化に失敗しました。","Error",MB_OK | MB_ICONERROR);
                return 0;
            }
            DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG_MAIN),NULL,MainProc);
            OleUninitialize();
            return 0;
        }
        
  3. WM_INITDIALOG: でドラッグターゲットの登録します。
    現在は cf[1] で一個しか登録していませんが、種類の異なる複数の形式を登録することも出来ます。
        //☆ Dialog CALLBACK
        BOOL CALLBACK MainProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
        {   LPIDROPTARGET_NOTIFY pdtn;
            UINT    cf[1];
            POINT   pt;
            HWND    mWnd;
            char    buf[MAX_PATH];
            int     i,j,len;
            static UINT gcf;
    
            switch (uMsg)
            {   case WM_INITDIALOG:
                    // ドラッグターゲットの登録
                    // CF_XXXX を指定するか、RegisterClipboardFormat() で登録した独自形式を指定します。
                    // 複数指定することもでき、配列の要素の先頭から優先順位順になっています。
                    //cf[0] = CF_TEXT;    // テキスト
                    cf[0] = CF_HDROP;   // ファイル
                    if (OLE_IDropTarget_RegisterDragDrop(hDlg, WM_DRAGDROP,cf,1) == FALSE)
                    {   MessageBox(hDlg,"ドロップターゲットの登録に失敗しました。","Error",MB_OK | MB_ICONERROR);
                        EndDialog(hDlg, FALSE);
                    }
                    SendMessage(GetDlgItem(hDlg,IDC_EDIT_DROPTEXT),WM_SETTEXT,0,(LPARAM)"ドラッグ&ドロップ サンプル");
                    break;
                case WM_CLOSE:
                    OLE_IDropTarget_RevokeDragDrop(hDlg);
                    EndDialog(hDlg, FALSE);
                    break;
        
  4. ドラッグ & ドロップの開始処理です。
    ListBox 上で左ボタンが押されているか、また幾つかのファイルが選択されているかを調べます。
    OLE_IDropSource_Start() でドラッグ & ドロップを開始します。
            case WM_SETCURSOR:
                if ((HWND)wParam == GetDlgItem(hDlg,IDC_LIST_FILE) && HIWORD(lParam) == WM_LBUTTONDOWN)
                {   if (SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_GETSELCOUNT,0,0) == 0)
                    {   return(FALSE);  }
                    // ドラッグ&ドロップの開始
                    cf[0] = CF_HDROP;
                    if (OLE_IDropSource_Start(hDlg,WM_GETDATA,cf,1,DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK) == DROPEFFECT_MOVE)
                    {   // DROPEFFECT_MOVE の場合リストボックスの選択されている項目を削除する。
                        for(i = SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_GETCOUNT,0,0);i >= 0 ;i--)
                        {   if (SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_GETSEL,i,0) == 0)
                            {   continue;   }
                            SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_DELETESTRING,i,0);
                        }
                    }
                }
                else    {   return(FALSE);  }
                GetCursorPos(&pt);
                PostMessage((HWND)wParam,WM_LBUTTONUP,0,MAKELPARAM(pt.x,pt.y));
                break;
        
  5. ドラッグ & ドロップの操作処理です。
    IDROPTARGET_NOTIFY_DROP: でドロップされたときの扱いは、基本的にクリップボードの操作と同じです。
            case WM_DRAGDROP:
                pdtn = (LPIDROPTARGET_NOTIFY)lParam;
                switch(wParam)
                {   case IDROPTARGET_NOTIFY_DRAGENTER:  // マウスがウィンドウに入ってきたとき
                        gcf = pdtn->cfFormat;
                    case IDROPTARGET_NOTIFY_DRAGOVER:   // マウスがウィンドウ内で移動したとき
                        pt.x = pdtn->ppt->x;
                        pt.y = pdtn->ppt->y;
                        mWnd = WindowFromPoint(pt);
                        if (gcf == CF_HDROP && mWnd == GetDlgItem(hDlg,IDC_LIST_FILE))
                        {   // クリップボードフォーマットが CF_HDROP で、マウスがリストボックスの上にいるとき
                            pdtn->dwEffect = DROPEFFECT_COPY;
                        }
                        else
                        {   pdtn->dwEffect = DROPEFFECT_NONE;   }
                        break;
                    case IDROPTARGET_NOTIFY_DRAGLEAVE:  // マウスがウィンドウから出たとき
                        break;
                    case IDROPTARGET_NOTIFY_DROP:       // ドロップされたとき
                        // ドロップされたデータ (pdtn->hMem) の扱いは基本的にクリップボードの操作と同じです。
                        switch(pdtn->cfFormat)
                        {   case CF_HDROP:          // ファイル
                                SetForegroundWindow(hDlg);
                                for(i = 0;i < (int)DragQueryFile((HDROP)pdtn->hMem,0xFFFFFFFF,NULL,0);i++)
                                {   DragQueryFile((HDROP)pdtn->hMem,i,buf,MAX_PATH-1);
                                    if(SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_FINDSTRING,(WPARAM)-1,(LPARAM)buf) < 0)
                                    {   SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_ADDSTRING,0,(LPARAM)buf); }
                                }
                                break;
                            default:
                                break;
                        }
                        break;
                }
                break;
        
  6. ドロップするデータを設定する処理です。
    ドロップするデータは基本的にクリップボードの操作と同じです。
            case WM_GETDATA:
                // ドロップするデータは基本的にクリップボードの操作と同じです。
                switch(wParam)
                {   case CF_HDROP:      // ファイル
                        {   OSVERSIONINFO os_info;
                            BOOL NTFlag = FALSE;
                            char **FileNameList;
                            // ファイル名の配列を作成する
                            FileNameList = (char **)GlobalAlloc(GPTR,
                                sizeof(char *)*SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_GETSELCOUNT,0,0));
                            if (FileNameList == NULL)   {   abort();    }
                            j = 0;
                            for(i = 0;i < SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_GETCOUNT,0,0);i++)
                            {   if (SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_GETSEL,i,0) == 0)
                                {   continue;   }
                                len = SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_GETTEXTLEN,i,0);
                                FileNameList[j] = (char *)GlobalAlloc(GPTR,len + 1);
                                if(FileNameList[j] == NULL)     {   abort();    }
                                SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_GETTEXT,i,(LPARAM)FileNameList[j]);
                                j++;
                            }
                            os_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
                            GetVersionEx(&os_info);
                            if (os_info.dwPlatformId == VER_PLATFORM_WIN32_NT)
                            {   NTFlag = TRUE;  }
                            // ドロップファイルリストの作成
                            // NTの場合はUNICODEになるようにする
                            *((HANDLE *)lParam) = CreateDropFileMem(FileNameList,
                                SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_GETSELCOUNT,0,0),NTFlag);
                            // ファイル名の配列を解放する
                            for(i = 0;i < SendMessage(GetDlgItem(hDlg,IDC_LIST_FILE),LB_GETSELCOUNT,0,0);i++)
                            {   GlobalFree(FileNameList[i]);    }
                            GlobalFree(FileNameList);
                        }
                        break;
        
  7. ドロップファイルの作成処理です。
    基本的にクリップボードの操作と同じです。
        //☆ ドロップファイルの作成
        static HDROP APIPRIVATE CreateDropFileMem(char **FileName,int cnt,BOOL fWide)
        {   LPDROPFILES lpDropFile;
            HDROP   hDrop;
            wchar_t wbuf[MAX_PATH];
            int     flen = 0;
            int     i;
        
            if (fWide == TRUE)
            {   // ワイドキャラ
                for(i = 0;i < cnt;i++)
                {   MultiByteToWideChar(CP_ACP,0,FileName[i],-1,wbuf,MAX_PATH);
                    flen += (wcslen(wbuf) + 1) * sizeof(wchar_t);
                }
                flen++;
            }
            else
            {   // マルチバイト
                for(i = 0;i < cnt;i++)
                {   flen += lstrlen(FileName[i]) + 1;   }
            }
            hDrop = (HDROP)GlobalAlloc(GHND,sizeof(DROPFILES) + flen + 1);
            if (hDrop == NULL)  {   return NULL;    }
            lpDropFile = (LPDROPFILES) GlobalLock(hDrop);
            lpDropFile->pFiles = sizeof(DROPFILES);     // ファイル名のリストまでのオフセット
            lpDropFile->pt.x = 0;
            lpDropFile->pt.y = 0;
            lpDropFile->fNC = FALSE;
            lpDropFile->fWide = fWide;                  // ワイドキャラの場合は TRUE
            // 構造体の後ろにファイル名のリストをコピーする。(ファイル名\0ファイル名\0ファイル名\0\0)
            if (fWide == TRUE)
            {   // ワイドキャラ
                wchar_t *buf;
                buf = (wchar_t *)(&lpDropFile[1]);
                for(i = 0;i < cnt;i++)
                {   MultiByteToWideChar(CP_ACP,0,FileName[i],-1,wbuf,MAX_PATH);
                    wcscpy(buf,wbuf);
                    buf += wcslen(wbuf) + 1;
                }
            }
            else
            {   // マルチバイト
                char *buf;
                buf = (char *)(&lpDropFile[1]);
                for(i = 0;i < cnt;i++)
                {   lstrcpy(buf,FileName[i]);
                    buf += lstrlen(FileName[i]) + 1;
                }
            }
            GlobalUnlock(hDrop);
            return(hDrop);
        }
        

【演習】

課題1

エクスプローラと相互に Drag & Drop を実行してみて下さい。

課題2

エクスプローラからクリップボードに Drop したファイル名を取得して、実際にファイルを格納してみて下さい。

超初心者のプログラム入門(Win32API C++)