Input Method

Input Methodを作ってみる。難しそうだが、そうでもない。

プロジェクトの作成

最初は、プロジェクトを作る。 WCE ATL COM AppWizardを選ぶ。

WCE ATL COM AppWizardを選択してプロジェクト新規作成
New Project

MFCサポートはなしのまま。
New Project

次のようなスケルトンが出来上がる。

Wizardが作ったSkeleton
Skeleton

オブジェクトを追加

プロジェクトができたら、メニューから[挿入(I)]-[ATLオブジェクトの新規作成(A)]を選んで、ATL オブジェクトウィザードダイアログボックスを表示させる。

ATL オブジェクトウィザード
ATL オブジェクトウィザード

ここで、Simple Objectを選んで、[次へ]をクリックし、ATL オブジェクトウィザードのプロパティダイアログボックスを表示させる。

ATL オブジェクトウィザードのプロパティ
ATL オブジェクトウィザードのプロパティ

名前をつけ、OKボタンをクリックして、オブジェクトを追加する。

IInputMethodのインプリメント

eMbedded Visual C++ 3.0のサンプルにあるdvoraksip.tlbを利用してIInputMethodをインプリメントする。 まず、ワークスペース上にあるオブジェクトを右クリックして、メニューを表示させ、インターフェースのインプリメントを選択する。

インターフェースのインプリメント
インターフェースのインプリメント

警告ダイアログボックス
インターフェースのインプリメントの警告

警告ダイアログボックスのOKボタンをクリックする。

使用可能なタイプライブラリダイアログボックス
使用可能なタイプライブラリ

使用可能なタイプライブラリの参照ボタンをクリックし、eMbedded Visual C++ 3.0のサンプルにあるdvoraksip.tlbを選択する。

インタフェースのインプリメント
インタフェースのインプリメント

IInputMethodを選択する。

ソースの修正

ここで、IMオブジェクトのソースファイルは、次のようになる。
// TestIM.h : Declaration of the CTestIM

#ifndef __TESTIM_H_
#define __TESTIM_H_

#include "resource.h"       // main symbols
#import "C:¥Windows CE Tools¥wce300¥Pocket PC 2002¥samples¥atl¥dvoraksip¥dvoraksip.tlb" raw_interfaces_only, raw_native_types, no_namespace, named_guids 

/////////////////////////////////////////////////////////////////////////////
// CTestIM
class ATL_NO_VTABLE CTestIM : 
	public CComObjectRootEx,
	public CComCoClass,
	public IDispatchImpl,
	public IInputMethod
{
public:
	CTestIM()
	{
	}

DECLARE_REGISTRY_RESOURCEID(IDR_TESTIM)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CTestIM)
	COM_INTERFACE_ENTRY(ITestIM)
	COM_INTERFACE_ENTRY(IDispatch)
	COM_INTERFACE_ENTRY(IInputMethod)
END_COM_MAP()

// ITestIM
public:
// IInputMethod
	STDMETHOD(Select)(wireHWND hwndSip)
	{
		return E_NOTIMPL;
	}
	STDMETHOD(Deselect)()
	{
		return E_NOTIMPL;
	}
	STDMETHOD(Showing)()
	{
		return E_NOTIMPL;
	}
	STDMETHOD(Hiding)()
	{
		return E_NOTIMPL;
	}
	STDMETHOD(GetInfo)(_tagImInfo * pimi)
	{
		if (pimi == NULL)
			return E_POINTER;
			
		return E_NOTIMPL;
	}
	STDMETHOD(ReceiveSipInfo)(_tagSipInfo * psi)
	{
		return E_NOTIMPL;
	}
	STDMETHOD(RegisterCallback)(IIMCallback * lpIMCallback)
	{
		return E_NOTIMPL;
	}
	STDMETHOD(GetImData)(ULONG dwSize, VOID * pvImData)
	{
		if (pvImData == NULL)
			return E_POINTER;
			
		return E_NOTIMPL;
	}
	STDMETHOD(SetImData)(ULONG dwSize, VOID * pvImData)
	{
		return E_NOTIMPL;
	}
	STDMETHOD(UserOptionsDlg)(wireHWND hwndParent)
	{
		return E_NOTIMPL;
	}
};

#endif //__TESTIM_H_
ここで、次の4箇所のコードを変更する。
// TestIM.h : Declaration of the CTestIM

#ifndef __TESTIM_H_
#define __TESTIM_H_

#include "resource.h"       // main symbols

#include <sip.h>

/////////////////////////////////////////////////////////////////////////////
// CTestIM
class ATL_NO_VTABLE CTestIM : 
	public CComObjectRootEx,
	public CComCoClass,
	public IDispatchImpl,
	public IInputMethod
{
public:
	CTestIM()
	{
	}

DECLARE_REGISTRY_RESOURCEID(IDR_TESTIM)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CTestIM)
	COM_INTERFACE_ENTRY(ITestIM)
	COM_INTERFACE_ENTRY(IDispatch)
	COM_INTERFACE_ENTRY(IInputMethod)
END_COM_MAP()

// ITestIM
public:
// IInputMethod
	STDMETHOD(Select)(HWND hwndSip)
	{
		return E_NOTIMPL;
	}
	STDMETHOD(Deselect)()
	{
		return E_NOTIMPL;
	}
	STDMETHOD(Showing)()
	{
		return E_NOTIMPL;
	}
	STDMETHOD(Hiding)()
	{
		return E_NOTIMPL;
	}
	STDMETHOD(GetInfo)(_tagImInfo * pimi)
	{
		if (pimi == NULL)
			return E_POINTER;
			
		return E_NOTIMPL;
	}
	STDMETHOD(ReceiveSipInfo)(SIPINFO * psi)
	{
		return E_NOTIMPL;
	}
	STDMETHOD(RegisterCallback)(IIMCallback * lpIMCallback)
	{
		return E_NOTIMPL;
	}
	STDMETHOD(GetImData)(ULONG dwSize, VOID * pvImData)
	{
		if (pvImData == NULL)
			return E_POINTER;
			
		return E_NOTIMPL;
	}
	STDMETHOD(SetImData)(ULONG dwSize, VOID * pvImData)
	{
		return E_NOTIMPL;
	}
	STDMETHOD(UserOptionsDlg)(HWND hwndParent)
	{
		return E_NOTIMPL;
	}
};

#endif //__TESTIM_H_

さらに、return E_NOTIMPLをreturn S_OKに変更する。 ここまでで、とりあえず、ビルドはできる。しかし、まったく使えないし、InputPanelにも変化はおきない。

レジストリの修正

COMサーバーの登録はレジストリにCOMサーバーの情報を書き込むことで行われる。 InputMethodを作成したときにはその情報もレジストリに書き込む必要がある。 レジストリに書き込まれる情報は、*.rgsファイルに存在している。 次の部分を追加する。

HKCR
{
	Test.TestIM.1 = s 'TestIM Class'
	{
		CLSID = s '{996248C9-39E6-44A1-9E37-386D1F486A67}'
	}
	Test.TestIM = s 'TestIM Class'
	{
		CLSID = s '{996248C9-39E6-44A1-9E37-386D1F486A67}'
		CurVer = s 'Test.TestIM.1'
	}
	NoRemove CLSID
	{
		ForceRemove {996248C9-39E6-44A1-9E37-386D1F486A67} = s 'TestIM Class'
		{
			ProgID = s 'Test.TestIM.1'
			VersionIndependentProgID = s 'Test.TestIM'
			IsSIPInputMethod = s '1'
			ForceRemove 'Programmable'
			InprocServer32 = s '%MODULE%'
			{
				val ThreadingModel = s 'Apartment'
			}
			'TypeLib' = s '{63A3EBFB-859B-4A7C-A1D9-E70A5C652401}'
		}
	}
}

これで、ビルドするとInputPanelに名前は表示されるようになる。

インプットパネルのメニュー
インプットパネルのメニュー

アイコンの作成

16×16と32×16の二つのビットマップをつくる。 それぞれ、IDB_BITMAP1、IDB_BITMAP2というIDをつける。 これを先ほどのコンストラクタでイメージリストを作り、選択させる。
	CTestIM()
	{
		m_hImageList1 = ImageList_Create(16,16,ILC_COLOR | ILC_MASK,1,0);
		HBITMAP hBitmap1 = LoadBitmap(_Module.m_hInst,MAKEINTRESOURCE(IDB_LIST1));
		ImageList_AddMasked(m_hImageList1,hBitmap1,RGB(255,255,255));
		DeleteObject(hBitmap1);

		m_hImageList2 = ImageList_Create(32,16,ILC_COLOR | ILC_MASK,1,0);
		HBITMAP hBitmap2 = LoadBitmap(_Module.m_hInst,MAKEINTRESOURCE(IDB_LIST2));
		ImageList_AddMasked(m_hImageList2,hBitmap2,RGB(255,255,255));
		DeleteObject(hBitmap2);
	}
イメージリストハンドルのメンバ変数も定義しておく。
	HIMAGELIST m_hImageList1;
	HIMAGELIST m_hImageList2;

ここで作ったイメージリストをGetInfo()で渡されるpimiにセットする。

	STDMETHOD(GetInfo)(_tagImInfo * pimi)
	{
		if (pimi == NULL)
			return E_POINTER;

		pimi->hImageNarrow = m_hImageList1;
		pimi->iNarrow = 0;

		pimi->hImageWide = m_hImageList2;
		pimi->iWide = 0;

		RECT rcSipRect  = {0,0,240,80};
		pimi->rcSipRect = rcSipRect;
		return S_OK;
	}

これで、このIMを選択したときに作成したBitmapが表示されるようになる。

インプットパネルのメニュー(アイコンつき)
IMを選択

パネルの作成

パネルを作成する。 次のようなWindowクラスを作る。
#pragma once

#include 

class CIMPanel : public CWindowImpl<CIMPanel>
{
	BEGIN_MSG_MAP(CIMPanel)
		MESSAGE_HANDLER(WM_PAINT,OnPaint)
	END_MSG_MAP()

public:

private:
	LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		PAINTSTRUCT ps;

		HDC hDC = BeginPaint(&ps);

		RECT rc = {0,0,240,80};
		FillRect(hDC,&rc,(HBRUSH)GetStockObject(WHITE_BRUSH));
		POINT pt[] = {{0,0},{239,0}};
		Polyline(hDC,pt,2);
		SetTextColor(hDC,RGB(255,0,0));

		rc.top = 1;

		DrawText(hDC,L"A",1,&rc,DT_CENTER | DT_VCENTER);
		EndPaint(&ps);
		return 0;

	}
};

これをIMクラスのメンバ変数に追加

	CIMPanel	m_panel;

selectメソッドが呼ばれたら、Windowを作成する。

	STDMETHOD(Select)(HWND hwndSip)
	{
		RECT rc = {0,0,240,80};
		m_panel.Create(hwndSip,rc,_T(""),WS_VISIBLE | WS_CHILD);

		return S_OK;
	}

Deselectメソッドが呼ばれたらWindowを壊しときましょう。

	STDMETHOD(Deselect)()
	{
		m_panel.DestroyWindow();
		return S_OK;
	}

とこれで、表示はできるようになりましたが、、パネルをクリックしても何も起こらないの。

入力

最後に入力イベントの実装を行う。 SIPに情報を送るには、RegisterCallbackメソッドを通じて送られてくるIIMCallbackを使用する。 CIMPanelにIIMCallback型のメンバ変数を追加して、RegisterCallbackで送られてきたポインタを保存する。

	STDMETHOD(RegisterCallback)(IIMCallback * lpIMCallback)
	{
		m_panel.m_pCallback = lpIMCallback;
		return S_OK;
	}
CIMPanelの実装はこのようになります。
#pragma once

#include 

class CIMPanel : public CWindowImpl
{
	BEGIN_MSG_MAP(CIMPanel)
		MESSAGE_HANDLER(WM_PAINT,OnPaint)
		MESSAGE_HANDLER(WM_LBUTTONDOWN,OnLButtonDown)
	END_MSG_MAP()

public:
	IIMCallback* m_pCallback;
private:
	LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		PAINTSTRUCT ps;

		HDC hDC = BeginPaint(&ps);

		RECT rc = {0,0,240,80};
		FillRect(hDC,&rc,(HBRUSH)GetStockObject(WHITE_BRUSH));
		POINT pt[] = {{0,0},{239,0}};
		Polyline(hDC,pt,2);
		SetTextColor(hDC,RGB(255,0,0));

		rc.top = 1;

		DrawText(hDC,L"A",1,&rc,DT_CENTER | DT_VCENTER);
		EndPaint(&ps);
		return 0;

	}

	LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		m_pCallback->SendString(_T("A"),1);
		return 0;
	}
};

これで完成。

世界一簡単な(かつ使えない)IM完成
完成