C++ API で File List を Drag&Drop する



C++ の OLE(Object Linking and Embedding) を使って、Windows エクスプローラと相互に File List を Drag&Drop します。
このプログラムの作成にあたって 窓プログラミング のお世話になりました。

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

プロジェクトの設定

  1. 新規プロジェクト(DragDrop)で空のプロジェクトを作成して下さい。
  2. ページ先頭の画面を参考にして DialogBox に EditControl と ListBox を配置して下さい。
    IDを次のように設定して下さい。
    BOX ID
    DialogBox IDD_DIALOG1
    EditControl IDC_EDIT1
    ListBox IDC_LIST1
  3. [ビルド] から [実行] を選択するとビルドに続いてエラーが無ければプログラムが実行されます。
    ListBox のフォルダーをダブルクリックすると、表示するフォルダーが切り替わります。
    エクスプローラからファイル名を選択して、ウインドウにドラッグすると ListBox に追加されます。
    ただし、ListBox に追加されるだけで実際にファイルがコピーされる訳ではありません。
  4. ListBox からファイルを選択してエクスプローラにドラッグするとリストに追加されます。
    こちらはファイルが実際にコピー(移動) されるので、操作したファイルを紛失しないように注意して下さい。

Main プログラムの説明

Drag&Drop のプログラムは OLE(Object Linking and Embedding) を使用します。
前回のプログラム File List を Drag&Drop する ではC言語の API を使用しましたが、 今回は C++ の API を使用します。
  1. DragDrop.h を取り込んで下さい。
    DRAGDROP* DragDrop; が Drag & Drop の Object の定義です。
    ItemCnt; は ListBox から選択された Item(ファイル) の数です。
    ItemNum[20]; は ListBox から選択された Item 番号を格納する領域で、取り合えず20個分用意しました。
    FileName[20][MAX_PATH]; は選択されたファイル名(フルパス) を格納する領域です。
        #include    <windows.h>
        #include    <SHLOBJ.H>
        #include    "DragDrop.h"
        #include    "resource.h"
    
        DRAGDROP*   DragDrop;               //Drag & Drop の Object の定義
        int     ItemCnt;                    //選択されている ITEM の数
        int     ItemNum[20];                //選択されている ITEM 番号の一覧を格納
        char    FileName[20][MAX_PATH];     //ItemNum に対応する ITEM Text(File Name)
    
        // Function Prototype
        LRESULT CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);
        int     SetFileName(HWND hdlg);
        
  2. WinMain() では OLE を初期化して DialogBox を表示します。
    終了時には OLE を Uninitialize して下さい。
        //★ WinMain 関数
        int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPTSTR lpCmdLine, int nCmdShow)
        {
            OleInitialize(NULL);
            DialogBox(hInst,MAKEINTRESOURCE(IDD_DIALOG1),NULL,(DLGPROC)DlgProc);
            OleUninitialize();
            return TRUE;
        }
        
  3. DialogBox の CALLBACK 関数です。
    WM_INITDIALOG: で DRAGDROP をインスタンス化して、OnCreate() で Class を起動します。
    プログラムの終了時に OnDestroy() で Object を終了させて、delete で開放して下さい。
    DlgDirList() で ListBox にフォルダー一覧を表示します。
        //★ CALLBACK 関数
        LRESULT CALLBACK DlgProc( HWND hDlg, UINT uMsg, WPARAM WP, LPARAM LP )
        {   static char szTmp[255]= { "C:\\windows\\temp\\*.*" };
    
            switch( uMsg )
            {   case WM_INITDIALOG:
                    DragDrop = new DRAGDROP(hDlg);
                    if (DragDrop==NULL)  return -1;
                    DragDrop->OnCreate();
                    DlgDirList(hDlg,szTmp,IDC_LIST1,IDC_EDIT1,DDL_DIRECTORY | DDL_DRIVES );
                    break;
        
  4. DialogBox 上でマウスの左ボタンが押されて、ドラッグが開始された時の処理です。
    SetFileName() で ListBox から選択されたファイルを取得して、OnLButtonDown(); でドラッグを開始します。
            case WM_LBUTTONDOWN:
                SetFileName(hDlg);
                return DragDrop->OnLButtonDown(WP, LP);
        
  5. ListBox のフォルダーをダブルクリックすると、表示するフォルダーを切り替えます。
            case WM_COMMAND:
                switch(LOWORD(WP))
                {   case IDC_LIST1:
                        if (HIWORD(WP) == LBN_DBLCLK)
                        {   if (DlgDirSelectEx(hDlg,szTmp,sizeof(szTmp),IDC_LIST1))
                            {   strcat(szTmp,"*.*");
                                DlgDirList(hDlg,szTmp,IDC_LIST1,IDC_EDIT1,DDL_DIRECTORY | DDL_DRIVES);
                            }
                            else    MessageBox(hDlg,szTmp,"File Selected",MB_OK);
                        }
                        break;
                }
                break;
        
  6. プログラムの終了時に OnDestroy() で Object を終了させて下さい。
    delete で DragDrop Object を開放します。
            case WM_CLOSE:
                EndDialog(hDlg,TRUE);
                break;
            case WM_DESTROY:
                DragDrop->OnDestroy();
                delete DragDrop;
                return 0;
        
  7. ListBox から選択された ITEM(ファイル) を FileName[] に格納する関数です。
    IDC_EDIT1 からパスを取得して、フルパスで格納します。
    ファイル名のポインタを DragDrop->_FileList[] に、個数を DragDrop->_FileCnt に格納します。
        //★ 選択されたファイル名を取り出して FileName[] に格納する
        int SetFileName(HWND hdlg)
        {   int     i;
            char    dirw[MAX_PATH], namew[64];
    
            ItemCnt= SendMessage(GetDlgItem(hdlg,IDC_LIST1),LB_GETSELITEMS,(WPARAM)20,(LPARAM)ItemNum);
            if (ItemCnt==LB_ERR || ItemCnt==0)
            {   MessageBox(hdlg,"Selct File Error","Drag&Drop", MB_OK);
                return -1;
            }
            GetDlgItemText(hdlg,IDC_EDIT1,dirw,MAX_PATH);
            strcat(namew,"\\");
            for(i=0; i<ItemCnt; i++)
            {   SendMessage(GetDlgItem(hdlg,IDC_LIST1),LB_GETTEXT,(WPARAM)ItemNum[i],(LPARAM)namew);
                strcpy(FileName[i],dirw);
                strcat(FileName[i],namew);
                DragDrop->_FileList[i]= FileName[i];
            }
            DragDrop->_FileCnt= ItemCnt;
            return 0;
        }
        

DRAGDROP Class の説明

  1. ドラッグ&ドロップには、以下の四つのクラスを継承した Object Class が必要です。
    継承するクラスは windows.h で純粋仮想関数(中身が無くて定義だけある)として定義されています。
    クラス名 継承クラス 簡単な説明
    CDropTarget IDropTarget ドラッグ中のマウスがウインドウに入ってきたりしたときの処理
    CDropSource IDropSource ドラッグ&ドロップを始めるときの処理
    CDataObject IDataObject ドラッグするデータを操作するクラス
    IEnumFORMATETC CEnumFORMATETC CDataObject が持っているデータの形式のリスト
  2. DRAGDROP Class は IDropTarget を継承しています。
    *_FileList[50]; にはドラッグするファイルリストを格納します。
    _FileCnt; はファイルリストの個数です。
        class DRAGDROP :   public IDropTarget
        {
        //-----------DRAGDROPの部分-----------
        protected:
            static char szAppName[];
            static char szClassName[];
            HWND    _hWnd;
    
        public:
            char    *_FileList[50];         //Drag File List
            int     _FileCnt;               //Drag File Count
        
  3. DRAGDROP クラスの中で ListBox の ID を使うので、DragDrop.cpp の先頭で "resource.h" を取り込んで下さい。
        #include <windows.h>
        #include <shlobj.h>
        #include "DragDrop.h"
        #include "resource.h"
        
  4. DialogBox 上でマウスの左ボタンが押されて、ドラッグを開始する時に呼ばれる関数です。
    _FileList と _FileCnt にドラッグするファイルの名前と個数を格納してこの関数を呼びます。
    CreateHDrop() でメモリ領域を割り当てて、CF_HDROP 形式でファイルリストを格納します。
    DoDragDrop() でドラッグを開始します。
        //★ CDataObjectを作成し CF_HDROP 形式のデータを登録
        LRESULT DRAGDROP::OnLButtonDown(WPARAM wParam, LPARAM lParam)
        {   CDataObject *dobj = NULL;
            CDropSource *dsrc = NULL;
            HANDLE      hObject = NULL;
            FORMATETC   fmt;
            STGMEDIUM   medium;
            DWORD       dwEffect;
            int         ret;
    
            //CDataObjectを作成し CF_HDROP 形式のデータを登録
            dobj = new CDataObject();
            if (dobj==NULL)         goto error;
            if (!dobj->allocate(2)) goto error;
    
            // NT系のWindowsの場合は次の行のFALSEをTRUEにしてUNICODE版のHDROPを作ってください 
            if ((hObject=CreateHDrop(_FileList,_FileCnt,FALSE))==NULL)    goto error;
            CreateMedium(CF_HDROP, hObject, &fmt, &medium);
            if (dobj->SetData(&fmt,&medium,TRUE)!=S_OK) goto error;   //解放はDataObjectに任す
            hObject = NULL;
            dsrc = new CDropSource();
            if(dsrc == NULL) goto error;
            ret = DoDragDrop(dobj, dsrc, DROPEFFECT_COPY,&dwEffect);
    
        error:
            if (dsrc)   dsrc->Release();
            if (dobj)   dobj->Release();
            if (hObject) GlobalFree(hObject);
            return  0;
        }
        
  5. Memory を割り当てて FileName を CF_HDROP 形式で格納する関数です。
    **FileName にはドラッグするファイルの名前がフルパスで格納されています。
        //★ Memory を割り当てて FileName を CF_HDROP 形式で格納する
        HDROP DRAGDROP::CreateHDrop(char **FileName, int cnt, BOOL fWide)
        {   HDROP       hDrop;
            LPDROPFILES lpDropFile;
            int         wtotal, btotal, wlen, i;
    
            if(fWide)
            {   // ワイドキャラ
                wtotal = 0;
                for(i = 0; i < cnt; i++)
                {   wtotal += ::MultiByteToWideChar(CP_ACP, 0, FileName[i], -1, NULL, 0);
                }
                btotal = wtotal * sizeof(wchar_t);
            }
            else
            {   // マルチバイト
                btotal = 0;
                for(i = 0;i < cnt;i++)
                {   btotal += ::lstrlen(FileName[i]) + 1;
                }
            }
            hDrop = (HDROP)::GlobalAlloc(GHND,sizeof(DROPFILES) + btotal + 2); //Wideの時も考えて+2
            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\0)
            if(fWide)
            {   // ワイドキャラ
                wchar_t *buf;
                buf = (wchar_t *)(&lpDropFile[1]);
                for(i = 0; i < cnt; i++)
                {   wlen = ::MultiByteToWideChar(CP_ACP, 0, FileName[i], -1, buf, wtotal);
                    wtotal -= wlen;
                    buf += wlen;
                }
                *buf = 0;
            }
            else
            {   // マルチバイト
                char *buf;
                buf = (char *)(&lpDropFile[1]);
                for(i = 0; i < cnt; i++)
                {   ::lstrcpy(buf,FileName[i]);
                    buf += ::lstrlen(FileName[i]) + 1;
                }
                *buf++ = 0;
                *buf = 0;
            }
            ::GlobalUnlock(hDrop);
            return  hDrop;
        }
        
  6. File List が Drop されたときの処理です。
    CF_HDROP でデータがドラッグされたことを確認して、ファイルリストを取得します。
    DragQueryFile((HDROP)medium.hGlobal,-1,NULL,0) でファイルの個数を取得します。
    DragQueryFile((HDROP)medium.hGlobal,i,fn,sizeof(fn)); でファイル名を取得します。
    ListBox に登録するのは、パスを除いたファイル名です。
        //★ File List が Drop されたときの処理
        HRESULT __stdcall DRAGDROP::Drop(IDataObject* pDataObject, DWORD grfKeyState, POINTL ptl, DWORD* pdwEffect)
        {   FORMATETC   fmt;
            STGMEDIUM   medium;
            int         i,j,cnt;
            char        fn[MAX_PATH],fname[48];
    
            _mouse = FALSE;                     //マウス座標表示OFF
            fmt.cfFormat = CF_HDROP;
            fmt.ptd = NULL;
            fmt.dwAspect = DVASPECT_CONTENT;
            fmt.lindex = -1;
            fmt.tymed = TYMED_HGLOBAL;
    
            // CF_HDROP 形式のデータを取得
            if (pDataObject->GetData(&fmt, &medium) == S_OK)
            {   // クリップボードのデータを表示する
                cnt= DragQueryFile((HDROP)medium.hGlobal,-1,NULL,0);    //ファイル数を取得
        //wsprintf(fn,"cnt=%d",cnt);
        //MessageBox(NULL,fn,"Copy File Count",MB_OK);
                for(i=0; i<cnt; i++)
                {   DragQueryFile((HDROP)medium.hGlobal,i,fn,sizeof(fn)); //ファイル名を取得
        //MessageBox(_hWnd,fn,"Drag File Name",MB_OK);
                    for(j=strlen(fn-1); j>=0 && fn[j]!='\\' && fn[j]!=':'; j--);
                    strcpy(fname,fn+j+1);
                    SendMessage(GetDlgItem(_hWnd,IDC_LIST1),LB_INSERTSTRING,0,(LPARAM)fname);  
                }
                ReleaseStgMedium(&medium);
                InvalidateRect(_hWnd, NULL, TRUE);
                *pdwEffect = DROPEFFECT_COPY;
                return S_OK;
            }
            InvalidateRect(_hWnd, NULL, TRUE);
            *pdwEffect = DROPEFFECT_NONE;
            return S_OK;
        }
        
  7. C# でも同様のプログラムを作成しています。
    こちらの方が簡単にプログラミング出来ます。
    エクスプローラと Drag&Drop を参照して下さい。

【演習】

課題1

プログラムを起動して、エクスプローラと相互に Drag & Drop を行ってみて下さい。

課題2

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

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