Win32API

リッチエディット大好き!

いまさらリッチエディット~?って気もしますが、好きなんですよリッチエディット。
仕事柄、ちょっとした検証サンプルをWin32で作ることが多いのですが、通常のログ出力にも、いわゆるprintfデバッグにもつかったりして重宝しています。
win32プログラミング始めたころから少しづつ拡張したり、ゴミ箱に捨てたり、ゴミ箱から発掘してきたりで、今はこんな感じになってます。

以下、ちょっとした要点の記録


ライブラリのロード

  HMODULE hre_dll=LoadLibrary("RichEd32.dll");

リッチエディットを使う場合、dllをいちいちロードする必要があります。
riched20.dllでもmsftedit.dllでもいいんですけど、機能的にはriched32.dllで十分なのでwin95のときから変わってないコードです。。。

余談になりますが、
私がWin32のアプリを作る場合、基本的にダイアログベースで作成し、その上にぺたぺたウィンドウを張る方法をとってます。
ダイアログのデザインにはResEditを使用しています。ダイアログなのでピクセル単位の調整に困ったりしますが、作業的にはまぁまぁ楽な部類です。


メインとは別のスレッドでリッチエディットを起動

  hthreadRE=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)thrProcRE, (void *)this, 0, NULL);

シングルスレッドでメッセージループをメインウィンドウと共有すると、リッチエディットのスクロールバーを動かしたときにメッセージループを占有してしまう状態?になってしまうんです。
あまりやりたくはなかったんですが、現在はこの手法に落ち着いてます。


externにしている関数群

extern void logdisp_SetCtlColor(COLORREF cr);
extern void logdisp_SetTextColor(COLORREF cr);
extern void logdisp_PrintTextf(char *fmt, ...);
extern void logdisp_DrawBitmap(HBITMAP hbmp, int w, int h);

私はリッチエディットも好きですが、printfも大好きなんです! cout << "..." よりも!
なので、PrintTextf()でフォーマットつきの文字列出力をサポートするようにしています。
DrawBitmap()ではHBITMAPの出力をサポートします。


文字出力と色変更の部分のソース抜粋

void logdisp_WriteWnd(char MText[])
{
  static bool doonce=false;
  SendMessage(OutputWnd, WM_SETREDRAW, FALSE, 0);
  {
    char linebuf[maxchars];
    int lines=(int)SendMessage(OutputWnd, EM_GETLINECOUNT, 0, 0);
    if(lines>maxlines){
      *(LPDWORD)linebuf=sizeof(linebuf)/sizeof(char);
      int charlen=(int)SendMessage(OutputWnd, EM_GETLINE, 0, (LPARAM)linebuf);
      if(charlen!=0){
        SendMessage(OutputWnd, EM_SETSEL, 0, (LPARAM)charlen);
        SendMessage(OutputWnd, EM_REPLACESEL, 0, (LPARAM)"");
      }
    }

    strcpy((char *)linebuf, MText);
    strcat((char *)linebuf, CHAR_CRLF);

    DWORD index=(DWORD)SendMessage(OutputWnd, WM_GETTEXTLENGTH, 0, 0);
    SendMessage(OutputWnd, EM_SETSEL, index+1, index+1);
    SendMessage(OutputWnd, EM_REPLACESEL, TRUE, (LPARAM)linebuf);
    SendMessage(OutputWnd, WM_VSCROLL, SB_BOTTOM, 0);
  }
  SendMessage(OutputWnd, WM_SETREDRAW, TRUE, 0);
  InvalidateRect(OutputWnd, NULL, FALSE);

  if(doonce==false){
    doonce=true;
    SendMessage(OutputWnd, WM_LBUTTONDOWN, 0, 0);
    SendMessage(OutputWnd, WM_KILLFOCUS, 0, 0);
  }
}

void logdisp_SetTextColor(COLORREF rgb)
{
  if(OutputWnd!=NULL){
    DWORD index=(DWORD)SendMessage(OutputWnd, WM_GETTEXTLENGTH, 0, 0);
    SendMessage(OutputWnd, EM_SETSEL, index+1, index+1);

    CHARFORMAT2 cf;
    cf.cbSize=sizeof(cf);
    cf.dwMask=CFM_COLOR;
    SendMessage(OutputWnd, EM_GETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&cf); //get current format
    cf.dwEffects&=~CFE_AUTOCOLOR;
    cf.crTextColor=rgb;
    SendMessage(OutputWnd, EM_SETCHARFORMAT, (WPARAM)SCF_SELECTION, (LPARAM)&cf); //set new format
  }
}

ちらつきを抑えるためにWM_SETREDRAWを使っていますが、こんな感じでいいのかしらん?


画像の貼り付け

IDataObjectを使用するわけですが、これがまた日本語資料が少なくて難解で困ります。
フォーマットとしては、ビットマップではなくメタファイルを使用するようにしています。
いかんせん、Win95時代からある代物なので、ビットマップをそのまま貼り付けて縮小でもしようものなら低品質な画像に変換されてしまうのですね。直接的な回避方法はあるんでしょうが、ちょっと安易に逃げてメタファイルです^^;
メタファイル自体も古くて、32bitアルファチャンネルつきbitmapに対応してなくて困ったちゃんなのですがね。

void CImageDataObject::SetImageData(HBITMAP hbmp, int w, int h, FORMATETC *fmt)
{
  HDC hmfdc=(HDC)CreateMetaFile(NULL);
  {
    SetMapMode(hmfdc, MM_ANISOTROPIC);
    SetWindowOrgEx(hmfdc, 0, 0, NULL);
    SetWindowExtEx(hmfdc, w, h, NULL);
    SetStretchBltMode(hmfdc, HALFTONE);
    HDC hmemdc=CreateCompatibleDC(NULL);
    SelectObject(hmemdc, hbmp);
    BitBlt(hmfdc, 0, 0, w+1, h+1, hmemdc, 0, 0, SRCCOPY);
    DeleteDC(hmemdc);
  }
  HMETAFILE hmf=CloseMetaFile(hmfdc);

  LPMETAFILEPICT lpmf=(LPMETAFILEPICT)GlobalAlloc(GMEM_FIXED, sizeof(METAFILEPICT));
  lpmf->hMF=hmf;
  lpmf->mm=MM_ANISOTROPIC;
  lpmf->xExt=lpmf->yExt=0;

  m_stgm.pUnkForRelease=NULL;
  m_stgm.tymed=TYMED_MFPICT;
  m_stgm.hGlobal=lpmf;

  this->SetData(fmt, &m_stgm, FALSE);
}

void InsertHBITMAP(HWND hwnd, HBITMAP hbmp, int w, int h)
{
  LPRICHEDITOLE pRichEditOLE;
  SendMessage(hwnd, EM_GETOLEINTERFACE, 0, (LPARAM)&pRichEditOLE);

  IOleClientSite *pOleClientSite;   
  pRichEditOLE->GetClientSite(&pOleClientSite);

  LPLOCKBYTES lpLockBytes=NULL;
  CreateILockBytesOnHGlobal(NULL, TRUE, &lpLockBytes);

  IStorage *pStorage;
  StgCreateDocfileOnILockBytes(lpLockBytes,
                               STGM_SHARE_EXCLUSIVE|STGM_CREATE|STGM_READWRITE,
                               0, &pStorage);

  FORMATETC fmt={0};  
  fmt.cfFormat=CF_METAFILEPICT; //  fm.cfFormat = CF_BITMAP;
  fmt.tymed=TYMED_MFPICT;       //  fm.tymed = TYMED_GDI;
  fmt.ptd=NULL;
  fmt.dwAspect=DVASPECT_CONTENT;
  fmt.lindex=-1;

  CImageDataObject *pCDO=new CImageDataObject;
  pCDO->SetImageData(hbmp, w, h, &fmt);

  IOleObject *pOleObject;
  OleCreateStaticFromData(pCDO, IID_IOleObject, OLERENDER_FORMAT, &fmt,  
                          pOleClientSite, pStorage, (void **)&pOleObject);
  CLSID clsid;
  pOleObject->GetUserClassID(&clsid);  


  REOBJECT reobject={0};
  reobject.cbStruct=sizeof(REOBJECT);
  reobject.clsid=clsid;  
  reobject.cp=REO_CP_SELECTION;
  reobject.dvaspect=DVASPECT_CONTENT;
  reobject.polesite=pOleClientSite;
  reobject.poleobj=pOleObject;
  reobject.pstg=pStorage;
  reobject.sizel.cx=(int)((float)w*HIMETRIC_INCH/MONITOR_DPI+0.5);
  reobject.sizel.cy=(int)((float)h*HIMETRIC_INCH/MONITOR_DPI+0.5);
  reobject.dwFlags=REO_DONTNEEDPALETTE|REO_BELOWBASELINE;

  SendMessage(hwnd, WM_SETREDRAW, FALSE, 0);
  {
    DWORD index=(DWORD)SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0);
    SendMessage(hwnd, EM_SETSEL, index+1, (LPARAM)index+1);
    pRichEditOLE->InsertObject(&reobject);
    SendMessage(hwnd, EM_REPLACESEL, TRUE, (WPARAM)CHAR_CRLF); 
    SendMessage(hwnd, WM_VSCROLL, SB_BOTTOM, 0);
  }
  SendMessage(hwnd, WM_SETREDRAW, TRUE, 0);
  InvalidateRect(hwnd, NULL, FALSE);

  pOleObject->Release();

  pCDO->FreeImageData();
  delete pCDO;

  pStorage->Release();
  pOleClientSite->Release();
  pRichEditOLE->Release();
}

貼り付けた画像の操作

IExRichEditOleCallbackをEM_SETOLECALLBACKで登録します。

        pREOLECallback=new IExRichEditOleCallback;
        SendMessage(hwndRE, EM_SETOLECALLBACK, 0, (LPARAM)pREOLECallback);

IExRichEditOleCallbackには、クリップボードコピーや右クリック時の動作といったUI操作について記述をしますが詳細割愛。
というか、説明できるほど詳しくないw

UI操作に対応してリッチエディットウィンドウにメッセージを飛ばしてくるので、処理します。
以下は、選択されたオブジェクトをHBITMAPに戻す関数。

bool extractHBITMAP(HWND hwnd, HBITMAP *hbmp, int *w, int *h)
{
  LPRICHEDITOLE pREOLE=NULL;
  SendMessage(hwnd, EM_GETOLEINTERFACE, 0, (LPARAM)&pREOLE);

  HRESULT hr=S_FALSE;

  if(pREOLE!=NULL){
    REOBJECT reobject={0};
    reobject.cbStruct=sizeof(REOBJECT);
    hr=pREOLE->GetObject(-1, &reobject, REO_GETOBJ_POLEOBJ);
    if(hr==S_OK){
      SIZEL sizel;
      reobject.poleobj->GetExtent(DVASPECT_CONTENT, &sizel);

      *w=(int)((float)reobject.sizel.cx/(HIMETRIC_INCH/MONITOR_DPI)+0.5);
      *h=(int)((float)reobject.sizel.cy/(HIMETRIC_INCH/MONITOR_DPI)+0.5);

      HDC hdc=GetDC(NULL);
      {
        HBITMAP holdbmp;

        HDC hmemdc=CreateCompatibleDC(hdc);
        *hbmp=CreateCompatibleBitmap(hdc, *w, *h);

        holdbmp=(HBITMAP)SelectObject(hmemdc, *hbmp);

        RECT rc;
        SetRect(&rc, 0, 0, *w, *h);
        hr=OleDraw(reobject.poleobj, DVASPECT_CONTENT, hmemdc, &rc);

        SelectObject(hmemdc, holdbmp);
        DeleteDC(hmemdc);
      }
      ReleaseDC(NULL, hdc);
    }
    pREOLE->Release();
  }
  if(hr==S_OK) return true;
  return false;
}