2012年4月5日 星期四

Ogre中文輸入 Final修改

之前的Ogre中文輸入Final其實算是堪用了
不過去年網友zevoid在留言中提到
可以使用 SetWindowLong 把Message Handler取代掉這個點子
似乎可以讓修改的幅度更小,程式更精簡
讓我不禁燃起來修改的欲望
只是去年初到年中在忙畢業的事,年底工作又忙,這件事就這樣放置著

最近事情較少,便正式開始執行
找到zevoid網友的blog http://www.wretch.cc/blog/zevoid/651189
重新研究後,在此再發一篇Ogre中文輸入 Final改
讓大家看看是不是更好用

首先最新的處理辦法,就只需要改一個地方:FrameListener的物件
而且不用更動ExampleFrameListener,只要繼承後再處理就行
這樣聽起來就簡單很多,實作如下:

新的FrameListener命名為ImeFrameListener
除了引用並繼承ExampleFrameListener外,當然要加入imm.h
#include <ExampleFrameListener.h>
#include <imm.h>

using namespace Ogre;

class ImeFrameListener : public ExampleFrameListener
{
    public:
      ImeFrameListener( RenderWindow* win, Camera* cam, bool bufferedKeys = false, bool bufferedMouse = false,
        bool bufferedJoy = false )
};
在新的FrameListener裡加入Window Procedure函式來準備取代原先Ogre的
同時,此Window Procedure要宣告為Static才行
這邊的Procedure,我們命名為IMEProc
static LRESULT CALLBACK IMEProc(HWND hwnd, UINT message, 
WPARAM wParam, LPARAM lParam)
另外還需的是,仍然是
GetIMECompResultString(const Ogre::UTFString& tempString)

GetIMECompString(const Ogre::UTFString& tempString)
同時,在兩者前加入virtual後,再加入空的內容
因為這兩者只需在實際使用CEGUI或QuickGUI等GUI時再決定輸入函式
virtual void GetIMECompResultString(const Ogre::String& tempString){};
virtual void GetIMECompString(const Ogre::UTFString& tempString){};
接下來,實作的部分
首先在ImeFrameListener的建構式裡取得目前視窗的Handle,也就是HWND
這邊使用RenderWindow::getCustomAttrubite(const String &name, void* pData)
取得HWND後[1],我們把目前的物件放到Window的UserData中
使用SetWindowLongPtr(HWND, INT, LONG_PTR);
最後使用SetWindowLong(HWND, INT, LONG)
把原本的WindowProcedure替換成IMEProc
 HWND hwnd = NULL;
 win->getCustomAttribute("WINDOW", &hwnd);//取得此視窗的Handler
 SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this);//將此物件指標存入視窗使用者資料
 SetWindowLong(hwnd, GWL_WNDPROC, (LONG)(ImeFrameListener::IMEProc));//設定新的訊息處理

接著就是IMEProc的實作
在switch(message)之前我們要取得ImeFrameListner的物件指標
使用GetWindowLongPtr( HWND, INT )來取得
接下來WM_IME_COMPOSITION的部分就跟之前的一模一樣
只是把ExampleCFrameListener::getSingletonPtr()改成取得的物件指標就好
 //取得目前的物件指標
 ImeFrameListener* ifListener = (ImeFrameListener*)GetWindowLongPtr(hwnd, GWLP_USERDATA);

 //使用UTF字串物件來取得全形字
 UTFString tempString;
 tempString.clear();
  switch (message)
  {
    //自己處理的IME轉換
     case WM_IME_COMPOSITION:

      HIMC hImc = ImmGetContext(hwnd);
      //正式取得轉換的字串
      if (lParam & GCS_RESULTSTR)
      {
       DWORD dwBufLen;
       wchar_t* tCharPtr;
       char* aCharPtr;
       dwBufLen = ImmGetCompositionString( hImc, GCS_RESULTSTR, 0, 0 );
       if(dwBufLen > 0)
       {
        tCharPtr = new wchar_t[dwBufLen];
        aCharPtr = new char[dwBufLen];
        memset( tCharPtr, 0, sizeof(wchar_t) * dwBufLen );
        ImmGetCompositionString( hImc, GCS_RESULTSTR, aCharPtr, dwBufLen );
        DWORD  slength = MultiByteToWideChar(CP_ACP, 0, aCharPtr, dwBufLen, 0, 0 );
        MultiByteToWideChar(CP_ACP, 0, aCharPtr, dwBufLen, tCharPtr, slength);
        tempString = tCharPtr;
        ifListener->GetIMECompResultString(tempString);
       }
      }// if(lParam...
      //取得轉換中的字串
      else if (lParam & GCS_COMPSTR)
      {
       DWORD dwBufLen;
       wchar_t* tCharPtr;
       dwBufLen = ImmGetCompositionStringW( hImc, GCS_COMPSTR, 0, 0 );
       if(dwBufLen > 0)
       {
        tCharPtr = new wchar_t[dwBufLen];
        memset( tCharPtr, 0, sizeof(wchar_t) * dwBufLen );
        ImmGetCompositionStringW( hImc, GCS_COMPSTR, tCharPtr, dwBufLen);
        tempString = tCharPtr;
        ifListener->GetIMECompString(tempString);
       }
       else
       {
        ifListener->GetIMECompString("");
       }
      }// if(lParam...
     ImmReleaseContext(hwnd, hImc);
     break;
  }

這樣,大致就完成了
編譯時,不要忘了再加入imm32的函式庫就成了
哇,真是清爽多了

以上任意取得HWND與替換WndProc除了可以使用IME輸入外
其實像替換掉視窗標題啦,換圖示之類的都辦得到了
應用十分廣,就看大家怎麼用了

最後附上下載完整的ImeFrameListener.h
還有以CEGUI為範本實作輸入法的程式碼OgreMain.cpp
本CEGUI使用最新的0.7.6

沒有留言: