[BlueLeaf1336]> PROBLEMS> OLE Drag Drop>

エクスプローラにファイルをドラッグする

historyTOP

2006/09/22:作成

はじめにTOP

以前に調べて速攻で断念した、エクスプローラへのドラッグアンドドロップです。ここに、もう一度挑戦しようと思います。

すでに関係ありそうな参考にできそうなパクれる情報の満載されたサイトをかき集めたりしたんですが、改めて調べてみると異常にたくさん出てきました。モロなやつもありました。

いつものことですが、このページではすでに書きつくされていようがどうしようが、完全にそのものがない限り、Delphi で書く、ことに重点を置いています。

今回調べたサイトです。○付きはかなり参考になる、あるいはモロのサイトです。これだけのヒントや答えが示されていてまだできない...

大雑把な説明TOP

異なるアプリケーションの間でドラッグアンドドロップでデータを受け渡すのは、クリップボードを使ったやり取りと似ているらしいのですが、クリップボードを使ってそういうやり取りをやったことがないので、なるほど!! とはならずです。

まず、ドラッグ元になるために必要最低限必要なのは、IDropSource を完成させる、また、ドロップするデータを処理する IDataObject を完成させる、やることはこれだけです。どちらもクラスではなくてインターフェースなので、AddRef とかそのほかいろいろとあるんですが、ほとんどは Delphi で定義されている TInterfacedObject から派生したクラスを作ることでカバーできてしまいます。

それに、IDropSource を完成させるために 2つ、IDataObject を完成させるために 9つ のメソッドを実装するわけですが、よくわからないものは「実装してません」を宣言するだけで許してくれたりもします。

などかいていると簡単やん、と思えてきますが、これがまた...

ベースになる何もしないクラスの宣言TOP

まず、決まりきった手順で処理するだけだったり、実装せずに許してもらえるメソッドが結構あるので、これらを「何もしないクラス」として宣言してしまいます。その上で、そのクラスから派生した「実際に何かをするクラス」を作ることにします。

「何もしないクラス」の宣言です。

uses
    Windows, SysUtils, Classes, ActiveX, ShlObj, ComObj, ShellApi;

type
    //-------------------------------------------------------------------------
    //  ドラッグ元にするためのなにもしないクラス
    TOleDragDropBase = class(TInterfacedObject, IDropSource, IDataObject)

    //  IDropSource
    protected
        function QueryContinueDrag(fEscapePressed: BOOL;
                    grfKeyState: Longint): HResult; virtual; stdcall;
        function GiveFeedback(dwEffect: Longint): HResult; virtual; stdcall;

    //  IDataObject
    protected
        function GetData(const formatetcIn: TFormatEtc; out medium: TStgMedium):
                    HResult; virtual; stdcall;
        function GetDataHere(const formatetc: TFormatEtc; out medium: TStgMedium):
                    HResult; virtual; stdcall;
        function QueryGetData(const formatetc: TFormatEtc): HResult;
                    virtual; stdcall;
        function GetCanonicalFormatEtc(const formatetc: TFormatEtc;
                    out formatetcOut: TFormatEtc): HResult; virtual; stdcall;
        function SetData(const formatetc: TFormatEtc; var medium: TStgMedium;
                    fRelease: BOOL): HResult; virtual; stdcall;
        function EnumFormatEtc(dwDirection: Longint; out enumFormatEtc:
                    IEnumFormatEtc): HResult; virtual; stdcall;
        function DAdvise(const formatetc: TFormatEtc; advf: Longint;
                    const advSink: IAdviseSink; out dwConnection: Longint): HResult;
                    virtual; stdcall;
        function DUnadvise(dwConnection: Longint): HResult; virtual; stdcall; 
        function EnumDAdvise(out enumAdvise: IEnumStatData): HResult;
                    virtual; stdcall; 

    //  データユーティリティ
    protected
        function CreateHText(const AText: String): HGLOBAL;
        function CreateHDrop(AFileList: TStringList): HDROP;
        procedure CreateMedium(const Data: HGLOBAL; var medium: TStgMedium);

    end;

このクラスは、TInterfacedObject から派生し、IDropSource と IDataObject をなんと呼べばよいのかくっつけています。つまりこのクラスひとつで、ドラッグ元になることとドラッグするデータの両方を捌いてしまう作戦です。

インターフェースを使う上でのややこしい話は、TInterfacedObject がやってくれるので、必要なメソッドのみ実装すればよいことになっています。

IDropSource と IDataObject から引き継いだ全てのメソッドに「virtual」をつけているのは、この「何もしないクラス」の本体は、本当に何もしないので、このクラスから派生したクラスで処理を上書きしてもらうためです。

でも、実装を許してもらえるものが結構あるので、このクラスでは「全て実装を許してもらう」形で完成させておきます。こうすることで、本当の本当に必要な(もしくは理解できた箇所だけ)実装だけを派生クラスでやればよいことになって、良い感じかも。

ベースになる何もしないクラスの本体TOP

本当になんにもしていません。このクラスを作っておけば、派生クラスでは、許してもらえないメソッド、つまり「IDataObject.GetData」「IDataObject.QueryGetData」だけを書くだけでなんとかなるはずです。

現時点で理解できる範囲で説明すると

  1. アプリケーションで DoDragDrop でドラッグを開始する。このとき、ドラッグ先に使ってもらう IDropSource, IDataObject を渡しておく。
  2. ドラッグ先は、IDropSource.QueryContinueDrag を呼び出してキャンセルするかどうかをたずねてくる
  3. ドラッグ先は、IDropSource.GiveFeedback を呼び出してカーソル変えたりするかどうかたずねてくる
  4. ドラッグ先は、IDataObject.QueryGetData を呼び出して、今ドラッグ中のデータって中身何よ、とたずねてくる。というか、ドラッグ先が受け取れるデータを持っているかどうかを確かめる
  5. ドラッグ先は、IDataObject.GetData を呼び出して、ドラッグ中のデータを受け取る

こんな感じです。多分すごく誤解しています。端折りすぎてます。

//=============================================================================
//  TOleDragDropBase

//-----------------------------------------------------------------------------
//  文字列形式のデータを登録
function TOleDragDropBase.CreateHText(const AText: String): HGLOBAL;
var
    Len: Integer;
    P: PChar;
begin
    Len := Length(AText) + 1;
    Result := GlobalAlloc((GMEM_SHARE or GMEM_MOVEABLE or GMEM_ZEROINIT), Len);
    if (Result <> 0) then
    begin
        P := GlobalLock(Result);
        try
            CopyMemory(P, PChar(AText), Len);
        finally
            GlobalUnlock(Result);
        end;
    end;
end;

//-----------------------------------------------------------------------------
//  HDROP形式のデータを登録
//  サンプル: "ソフトからショートカットを作る"
//  http://forum.nifty.com/fdelphi/samples/00094.html
function TOleDragDropBase.CreateHDrop(AFileList: TStringList): HDROP;
var
    i: Integer;
    LDropFiles: TDropFiles;
    LBuf: TMemoryStream;

    LName: String;
    //LNameLen: Integer;
    //LNameBuf: PWChar;

    LChar: Char;

    P: Pointer;
begin
    if (AFileList.Count = 0) then Result := 0
    else
    begin
        //  構造体作成
        FillChar(LDropFiles, SizeOf(LDropFiles), 0);
        LDropFiles.pFiles := SizeOf(LDropFiles);    //  ファイルリストのオフセット
        LDropFiles.pt := Point(0, 0);
        LDropFiles.fNC := False;
        //LDropFiles.fWide := True;                 //  NT系らしいけどうまくいかない
        LDropFiles.fWide := False;

        //  TDropFiles の後ろに ファイル名\0ファイル名\0ファイル名\0\0\0 と連結
        LBuf := TMemoryStream.Create();

        //  構造体書き込み
        LBuf.Write(LDropFiles, SizeOf(LDropFiles));

        //  ファイル名を追加
        for i := 0 to AFileList.Count - 1 do
        begin
            LName := AFileList.Strings[i];

            //LNameLen := MultiByteToWideChar(CP_ACP, 0, PChar(LName), -1, nil, 0);
            //GetMem(LNameBuf, LNameLen + 1);
            //MultiByteToWideChar(CP_ACP, 0, PChar(LName), -1, LNameBuf, LNameLen);
            //LBuf.Write(LNameBuf, LNameLen);
            //FreeMem(LNameBuf);

            LBuf.Write(LName[1], Length(LName));

            LChar := #0;
            LBuf.Write(LChar, 1);
        end;
        LChar := #0;
        LBuf.Write(LChar, 1);

        //  ココまでの内容をメモリに書き込む
        Result := GlobalAlloc((GMEM_SHARE or GMEM_MOVEABLE or GMEM_ZEROINIT), LBuf.Size);
        P := GlobalLock(Result);
        CopyMemory(P, LBuf.Memory, LBuf.Size);

        LBuf.Free();

        //  ロック解放
        GlobalUnlock(Result);
    end;
end;

//-----------------------------------------------------------------------------
//  ドラッグデータ作成
procedure TOleDragDropBase.CreateMedium(const Data: HGLOBAL; var medium: TStgMedium);
begin
    medium.tymed := TYMED_HGLOBAL;
    medium.unkForRelease := nil;
    medium.hGlobal := Data;
end;

//=============================================================================
//  TOleDragDropBase - IDropSource

//-----------------------------------------------------------------------------
//  キーやマウスボタンの状態をみて、ドラッグを続けるかやめるかを決める関数
function TOleDragDropBase.QueryContinueDrag(fEscapePressed: BOOL;
            grfKeyState: Longint): HResult;
begin
    //  ESCが押されたらキャンセル
    if (fEscapePressed) then
    begin
        Result := DRAGDROP_S_CANCEL;
    end
    //  マウスの両ボタンが押されたらキャンセル
    else if ((grfKeyState and (MK_LBUTTON or MK_RBUTTON)) = (MK_LBUTTON or MK_RBUTTON)) then
    begin
        Result := DRAGDROP_S_CANCEL;
    end
    //  マウスボタンが離されたらドロップ
    else if ((grfKeyState and (MK_LBUTTON or MK_RBUTTON)) = 0) then
    begin
        Result := DRAGDROP_S_DROP;
    end
    //  それ以外は継続
    else
    begin
        Result := S_OK;
    end;
end;

//-----------------------------------------------------------------------------
//  マウスカーソルの形状を変えたり、特殊効果を出したりするための関数
function TOleDragDropBase.GiveFeedback(dwEffect: Longint): HResult;
begin
    //標準のマウスカーソルを使う
    Result := DRAGDROP_S_USEDEFAULTCURSORS;
end;

//=============================================================================
//  TOleDragDropBase - IDataObject

//-----------------------------------------------------------------------------
//  指定された形式のデータがあればそれを返す関数
function TOleDragDropBase.GetData(const formatetcIn: TFormatEtc; out medium: TStgMedium):
            HResult;
begin
    Result := E_NOTIMPL;
end;

//-----------------------------------------------------------------------------
//  指定された形式のデータが有るか無いかを返す関数
function TOleDragDropBase.QueryGetData(const formatetc: TFormatEtc): HResult;
begin
    Result := E_NOTIMPL;
end;

//-----------------------------------------------------------------------------
//  気にしない。GetDataに似ているらしい
function TOleDragDropBase.GetDataHere(const formatetc: TFormatEtc; out medium: TStgMedium):
            HResult;
begin
    Result := E_NOTIMPL;
end;

//-----------------------------------------------------------------------------
//  気にしない
function TOleDragDropBase.GetCanonicalFormatEtc(const formatetc: TFormatEtc;
            out formatetcOut: TFormatEtc): HResult;
begin
    Result := E_NOTIMPL;
end;

//-----------------------------------------------------------------------------
//  気にしない。データを追加する関数
function TOleDragDropBase.SetData(const formatetc: TFormatEtc; var medium: TStgMedium;
            fRelease: BOOL): HResult;
begin
    Result := E_NOTIMPL;
end;

//-----------------------------------------------------------------------------
//  気にしない
function TOleDragDropBase.EnumFormatEtc(dwDirection: Longint; out enumFormatEtc:
            IEnumFormatEtc): HResult;
begin
    Result := E_NOTIMPL;
end;

//-----------------------------------------------------------------------------
//  気にしない
function TOleDragDropBase.DAdvise(const formatetc: TFormatEtc; advf: Longint;
            const advSink: IAdviseSink; out dwConnection: Longint): HResult;
begin
    Result := OLE_E_ADVISENOTSUPPORTED;
end;

//-----------------------------------------------------------------------------
//  気にしない
function TOleDragDropBase.DUnadvise(dwConnection: Longint): HResult;
begin
    Result := OLE_E_ADVISENOTSUPPORTED;
end;

//-----------------------------------------------------------------------------
//  気にしない
function TOleDragDropBase.EnumDAdvise(out enumAdvise: IEnumStatData): HResult;
begin
    Result := OLE_E_ADVISENOTSUPPORTED;
end;

実際に何かをするクラスの宣言TOP

多分こうなるはずです。この方針で進んだときにエクスプローラへのドラッグができなかったという事実については、また後で考えることにします。

type
    //-------------------------------------------------------------------------
    //  ドラッグ元にするためのクラス
    TOleDragDropObject = class(TOleDragDropBase)

    protected
        function GetData(const formatetcIn: TFormatEtc; out medium: TStgMedium):
                    HResult; override;
        function QueryGetData(const formatetc: TFormatEtc): HResult;
                    override;

    public
        (etc.)
    end;

EOFTOP