SDK Index Previous page Next page

OLE DragDrop (IDataObjectの実装)


はじめに

今回説明するCDataObjectはドラッグするデータにアクセスするためのクラスです。ドラッグの受け手はこのクラス(正確にはインターフェイス)を使って、どんなデータがあるか調べたり実際にデータを得たりするわけです。
このクラスはなかなかに複雑なクラスで私も半分ぐらい(最低限必要な部分)しか分かっていません。ここでも半分ぐらい未実装になっています。でも普通はこれで十分なはずです。

それとDataObjectはデータのチェックと取得が出来ればよいので実装方法にかなり自由度があります。ここで示す方法はその一例です。

CDataObjectを作る


作らないといけない仮想関数は
 QueryInterface
 AddRef
 Release
 GetData
 GetDataHere
 QueryGetData
 GetCanonicalFormatEtc
 SetData
 EnumFormatEtc
 DAdvise
 DUnadvise
 EnumDAdvise
の12個ですが赤字のメンバは今回はよく分からないので実装していません。
なので、今回作ったのは最初の3つを除くと4メンバになります。

実装方針

クラスを作る前に、CDataObjectにデータをどうやって格納するかを決めます。
listとかvectorとかいろいろ方法はあると思いますが、動的に確保した配列に入れることにします。配列はallocateというメンバを作ってそれで確保するようにします。サンプルではMyDArrayというテンプレートを作ってそれを使っています。それとCSTGMEDIUM::Dupというデータを複製するためのクラス&メンバも有りますが、説明は省略します、最終回で載せるサンプルを見てください。

クラス定義

とりあえずクラス定義の部分だけ先に書いておきます。
class CDataObject : public IDataObject
{
  public:
      CDataObject() : _RefCount(1){
          _num = 0;
      };
      ~CDataObject(){};

    virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv);
    virtual ULONG __stdcall AddRef(void);
    virtual ULONG __stdcall Release(void);

    virtual HRESULT __stdcall GetData(FORMATETC *pFormatetc, STGMEDIUM *pmedium);
    virtual HRESULT __stdcall GetDataHere(FORMATETC *pFormatetc, STGMEDIUM *pmedium);
    virtual HRESULT __stdcall QueryGetData(FORMATETC *pFormatetc);
    virtual HRESULT __stdcall GetCanonicalFormatEtc(FORMATETC *pFormatetcIn, FORMATETC *pFormatetcInOut);
    virtual HRESULT __stdcall SetData(FORMATETC *pFormatetc, STGMEDIUM *pmedium, BOOL fRelease); /*fRelease=TRUEの時DataObject*/
    virtual HRESULT __stdcall EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatetc);
    virtual HRESULT __stdcall DAdvise(FORMATETC *pFormatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection);
    virtual HRESULT __stdcall DUnadvise(DWORD dwConnection);
    virtual HRESULT __stdcall EnumDAdvise(IEnumSTATDATA **ppenumAdvise);

    BOOL allocate(int num);

  protected:
    class CObject{
    public:
        FORMATETC   fmt;
        STGMEDIUM   medium;
    public:
        CObject(){
            medium.tymed = TYMED_NULL;
        }
        ~CObject(){
            if(medium.tymed != TYMED_NULL) ReleaseStgMedium(&medium);
        }
        BOOL Set(FORMATETC* pf, STGMEDIUM *pm, BOOL fRelease){
            fmt = *pf;
            if(fRelease){
                medium = *pm;
                return  TRUE;
            } else{
                return CSTGMEDIUM::Dup(&medium, pf, pm);
            }
        }
    };

    typedef CMyDArray<CObject> OBJLIST;
    LONG _RefCount;
    OBJLIST _Objects;
    int     _num;
};

GetData

指定された形式のデータがあればそれを返す関数です。配列の中を調べて形式が一致すればデータを複製して返しています。データを複製しているのでデータを受け取った側はそのデータを解放しなければいけません。
ちなみに形式の比較とかはよく分かってません、参考にしたソースのをそのまま持ってきました。

データの持ち方ですが、はじめから全部のデータを作る事をせずにGetDataが呼ばれたときに必要なデータだけ作って返してもOKです。
HRESULT __stdcall CDataObject::GetData(FORMATETC *pFormatetc, STGMEDIUM *pmedium)
{
    int i;

    if(pFormatetc == NULL || pmedium == NULL){
        return E_INVALIDARG;
    }

    if (!(DVASPECT_CONTENT & pFormatetc->dwAspect)) return DV_E_DVASPECT;

    for(i = 0; i < _num; ++i){
        if(_Objects[i].fmt.cfFormat == pFormatetc->cfFormat && 
            (_Objects[i].fmt.tymed & pFormatetc->tymed) != 0){
            if(CSTGMEDIUM::Dup(pmedium, &_Objects[i].fmt, &_Objects[i].medium) == FALSE){
                return E_OUTOFMEMORY;
            }
            return S_OK;
        }
    }

    return DV_E_FORMATETC;
}

GetDataHere

よく分かりません。だからE_NOTIMPLを返しています。
HRESULT __stdcall CDataObject::GetDataHere(FORMATETC *pFormatetc, STGMEDIUM *pmedium)
{
    return E_NOTIMPL;
}

QueryGetData

指定された形式のデータが有るか無いかを返す関数です。
データを返さないGetDataです。
HRESULT __stdcall CDataObject::QueryGetData(FORMATETC *pFormatetc)
{
    int i;

    if(pFormatetc == NULL){
        return E_INVALIDARG;
    }

    if (!(DVASPECT_CONTENT & pFormatetc->dwAspect)) return DV_E_DVASPECT;

    for(i = 0; i < _num; ++i){
        if(_Objects[i].fmt.cfFormat == pFormatetc->cfFormat && 
            (_Objects[i].fmt.tymed & pFormatetc->tymed) != 0){
            return S_OK;
        }
    }

    return DV_E_FORMATETC;
}

EnumFormatEtc

欲しい形式のデータがあるかどうかを調べる方法としてこの関数で取得できるIEnumFormatEtcを使う方法があります。この関数ではそのためのIEnumFormatEtcを作って返します。もちろんIEnumFormatEtc自体も自分で実装しないといけません。
これもE_NOTIMPLにしてもいいみたいなんですが一応理解できたので実装しました。CEnumFormatEtcの形式データの格納方法はCDataObjectと同じように配列を使っています。
HRESULT __stdcall CDataObject::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatetc)
{
    int i;
    CEnumFORMATETC* pfmt;

    if (ppenumFormatetc == NULL){
        return E_INVALIDARG;
    }
    
    *ppenumFormatetc = NULL;
    switch (dwDirection) {
        case DATADIR_GET:
            pfmt = new CEnumFORMATETC;
            if(pfmt == NULL){
                return E_OUTOFMEMORY;
            }
            
            if(!pfmt->allocate(_Objects.size())){
                delete pfmt;
                return E_OUTOFMEMORY;
            }

            for(i = 0; i < _num; ++i){
                pfmt->SetFormat(&_Objects[i].fmt);
            }
            *ppenumFormatetc = pfmt;
            break;
        default:
            return E_NOTIMPL;
    }

    return S_OK;
}

SetData

データを追加する関数です。配列の最後にデータを格納するのですが引数のfReleaseがFALSEの時はデータを複製して格納します。TRUEの時はそのまま格納します。
fReleaseがTRUEの時はCDataObjectがデータを解放します。FALSEの時はSetDataを呼び出した側がちゃんと解放しないといけません。
HRESULT __stdcall CDataObject::SetData(FORMATETC *pFormatetc, STGMEDIUM *pmedium, BOOL fRelease)
{
    if(pFormatetc == NULL || pmedium == NULL) return E_INVALIDARG;

    if(_num >= _Objects.size()) return E_OUTOFMEMORY;

    if(_Objects[_num].Set(pFormatetc, pmedium, fRelease) == FALSE) return E_OUTOFMEMORY;

    _num++;

    return S_OK;
}

GetCanonicalFormatEtc

これもよく分かりません。だからE_NOTIMPLを返しています。
HRESULT __stdcall CDataObject::GetCanonicalFormatEtc(FORMATETC *pFormatetcIn, FORMATETC *pFormatetcOut)
{
    return E_NOTIMPL;
}

DAdvise DUnadvise EnumDAdvise

やっぱりよく分かりません。でも別にサポートしなくても良いみたいなのでOLE_E_ADVISENOTSUPPORTEDを返しています。
HRESULT __stdcall CDataObject::DAdvise(FORMATETC *pFormatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection)
{
    return OLE_E_ADVISENOTSUPPORTED;
}

HRESULT __stdcall CDataObject::DUnadvise(DWORD dwConnection)
{
    return OLE_E_ADVISENOTSUPPORTED;
}

HRESULT __stdcall CDataObject::EnumDAdvise(IEnumSTATDATA **ppenumAdvise)
{
    return OLE_E_ADVISENOTSUPPORTED;
}

allocate

配列を確保します。
BOOL CDataObject::allocate(int num)
{
    return  _Objects.allocate(num);
}

おわりに

だんだん説明が適当になっていく・・ いつものことか(^.^)

to sdk prev next