Sam George
Microsoft Corporation
April 2002
日本語版最終更新日 2003 年 10 月 23 日
適用対象 :
Microsoft Windows XP Tablet PC Edition
概要 : インスタント メッセージングを拡張するアーキテクチャとコード サンプルを取得して、Windows XP Tablet PC Edition オペレーティング システムに導入されたインク データ型を組み込みます。他の Microsoft オペレーティング システムでのインクの相互運用性について説明します。(印刷ページ数 : 22 ページ)
MicrosoftR WindowsR XP Tablet PC Edition オペレーティング システムの重要な新機能の 1 つは、開発者がペン ベースの入力のサポートをアプリケーションに簡単に追加できるということです。この入力はインクと呼ばれ、Windows XP Tablet PC Edition オペレーティング システムに含まれた豊富な API を使用すると、この新しいデータ型を簡単に収集、保持、読み込み、操作、変換できます。この記事で取り上げる内容は、次のとおりです。
| • | インクの収集に使用するタブレット PC インク オブジェクト モデル |
| • | Windows XP Tablet PC Edition 以外のオペレーティング システムによるインク サポートのさまざまなレベル |
| • | 収集したインクを他のオペレーティング システムで表示する方法 |
この記事では、さまざまなオペレーティング システム間のインクの相互運用性を取り上げています。インスタント メッセージング クライアントにインク サポートを組み込むためのアーキテクチャについて説明し、Windows XP Tablet PC Edition オペレーティング システムでインクを収集して、他のオペレーティング システムにブロードキャストし、それらのオペレーティング システムでインクをレンダリングする方法を説明します。
Windows XP Tablet PC Edition オペレーティング システムには、インク データ型 (Ink) のサポートが組み込まれていますが、他のオペレーティング システムでこの新しいデータ型がどのように処理されるのかが明確でない場合があります。
Windows XP Tablet PC Edition には、再配布可能マージ モジュール (MSM) があります。これを使用すると、タブレット PC で収集したインクを他のオペレーティング システムでレンダリングできるようになります。マージ モジュールには、Microsoft Windows インストーラで共有コンポーネントをインストールするために必要なすべての情報が含まれています。このマージ モジュールは、以下のオペレーティング システムにインストールできます。
| • | Windows XP Professional |
| • | Windows XP Professional Service Pack 1 |
| • | Windows 2000 Professional Service Pack 2 |
再配布可能マージ モジュールでは、Windows XP Tablet PC Edition のインクの COM ライブラリ、.NET ライブラリ、InkPicture コントロール、InkEdit コントロールがインストールされます。また、COM および .NET の各オブジェクト モデルからインクを収集およびレンダリングする機能が提供されますが、InkPicture コントロールと InkEdit コントロールではインクのレンダリング機能のみが提供されます。
複数のオペレーティング システム上で動作するアプリケーションでは、次の 3 つのレベルのインク サポートを利用できます。
| • | 収集、レンダリング、および認識のサポート。Windows XP Tablet PC Edition でのみ利用できます。 |
| • | 収集の部分的サポートとレンダリングの全面的サポート。再配布可能マージ モジュールがインストールされている、サポート対象のあらゆるオペレーティング システムで利用できます。 |
| • | サポートなし。再配布可能マージ モジュールがサポートされていないため、または単に未インストールであるために、再配布可能マージ モジュールがインストールされていない、あらゆるオペレーティング システムではこのレベルになります。 |
3 つのレベルのインク サポートがありますが、Windows XP Tablet PC Edition 以外のオペレーティング システムにインストールするアプリケーションのセットアップを作成する開発者は、マージ モジュールをインストールするかどうかを選択できます。この選択は、当然ながら、アプリケーション開発者に全面的に任されます。たとえば、インターネットを通じてインストールされたクライアント側の ActiveX コードを持つ Web ベースのアプリケーションでは、マージ モジュールをインストールしないように設定できます。ただし、インクを使用するほとんどの Win32 アプリケーションでは、このモジュールをインストールすることが普通です。
オペレーティング システムのインク サポート レベルを確認する方法は、アプリケーションでプラットフォームのどの部分を使用するかに応じて異なります。たとえば、InkEdit コントロールを使用しているアプリケーションでは、そのコントロールのインスタンスを作成し、InkEdit の InkMode プロパティを確認します。
この記事で説明するアーキテクチャでは InkEdit コントロールは使用しませんが、代わりに、InkOverlay COM オブジェクトを使用します。InkOverlay は、Windows XP Tablet PC Edition 以外の、マージ モジュールがインストールされたオペレーティング システム、および Windows XP Tablet PC Edition のどちらでもインクを収集できます。次のコードは、InkOverlay が存在するかどうかを調べる方法を示しています。オペレーティング システムに InkOverlay が存在する場合は、インクの収集およびレンダリングが可能です。ただし、マージ モジュールをインストールしても、インク認識エンジンはインストールされない点に注意してください。
C++ で実行時にインクサポートレベルを検出する
// タブレット PC オートメーション インターフェイスのヘッダー
#include <atlbase.h>
#include <msinkaut.h>
#include <msinkaut_i.c>
BOOL defaultRecoInstalled;
BOOL InkEnabledOS;
CComPtr<IInkOverlay> spIInkOverlay;
CComPtr<IInkRecognizerContext> spIInkRecognizerContext;
InkEnabledOS = FALSE;
// InkOverlay オブジェクトを作成します。
HRESULT hr = spIInkOverlay.CoCreateInstance(CLSID_InkOverlay);
if (SUCCEEDED(hr))
{
// InkOverlay オブジェクトが存在しています。インクを収集し、レンダリングできます。
InkEnabledOS = TRUE;
}
defaultRecoInstalled = FALSE;
// 既定の認識コンテキストを作成します。
hr = spIInkRecognizerContext.CoCreateInstance
(CLSID_InkRecognizerContext);
if (SUCCEEDED(hr))
{
// 既定の認識コンテキストがインストールされました。インクを認識できます。
defaultRecoInstalled = TRUE;
}
C# で実行時にインクサポートレベルを検出する
using System;
using System.Reflection;
using System.IO;
bool InkEnabledOS = false;
bool defaultRecoInstalled = false;
try
{
// このコードに到達する前に .NET Loader によって例外がスローされないように、
// リフレクションを使用して Microsoft.Ink アセンブリを読み込みます。
// 注 : バージョン番号は、インストールされたバージョンに一致するように変更する必要があります。
Assembly asm =
Assembly.Load("Microsoft.Ink, Version=1.0.XXXX.X,
Culture=neutral, PublicKeyToken=35ac7114bc3930a2");
// リフレクションを使用して InkOverlay のインスタンスを作成します。
Type InkOverlayType = asm.GetType("Microsoft.Ink.InkOverlay");
ConstructorInfo InkOverlayConstructor =
InkOverlayType.GetConstructor(new Type[0]);
// InkOverlay がインストールされていない場合は、これがスローされます。
object InkOverlay = InkOverlayConstructor.Invoke(new object[0]);
InkEnabledOS = true;
// リフレクションを使用して RecognizerContext のインスタンスを作成します。
Type InkRecoContextType =
asm.GetType("Microsoft.Ink.RecognizerContext");
ConstructorInfo InkRecoContextConstructor =
InkRecoContextType.GetConstructor(new Type[0]);
// RecognizerContext がインストールされていない場合は、これがスローされます。
object InkRecoCOntext =
InkRecoContextConstructor.Invoke(new object[0]);
defaultRecoInstalled = true;
}
catch(FileNotFoundException)
{
// 再配布可能マージ モジュールがインストールされていない、Windows XP Tablet PC Edition 以外の OS で実行します。
// Microsoft.Ink.dll が見つかりませんでした。
}
catch(Exception)
{
// InkOverlay または RecognizerContext が存在しません。
// ブール値を確認してどちらが存在しないかを判断します。
}
::GetSystemMetrics(SM_TABLETPC) を呼び出して、アプリケーションが Windows XP Tablet PC Edition オペレーティング システム上で実行されているどうかを確認することも可能です。ただし、戻り値がゼロ以外である場合は、InkOverlay または RecognizerContext がインストールされているかどうかはわかりません。Windows XP Tablet PC Edition SDK では、次のように説明しています。
"Windows GetSystemMetrics API を使用して、SM_TABLETPC をインデックス値として渡します (SM_TABLETPC は Winuser.h で定義します)。Microsoft Windows XP Tablet PC Edition オペレーティング システムが実行されていれば、メソッドは true かゼロ以外の値を返し、それ以外の場合は false かゼロを返します。"
"true またはゼロ以外の値が返されても、Microsoft Windows XP Tablet PC Edition のすべてのコンポーネントがインストール済みで正常に動作しているとは限らないので、アプリケーションではこの戻り値に依存しないようにする必要があります。コントロールが存在しているどうかを判断するには、そのコントロールのインスタンスを作成する必要があります。"
Windows 95 から Windows XP までのすべての Microsoft オペレーティング システムは、いずれかのバージョンの Rich Edit コントロールを備えています。このコントロールを使用して、RTF 形式のテキストや GIF などの埋め込みオブジェクトを表示できます。Windows XP Tablet PC Edition オペレーティング システムでは、Rich Edit 3.0 コントロールのスーパーセットである InkEdit コントロールが導入されています。InkEdit コントロールでは、インクをテキストに変換する機能、RTF テキストをインクで表示する機能など、Rich Edit 3.0 コントロールに多数の新機能が追加されます。
最初のバージョンの Windows XP Tablet PC Edition オペレーティング システムでは、周囲のテキストが変化してもサイズが変化しないインク コンテナをプログラムで追加する InkEdit コントロールはサポートされていません。また、InkEdit コントロールは、インクの選択および削除のために InkOverlay で提供される追加の EditingMode をサポートしません。これらの 2 つの理由から、このアーキテクチャの場合、チャット入力では Rich Edit コントロールに InkOverlay を組み合わせて使用し、チャット履歴では Rich Edit コントロールを使用することをお勧めします。
したがって、テキストとインク (Windows XP Tablet PC Edition 以外の OS の場合はインクを表現する GIF) の両方をレンダリングできる優れたチャット履歴を構築するには、簡単な OLE コンテナの構築も必要になるので、やや複雑な処理となります。このコントロールの詳細については、この記事の後半で説明しています。ここでは、このアーキテクチャの概要を眺めてみます。
これまでに、オペレーティング システムのインク サポート レベルの確認方法を習得し、必要なコントロールやインク オブジェクトについての基礎知識を得ました。次はアーキテクチャを調査し、インスタント メッセージングを拡張してインク データ型を組み込みます。

図 1. インクの収集とブロードキャストのシナリオ
図 1 では、Windows XP Tablet PC Edition オペレーティング システムでインクを収集し、他の 3 つのクライアントにブロードキャストしています。これらのクライアントでは、インクのサポート レベルがそれぞれ異なる、次のオペレーティング システムが実行されています。
Windows XP Tablet PC Edition オペレーティング システム : このオペレーティング システムは、"Tablet PC OS" として示されています。
Windows XP Tablet PC Edition の再配布可能マージ モジュールをサポートおよびインストールした、Windows XP Tablet PC Edition 以外のオペレーティング システム : このオペレーティング システムは、"Redist OS" として示されています。
Windows XP Tablet PC Edition の再配布可能マージ モジュールがインストールされていない、Windows XP Tablet PC Edition 以外のオペレーティング システム : このオペレーティング システムは、"Non-Redist OS" として示されています。
図のステップ 1 から開始します。
1. | Windows XP Tablet PC Edition オペレーティング システム上で実行されているインスタント メッセージング クライアントでインクを収集します。インスタント メッセージング クライアントは、InkOverlay が組み合わされた Rich Edit コントロールを使用してインクを収集し、ブロードキャストに備えてインク オブジェクトの Save メソッドに PersistenceFormat.Gif オプションを指定してインクを保存します。このように収集または保存されたデータは、GIF の構築、またインク オブジェクトへの読み込みに使用できます。 |
2. | インク データが埋め込まれた GIF データを、他の 3 つのクライアントにブロードキャストします。このデータの転送方法 (TCP/IP、HTTP など) は、この記事では扱いません。 |
インク データが埋め込まれた GIF データを各クライアントが受信し、各クライアントはそれぞれの方法でレンダリングします。一番左側のクライアント ("Tablet PC OS") は、Windows XP Tablet PC Edition オペレーティング システム上で実行されていて、チャット履歴ウィンドウで Rich Edit コントロールを使用しています。このクライアントは、インクを OLE オブジェクトに挿入し、その OLE オブジェクトを Rich Edit コントロールに挿入します。この OLE コントロールは、検出されたインク サポートのレベルに応じて、データをインクとして表示するか、GIF として表示するかを決定します。この最初のクライアントでは、インクをインクとしてレンダリングします。
中央のクライアント ("Redist OS") は、Windows XP Tablet PC Edition オペレーティング システム上では実行されていませんが、再配布可能マージ モジュールがインストールされています。このクライアントも、Rich Edit コントロールに挿入された OLE コントロールを使用してインク データをレンダリングします。
一番右側のクライアント ("Non-Redist OS") は、Windows XP Tablet PC Edition オペレーティング システム上で実行されていないことに加え、再配布可能マージ モジュールもインストールされていません。したがって、このクライアントは OLE コントロールを使用し、OLE コントロールを Rich Edit コントロールに挿入します。その OLE コントロールではインク データから GIF が作成されます。
図 1 を見ると、Tablet PC OS と Redist OS の各クライアントのチャット履歴ウィンドウで、"Hello" と表示されたインクに "Ink - Editable" というラベルが表示されています。このラベルが表示された場合は、インクがインクとして保存されたことを示しています。このインクを Rich Edit 入力コントロールにドラッグして注釈を付けることができます。要約すると、これは Rich Edit チャット入力コントロールにドラッグ アンド ドロップを実装することで得られる機能です。インクを含む OLE コンテナをドロップすると、Rich Edit チャット入力コントロールに連結された InkOverlay にコンテナのインクがコピーされます。この機能を使用すると、前に受信したインク チャットをチャット履歴から取り出し、注釈を付けることができます。Non-Redist OS では、Rich Edit チャット入力コントロールに連結された InkOverlay がなく、インクをコピーできないため、この機能は使用できません。
これでインク チャット アーキテクチャの概要を把握できたので、今度はアーキテクチャを詳しく調べてみます。最初に、どのような方法でインクを保存すれば、インクを GIF に再構築できるのか、またインク オブジェクトの Load メソッドで読み込み、インクを忠実に再現して、Strokes コレクションのインスタンスを作成できるのかを調べます。
Windows XP Tablet PC Edition の COM および .NET API は、インクをいくつかの形式で保存できるメソッドを備えています。相互運用可能なインク チャットを実現するために、ここでは GIF 形式を使用します。GIF 形式には、レンダリングされないメタデータを格納できる拡張セクションがあります。Windows XP Tablet PC Edition の COM および .NET API では、この機能を利用してインク データを GIF 表現と共に格納します。これを次のコード例に示します。
C++ でインクを相互運用可能な GIF 形式で保存する
// タブレット PC オートメーション インターフェイスのヘッダー #include <atlbase.h> #include <msInkaut.h> #include <msInkaut_i.c> CComPtr<IInkOverlay> spIInkOverlay; CComPtr<IInkDisp> spIInkDisp; // InkOverlay は既にインクを持っているとします。 // インク オブジェクトへのポインタを取得します。 spIInkOverlay->get_Ink(&spIInkDisp); // “拡張 GIF 形式” を指定して Save を呼び出します。 CComVariant svarInkData; spIInkDisp->Save(IPF_GIF, IPCM_Default, &svarInkData); // svarInkData に VT_UI1 (バイト) の SafeArray が作成されました。
C# でインクを相互運用可能な GIF 形式で保存する
using Microsoft.Ink;
Microsoft.Ink.InkOverlay InkOverlay;
// 拡張 GIF 形式 を指定して Save を呼び出します。
byte[] InkData =
InkOverlay.Ink.Save(Microsoft.Ink.PersistenceFormat.Gif);
// InkData が、インク データを含むバイトの配列として初期化されました。
この形式でインクを保存すると、待機中の各クライアントにブロードキャストできます。このブロードキャストの際、インクをレンダリングできるクライアント宛てにはインクへの変換が可能な形式が使用され、インクをレンダリングできないクライアント宛てには GIF への変換が可能な形式が使用されます
待機中のクライアント宛てに回線を通じて転送されたデータは、BYTE の VARIANT 配列 (C++ の場合) または byte[] 配列 (C# の場合) に戻された後、レンダリングされます。クライアントのオペレーティング システムが持つインク サポート レベルを確認済みであれば、次のコードを使用して GIF またはインクをレンダリングできます。このアーキテクチャの場合、このコードは Rich Edit 履歴コントロールに挿入される OLE コントロール (C++ の場合) に存在します。C# の場合は、System.Windows.Forms.Panel から継承され、InkOverlay を使用するコントロール (または InkOverlay を使用する他のいずれかの簡易コントロール) を使用できます。
C++ でインクを読み込む
//
// 拡張 GIF データを持つ内部インク オブジェクトを読み込み、
// インクを表示できるコンピュータで実行している場合は
// インクを表示します。
//
STDMETHODIMP SampleOleControl::LoadInkGifData(VARIANT Data)
{
if(Data.vt != (VT_ARRAY | VT_UI1))
{
//この場合は、データの種類が不適切です。
return E_INVALIDARG;
}
if(InkEnabledOS)
{
//
// インク オブジェクトへのポインタを取得します。
//
HRESULT hr = spIInkOverlay->get_Ink(&spIInkDisp);
if(FAILED(hr))
{
return hr;
}
//
// インク データを読み込みます。
//
hr = spIInkDisp->Load(Data);
if(FAILED(hr))
{
return hr;
}
}
else
{
//
// GIF を作成し、読み込みます。
//
CImage* pImage = new CImage();
//
// インクの GIF データに対する IStream インターフェイスを取得します。
//
CComPtr<IStream> spIStream;
HRESULT hr = CreateStreamFromSafeArray(&Data, &spIStream);
if(FAILED(hr))
{
return hr;
}
//
// GIF を作成します。
//
hr = pImage ->Load(spIStream);
if(FAILED(hr))
{
return hr;
}
}
return S_OK;
}
//
// バイトの VARIANT 配列を受け取り、
// IStream インターフェイスをその配列に返します。
//
HRESULT SampleOleControl::CreateStreamFromSafeArray
(
VARIANT *pvSafeArray, IStream **ppIStream
)
{
HRESULT hr = S_OK;
*ppIStream = NULL;
SAFEARRAY *psa = (SAFEARRAY *)(pvSafeArray->parray);
ULONG ulSize = psa->rgsabound[0].cElements;
if (ulSize == 0)
return E_FAIL;
HGLOBAL hMem = ::GlobalAlloc(GHND, ulSize);
if (hMem == NULL)
return E_OUTOFMEMORY;
byte *pb;
hr = SafeArrayAccessData(psa, (void **)&pb);
if (FAILED(hr))
return hr;
if (! pb)
return E_FAIL;
// インク データをグローバル メモリ ブロックにコピーします。
byte *pbStm = (byte *)::GlobalLock(hMem);
::memcpy(pbStm, pb, ulSize);
::GlobalUnlock(hMem);
::SafeArrayUnaccessData(psa);
hr = ::CreateStreamOnHGlobal(hMem, TRUE, ppIStream);
return hr;
}
C# で実行時にインクサポートレベルを検出する
private LoadInkGifData(byte[] InkData)
{
if(InkEnabledOS)
{
//
// インクを InkOverlay に読み込みます。
//
InkOverlay.Ink.Load(InkData);
}
else
{
//
// メモリ ストリームを取得します。
//
MemoryStream stream = new MemoryStream(InkData);
stream.Position = 0;
//
// このデータを使用してイメージを作成します。
//
Image image = Bitmap.FromStream(stream);
stream.Close();
}
}
これで、回線を通じ、待機中のクライアントにインクが転送され、インクに対するオペレーティング システムのサポート状況に応じてデータをインクまたは GIF に変換するクライアントが用意されました。次に、Rich Edit チャット入力コントロールでインクを修正するには何が必要であるかを調べます。この機能は、追加したインクを削除したり、インクを選択して移動したりする際に役立ちます。また、チャットのインクの色、幅、および透明度もカスタマイズできます。ここでは、次の操作のための方法を調べます。
1. | InkOverlay に組み込まれたサポートを使用してインクを選択する。 |
2. | InkOverlay に組み込まれたサポートを使用してインクを削除する。 |
3. | インクの外観を変更する。 |
タブレット PC の COM および .NET オブジェクト モデルがあるので、この操作はたいへん簡単です。次のコードは、InkOverlay を選択モードまたは削除モードに切り替える方法を示しています。
C++ で InkOverlay を選択モードまたは削除モードに切り替える
// タブレット PC オートメーション インターフェイスのヘッダー #include <atlbase.h> #include <msInkaut.h> #include <msInkaut_i.c> // // オーバーレイを無効にし、削除モードに切り替えます。 // spIInkOverlay->put_Enabled(VARIANT_FALSE); spIInkOverlay->put_EditingMode(IOEM_Delete); // 注 : 選択モードに切り替えるには、上の行をコメント行にして、 // 次の行を使用します。 // spIInkOverlay->put_EditingMode(IOEM_Select); // // 削除を指定します。 // spIInkOverlay->put_EraserMode(IOERM_StrokeErase); // // オーバーレイを再び有効にします。 // spIInkOverlay->put_Enabled(VARIANT_TRUE);
C# で InkOverlay を選択モードまたは削除モードに切り替える
sing Microsoft.Ink; // // オーバーレイを無効にし、削除モードに切り替えます。 // InkOverlay.Enabled = false; InkOverlay.EditingMode = InkOverlayEditingMode.Delete; // // 削除を指定します。 // InkOverlay.EraserMode = InkOverlayEraserMode.StrokeErase; // 注 : 選択モードに切り替えるには、上の行をコメント行にして、 // 次の行を使用します。 // InkOverlay.EditingMode = InkOverlayEditingMode.Select; // // オーバーレイを再び有効にします。 // InkOverlay.Enabled = true;
今度は、不透明な細いインクを、太い半透明のインクに変更する方法を調べます。
C++ でインクの DrawingAttributes を変更する
// タブレット PC オートメーション インターフェイスのヘッダー
#include <atlbase.h>
#include <msInkaut.h>
#include <msInkaut_i.c>
//
// 既定の描画属性を変更します。
//
CComPtr<IInkDrawingAttributes> spIInkDrawAttrs = NULL;
hr = spIInkOverlay->get_DefaultDrawingAttributes(&spIInkDrawAttrs);
if (SUCCEEDED(hr))
{
//
// 幅と透明度を変更します。
//
spIInkDrawAttrs->put_Width((float)200);
spIInkDrawAttrs->put_Transparency(120);
//
// 新しい描画属性を設定します。
//
spIInkOverlay->putref_DefaultDrawingAttributes(spIInkDrawAttrs);
}
C# でインクの DrawingAttributes を変更する
using Microsoft.Ink; // // 新しい描画属性を設定します。 // InkOverlay.DefaultDrawingAttributes.Width = 200F; InkOverlay.DefaultDrawingAttributes.Transparency = 120;
テキスト入力ウィンドウをインク データの機能的なエディタとして完成させるには、InkOverlay のスクロールについて詳しく調査し、ウィンドウより大きなインクを作成、編集できるようにする必要があります。ウィンドウをスクロールする場合は、すべての表示コードでスクロール バーの位置を考慮する必要があります。再表示はすべて InkOverlay によって処理されるので、必要な作業は、スクロール バーの位置が変わるたびに InkOverlay のレンダラに関連付けられた ViewTransform を更新することです。PixelToInkSpace 関数では、レンダラに現在適用されているすべての変換が考慮されるので、インクの移動量を計算するときにはその点を注意する必要があります。この影響を避けるには、同時に (0,0) を変換し、その差を使用します。
C++ で InkOverlay をスクロールする
// ウィンドウ プロシージャ case WM_HSCROLL: // ScrollInfo 構造体と SetScrollInfo() を更新します。 // ... // スクロール情報を取得します。 GetScrollInfo(hWnd, SB_HORZ, &si); // インク スペースに変換します。レンダラの ViewTransform に更新を適用するので、 // インク座標の原点が // 画面に対して移動している場合があります。 xScroll = -si.nPos; g_pIInkRenderer->PixelToInkSpace(hdc, &xScroll, &yScroll); g_pIInkRenderer->PixelToInkSpac(hdc, &xOrigin, &yOrigin); // インクに変換を適用します。 g_pIInkTransform->put_eDx(xScroll-xOrigin); g_pIInkRenderer->SetViewTransform(g_pIInkTransform); // インクが移動しました。正しく再描画されることを確認します。 InvalidateRgn(hWnd, NULL, true); UpdateWindow(hWnd); break;
C# で InkOverlay をスクロールする
void hScrollBar1_OnScroll(object sender, ScrollEventArgs args)
{
xCurrentScroll = args.NewValue;
// インク スペースに変換します。レンダラの ViewTransform に更新を適用するので、
// インク座標の原点が
// 画面に対して移動している場合があります。
Point scrollAmount = new Point(-xCurrentScroll, -yCurrentScroll);
Point origin = new Point(0, 0);
Graphics g = CreateGraphics();
inkOverlay.Renderer.PixelToInkSpace(g, ref scrollAmount);
inkOverlay.Renderer.PixelToInkSpace(g, ref origin);
// 現在のスクロール位置の x 座標と y 座標に基づいて
// 変換を作成します。
Matrix m = new Matrix();
m.Translate(scrollAmount.X-origin.X, scrollAmount.Y - origin.Y);
inkOverlay.Renderer.SetViewTransform(m);
// インクが移動しました。正しく再描画されることを確認します。
Invalidate();
}
Rich Edit コントロールは、チャット履歴ウィンドウの実装に必要なすべてを簡単にサポートできます。Rich Edit コントロールでインク ストロークをレンダリングするには何が必要であるかを詳しく調べてみます。この記事の目的のうえから実際に必要となるのは、チャット履歴ウィンドウにオブジェクトを追加し、それを履歴ウィンドウから入力ウィンドウにコピーして編集できるようにする機能のみです。ATL の Minimal Control ウィザードを使用すると、ほとんど手間をかけずにあらゆる面で機能的な OLE コントロールを作成できるので、簡単にするためにそこから開始します。
OLE コントロールでインクをレンダリングする
基本コントロールにはウィンドウ ハンドルがないので、チャット入力で使用した InkOverlay は使用せずに、この基本コントロールの OnDraw メソッドで InkRenderer オブジェクトの Draw メソッドを使用してストロークを描画します。ここで注意することは、ウィンドウを持たない OLE コントロールを使用する場合、普通はその親コントロールのデバイス コンテキストでこのコントロールの描画が必要になる点です。InkRenderer で正しい位置にストロークを描画するには、すべてのストロークを移動して適切な場所に配置する必要があります。このために必要な機能は、InkRenderer の ViewTransform オブジェクトに用意されています。
if(m_inkEnabledOS)
{
// この四角形は、コントロール自体に必ずしも
// 関係がありません。これは、含まれているコントロールの
// 座標空間にあるコントロールの四角形です。インクに
// ビュー変換を適用して、この四角形の内部にインクを
// レンダリングする必要があります。
CComPtr<IInkStrokes> spIStrokes;
// 四角形領域の左上を取得します。
long x, y;
x = di.prcBounds->left;
y = di.prcBounds->top;
m_spIInkDisp->get_Strokes(&spIStrokes);
// HIMETRIC に変換します。
m_spIInkRenderer->PixelToInkSpace((long)di.hdcDraw, &x, &y);
// 既存の変換を取得します。
m_spIInkRenderer->GetViewTransform(m_spIInkTransform);
// 変換を設定します。
m_spIInkTransform->Translate((float)x, (float)y);
// 変換を適用します。
m_spIInkRenderer->SetViewTransform(m_spIInkTransform);
m_spIInkRenderer->Draw((long)(di.hdcDraw), spIStrokes);
}
OLE コントロールでドラッグアンドドロップをサポートする
Rich Edit コントロールの OLE オブジェクトをドラッグまたはコピーすると、そのオブジェクトの GetClipboardData() メソッドが呼び出され、クリップボードのデータが取得されます。このオブジェクトにはインク データ以外のデータは含まれていないので、インク オブジェクトの ClipboardCopy メソッドを使用してすべての操作をインク オブジェクトで処理します。
STDMETHODIMP SampleOLEControl::GetClipboardData(DWORD dwReserved,
IDataObject **ppDataObject)
{
HRESULT hr;
// インク オブジェクトですべての操作を処理します。
hr = m_spIInkDisp->ClipboardCopy(NULL, ICF_Default,
ICB_ExtractOnly, ppDataObject);
return hr;
}
C++ で OLE コントロールを Rich Edit で使用する
コントロールが完成したので、これをチャット履歴ウィンドウに挿入し、チャット履歴からチャット入力にドラッグして編集できるようにします。サポート技術情報 141549 に、Rich Edit コントロールで OLE コントロールを使用する場合の一般的なコード例があります。
C++ で OLE オブジェクトを Rich Edit にドロップまたは貼り付ける
チャット入力ウィンドウでは、Rich Edit コントロールに貼り付けるインク データを取得して、Rich Edit に組み合わせた InkOverlay に貼り付けます。Rich Edit コントロールの既定の動作を拡張するすべての機能は、IRichEditOleCallback インターフェイスを継承するクラスの実装によって実現します。このトピックでは、QueryAcceptData() コールバックを使用した貼り付け操作の制御に重点を置いているので、他のすべてについては E_NOTIMPL を返すにとどめています。
//
// 関数 : CRichEditOleCallback::QueryAcceptData
//
// 目的 : この関数を呼び出して、貼り付けたデータまたはドラッグされたデータを
// 受け入れるかどうかを確認します。インク関連の要求は、
// ここでは受け付けません。
//
HRESULT CRichEditOleCallback::QueryAcceptData(LPDATAOBJECT pDataObj ,
CLIPFORMAT* pcfFormat,
DWORD /* reco */,
BOOL fReally ,
HGLOBAL /* hMetaPict */)
{
HRESULT hr;
CComPtr<IInkStrokes> spIStrokes;
VARIANT_BOOL canPaste;
// InkOverlay のインク オブジェクトがこの貼り付け操作を処理できるかどうかを確認します。
m_spIInkDisp->CanPaste(pDataObj, &canPaste);
if (canPaste == VARIANT_FALSE)
// これを貼り付けないことを RichEdit に通知します。
return E_FAIL;
if (!fReally)
// 貼り付け可能であるが、貼り付けなかったことを RichEdit に通知します。
return S_FALSE;
// InkOverlay が貼り付けを処理するよう試みます。
hr = m_spIInkDisp->ClipboardPaste(0, 0, pDataObject,
&spIStrokes);
if(SUCCEEDED(hr))
// 貼り付けが処理されたことを RichEdit に通知します。
return S_FALSE;
// そのまま RichEdit で貼り付けを処理します。
return S_OK;
}
上記のコピーと貼り付け操作では、ストロークのインク サーフェスとの相対位置が保存されていない点に注意してください。チャット履歴からチャット入力ウィンドウにドラッグされたインクはすべてウィンドウの左上隅に表示されます。この動作を変更する場合は、コピー操作を実行する前に、拡張プロパティを使用して相対位置をインク オブジェクトに付加します。上記のコードで貼り付けの位置を変更する場合は、座標を取得し、その位置に応じて貼り付けられたストロークを移動します。
タブレット PC API には、アプリケーションのペン入力を編集、保存、変換するための豊富なインターフェイスがあります。再配布可能マージ モジュールを使用すると、タブレット PC 以外のコンピュータ上でインクを収集およびレンダリングできます。また、拡張 GIF として保存できるということは、任意のコンピュータ上でインクを表示でき、後で編集できる状態でインクが保存されることを意味します。