文本服务框架的使用

本节向软件开发者介绍了如何创建文本服务和支持TSF的应用程序的指导方针和标准实现方式。

  在Microsoft®平台软件开发包(SDK)的 samples 文件夹的 winui 子文件夹下提供了一组可供编译的TSF应用程序和文本服务的范例源代码。

  •  公共组成部分

  •  应用程序

  • 文本服务

下列软件组件被用于支持TSF的应用程序文本服务中或者由它们来实现。

  • 线程管理器

  • 客户端标识符 

  • 文档管理器

  • 编辑内容

  • 片段

  • 属性

  • 共享包

  • 输入组合

线程管理器是TSF管理器的基本组成部分,由它完成有关应用程序和文本服务(客户端)之间进行联系的公共任务。这些任务包括(但不限于)激活或挂起TSF文本服务、文档管理器的创建和维护文档与输入焦点之间的正确关联。线程管理器可以通过ITfThreadMgr接口来定义。

  TSF管理器的多数接口和对象都可以使用线程管理器接口提供的方法来获得。

应用程序

  应用程序可以通过带CLSID_TFThreadMgr参数的CoCreateInstance函数来创建一个线程管理器对象。

文本服务

  文本服务在其ITfTextInputProcessor::Activate服务方法中包含了一个线程管理器对象。

事件通知

  线程管理器还负责向客户端发送事件通知。在TSF中,事件通知是通过一个被称之为事件接收器的COM对象来收取的。因此要想获得线程管理器发送的事件通知的话,客户端就应该实现一个ITfThreadMgrEventSink对象并安装事件接收器。事件接收器的安装方法是向线程管理器查询IID_ITfSource并调用带IID_ITfThreadMgrEventSink参数的ITfSource::AdviseSink方法。

客户端标识符

  在文本服务框架中,应用程序和文本服务被定义为客户端。所有客户端都会接收到并必须要维护一个由线程管理器分配的客户端标识符,客户端在调用各种TSF方法的时候通常都需要出示自己的客户端标识符。

应用程序

  应用程序获取客户端标识符的方法是调用 ITfThreadMgr::Activate

文本服务

  文本服务在调用 ITfTextInputProcessor::Activate 方法的时候就会得到它的客户端标识符了。

编辑内容

  TSF定义了一个被称之为“编辑内容”的基本文本输入模型,它可以理解为一个通过 ITfContext 接口创建的连续的文本流,通常是由应用程序创建编辑内容提供给文本服务使用,不过并不是说文本服务就不可以创建编辑内容了。不管是哪种类型的程序创建了编辑内容,在TSF框架中都被理解为 编辑内容所有者

应用程序

  应用程序通过 ITfDocumentMgr::CreateContext 方法来创建编辑内容。

文本服务

  文本服务通常是使用当前活动的编辑内容,也就是位于活动文档管理器堆栈最上面的编辑内容。获得当前活动内容的方法是先通过 ITfThreadMgr::GetFocus 文本服务得到活动文档管理器,然后再用 ITfDocumentMgr::GetTop 得到堆栈最上面的编辑内容。

  有时,文本服务也需要创建自己的编辑内容,这应该使用 ITfDocumentMgr::CreateContext 方法来完成。

编辑信息片段

  很多文本服务方法(比如 ITfRange::SetText )需要某种标识一个编辑内容的方法来实现只读或读/写 文档锁 。文档锁由TSF管理器和应用程序通过协商获得,文本服务不能直接实现这种协商。文本服务只能通过 编辑会话 请求对特定内容进行只读或读/写访问来获得文档锁。开通编辑会话后,文本服务需要提供一个用于标识被请求访问的编辑内容的编辑信息片段。这个信息片段用来出示给文本服务方法以标识实际需要访问的编辑内容。

   ITfDocumentMgr::CreateContext 方法也会提供给编辑内容所有者一个编辑信息片段,这个信息片段具备只读访问级别且不能改变其此访问级别。实际上TSF管理器并没有实现这个信息片段的文档锁,也就是说该信息片段虽然内部标记为只读,但文档并未真正锁定。例如编辑内容所有者用 ITfDocumentMgr::CreateContext 提供的编辑信息片段来调用 ITfContext::GetSelection 的结果,跟应用程序调用 ITextStoreACP::GetSelection ITextStoreAnchor::GetSelection 是一样的。在获取所需的内容之前,应用程序需要确定是否存在文档锁。因为如果没有加锁的话,应用程序就会发生TS_E_NOLOCK异常。因此当应用程序用没有文档锁的信息片段来调用它的一个文本存储方法时,它应该清楚这个情况并自己处理可能发生的问题。

  如果编辑内容所有者需要一个具备读/写访问级别的编辑信息片段,它就应该建立自己的编辑会话。

文档管理器

  文档管理器是通过 ITfDocumentMgr 接口创建并用来管理编辑内容的。每个文档管理器都维护着一个被称之为内容栈的后进先出缓冲区,内容栈里面存储的就是该文档管理器所管理的编辑内容列表。通常内容栈里面只有一个编辑内容,不过根据需要,也可以把其它内容添加到栈中。每个栈最多只允许装载两个内容。

应用程序

  应用程序通过调用 ITfThreadMgr::CreateDocumentMgr 方法来创建文档管理器对象。应用程序应该为它所管理的每份文档创建一个专属的文档管理器对象。应用程序使用文档管理器来创建编辑内容、把内容添加到内容栈和把内容栈中的内容移出来。

文本服务

  文本服务不创建自己的文档管理器对象,而是调用 ITfThreadMgr::GetFocus 方法来获取当前活动的文档管理器对象。文本服务使用文档管理器来获取内容栈最上面的那份编辑内容。

  文本服务也可以使用文档管理器来创建它自己的编辑内容,并把它添加到内容栈或从中删除。这么做的原因是通常文本服务需要显示某种形式的用户界面,比如当显示一个可供用户选择的候选文字列表时,文本服务就把它自己的编辑内容放到内容栈上;当候选列表关闭时,文本服务就把它自己的编辑内容从内容栈中移出。

片段

  在TSF里面,所有文本都存储在一个被称为片段的对象中。片段是一个描述了存在于用作读、写和操作文本的文本流里面的某一小段文本的对象。

  文本片段不与应用程序中文本流上的字符位置(ACP)相关联,而是涉及并与特定的文本段联系在一起,这就使得当该段文本在文本流中的位置发生变化时,也不需要重新定义相应的片段--即片段是可游移的。

  例如,在下面这个文本流中定义了一个片段“真好”:


今天天气<anchor>真好</anchor>,花儿都开了。


  其中<anchor>标签表示片段开始,</anchor>标签表示片段结束。因为片段在包含它的文本流中是可游移的,所以如果在“真好”的前面插入文本“~”的话,片段标签可以带着它的文本片段跟在字符“~“的后边。在上述插入过程中,如果是用ACP的方式来定义片段的话,则最终片段所包含的文本会变成“~真”。比如,在TSF中该插入操作的结果是:


今天天气~<anchor>真好</anchor>,花儿都开了。


  假设是ACP方式定义的片段的话,结果就会变成:


今天天气<anchor>~真</anchor>好,花儿都开了。


  而这显然不是我们想要的。

  当在片段中插入或删除文本时,片段的边界就会相应地伸展或收缩。这种由于片段本身内容的更改所导致的边界伸展或收缩是必然的,但不允许从片段中剪切出文本或把文本粘贴到文本流中。

片段标签

  片段是由一头一尾两个标签界定出来的,分别表示片段的开始和结束。标签本身由小于号“<”和大于号“>”及它们括起来的片段标识符组成。片段开始标签放在要定义为片段的文本段的前面,结束标签则放在文本段的后面。开始和结束标签可以一前一后紧挨着出现,此时表示一个空片段。

  比如,原始文本内容为:

这里是示例文字。


  现在在文本的前面插入紧挨着的开始和结束两个标签,结果得到文本:

<anchor></anchor>这里是示例文字。


  注意上面这对标签之间是没有任何空格的,它表示一个长度为0的空片段。

  现在将结束标签往后移动三个字符位置,结果得到文本:

<anchor>这里是</anchor>示例文字。


  由于开始标签位于第一个字符的前面,结束标签位于第三个字符的后面,因此这对标签界定了内容为“这里是”的文本片段。

  另外请注意:开始标签不能放在结束标签的后面,结束标签也不能放在开始标签的前面。

标签重心

  每个片段标签都有一个重心设置用来说明当文本流在标签位置上插入了文本时该如何进行处理。当标签位置上发生插入操作时,标签的位置肯定相应地要进行调整。重心就是用来决定如何调整标签位置的。

  例如:

今天天气<anchor></anchor>很冷。


  如果在片段位置上插入文本“真的”的话,片段开始标签可能会被调整到已插入文本的前面或后面:

今天天气<anchor>真的</anchor>很冷。


  或:

今天天气真的<anchor></anchor>很冷。


  标签重心用来说明当标签位置上插入文本时,该如何重新调整其位置。有两种重心设置可以选择:前倾或后倾。

  当标签重心设为前倾时,标签相对文本插入位置向前移动,因此插入结果是文本处于标签的后面:

今天天气<anchor>真的</anchor>很冷。


  当标签重心设为后倾时,标签相对文本插入位置向后移动,因此插入结果是文本处于标签的前面:

今天天气真的<anchor></anchor>很冷。


复制和备份

  有两种方法可以“复制”文本片段对象。第一种是使用 ITfRange::Clone 方法来克隆,第二种是使用 ITfContext::CreateRangeBackup 方法来 备份

  克隆将创建片段的一个副本(不包括静态数据),克隆片段将复制源片段的标签并覆盖其文本内容。克隆片段实际上是源片段的全关联对象,也就是说当克隆片段或源片段中某个的文本或属性发生改变时,其它关联片段也会动态地发生同样的改变。

  备份将片段当前的文本和属性作为静态数据存储起来,然后克隆并跟踪源片段的容量和位置发生的变化。也就是说备份片段的文本和属性是静态的,不会随源片段发生改变而改变。

  例如,下面这个文本流中包含了一个标记为pRange的片段:

"这是一段示例<pRange>文字</pRange>。"


  接下来分别克隆和备份这个片段:


  1. ITfRange *pClone;
  2. ITfRangeBackup *pBackup;
  3. pRange->Clone(&pClone);
  4. pContext->CreateRangeBackup(ec, pRange, &pBackup);
复制代码


  此时,片段、克隆和备份对象的内容分别如下:


pRange  = "文字"
pClone  = "文字"
pBackup = "文字"


  然后更改pRange片段的文本:


WCHAR wsz[] = L"中文字";
pRange->SetText(ec, 0, wsz, lstrlenW(wsz));


  此时,各有关对象的内容如下:


文本流内容 = "这是一段示例中文字。"
pRange  = "中文字"
pClone  = "中文字"
pBackup = "文字"


  上面这个改变文本流内容的操作同时导致了pRange和pClone片段的结束标签发生改变。现在pClone的文本内容为“中文字”,因为源片段中的文本内容发生了改变,而这些改变可以被所有克隆片段所跟踪到。当pRange和pClone片段所覆盖的文本发生改变时,pClone的文本同时也被改变了。

  pBackup中的文本没有随着源片段pRange的改变而改变,因为备份片段中的数据(文本和属性)不与文本流内容相关而是单独存储的。对备份片段的内容所做的修改是静态的,也就是说不会影响到文本流和源片段的内容。

  在恢复备份时,备份内容完全可以应用到源片段或另一个片段中去。应用备份内容到源片段的方法是,在调用 ITfRangeBackup::Restore 方法时传递一个空指针给 pRange 参数,比如:

pBackup->Restore(ec, NULL);


  此时,各有关对象的内容如下:

文本流内容 = "这是一段示例文字。"
pRange  = "文字"
pClone  = "文字"
pBackup = "文字"



  恢复备份到另一个片段的方法是,在调用 ITfRangeBackup::Restore 方法时传递目标片段的指针给 pRange 参数,备份文本和属性将应用到指定的目标片段中。比如,在调用 Restore 方法前先对pRange片段做出以下的一些改变:


  1. LONG lShifted;
  2. pRange->ShiftEnd(ec, -2, &lShifted, NULL);
复制代码


  此时,各有关对象的内容如下:


文本流内容 = "这是一段示例中文字。"
pRange  = "中"
pClone  = "中文字"
pBackup = "文字"


   注意: 当pRange片段的结束标签向前移动两个字符位置之后,pClone片段的结束标签没有发生变化。

  接下来恢复备份到pRange中:


  1. pBackup->Restore(ec, pRange);
复制代码


  此时,各有关对象的内容如下:


文本流内容 = "这是一段示例文字文字。"
pRange  = "文字"
pClone  = "文字文字"
pBackup = "文字"


  pRange覆盖的那部分文本被替换为“文字”,被pClone覆盖的那部分文本相应发生改变,而pBackup则修改为与pRange匹配。
属性

  在TSF中,属性被用来关联文本片段和元数据。这些属性包括粗体文本、文本语言标识符和文本服务提供的原始数据(比如从语音文本服务那里得到的和文本相关的音频数据)。

  下面的格式文本实例演示了一个假设的文本色彩属性是如何通过红(R)、绿(G)和蓝(B)三种基本颜色参数来定义任何可能的色彩表现的。


COLOR:      RR      GGGGGGGG
TEXT:  this is some colored text


  不同类型的属性可以叠加在一起。仍以上面这段文本为例,可以为它加上色彩和加粗(B)或倾斜(I)的文本属性。


ATTRIB:BBBBBBB      IIIIIIIIIIII
COLOR:      RR      GGGGGGGG
TEXT:  this is some colored text


  文本“this□”将会被设为加粗,“is“将会被设为加粗和红色,“□some□”将会以常规方式显示,“colored□”将会被设为绿色和倾斜,而“text“则被设为倾斜。

  相同类型的属性不能叠加在一起。比如以下的情形是不允许出现的,因为文本“is“和”colored“重复定义了相同类型的属性值。


COLOR: GGG GGGG RRR BBBBGGG     
COLOR:      RR      GGGGGGGG
TEXT:  this is some colored text


属性类型

  TSF定义了三种不同的属性类型:

静态属性

  静态属性对象把属性数据和文本存储在一起,另外它还存储了属性所应用到的每个文本片段的片段信息。当使用GUID_TFCAT_PROPSTYLE_STATIC参数调用属性对象的 ITfReadOnlyProperty::GetType方法返回成功时,表示该属性为静态属性。

简洁式静态属性

  除了不存储文本片段信息之外,简洁式静态属性与静态属性完全一样。当需要用片段覆盖具备简洁式静态属性的文本时,TSF将会把每段属性相似的文本创建为一个分组,然后创建一个单独的片段覆盖它。对于那些基于每个字符来存储属性的要求来说,简洁式静态属性是最有效的实现方式。当使用GUID_TFCAT_PROPSTYLE_STATICCOMPACT参数调用属性对象的 ITfReadOnlyProperty::GetType方法返回成功时,表示该属性为简洁式静态属性。

定制属性

  定制属性对象中存储了属性所应用到的每个文本片段的片段信息。它不存储属性的实际数据,而是存储了一个 ITfPropertyStore对象。TSF管理器使用这个对象来访问和维护属性数据。当使用GUID_TFCAT_PROPSTYLE_CUSTOM参数调用属性对象的 ITfReadOnlyProperty::GetType方法返回成功时,表示该属性为定制属性。

属性的使用

  获取属性值的方法是使用 ITfReadOnlyProperty接口,而修改属性值的方法则是使用 ITfProperty接口。

  当调用 ITfContext::GetProperty方法时,必须指定一个特定的属性类型。 ITfContext::GetProperty方法需要用一个 GUID来标识想要获取的属性。TSF 定义并广泛应用着一组 预设属性标识符,文本服务也可以定义自己的属性标识符。如果要使用定制属性的话,提供属性的程序必须公布属性 GUID及其数据获取格式。

  比如,要获取某个文本片段所有者的 CLSID时,就应该先用 ITfContext::GetProperty方法获取属性对象,然后用 ITfProperty::FindRange方法得到完整覆盖了该属性的文本片段,最后用 ITfReadOnlyProperty::GetValue方法得到一个 TfGuidAtom类型数据,它代表拥有这个片段的文本服务的 CLSID。以下的示例代码演示这么一种应用编程情形:在给定一个编辑内容、文本片段和编辑信息片段的条件下,编写一个专门的处理函数来获取拥有这段文本内容的文本服务的 CLSID


  1. HRESULT GetTextOwner(   ITfContext *pContext, 
  2.                         ITfRange *pRange, 
  3.                         TfEditCookie ec, 
  4.                         CLSID *pclsidOwner)
  5. {
  6.     HRESULT     hr;
  7.     ITfProperty *pProp;
  8.     *pclsidOwner = GUID_NULL;
  9.     hr = pContext->GetProperty(GUID_PROP_TEXTOWNER, &pProp);
  10.     if(S_OK == hr)
  11.     {
  12.         ITfRange    *pPropRange;
  13.         hr = pProp->FindRange(ec, pRange, &pPropRange, TF_ANCHOR_START);
  14.         if(S_OK == hr)
  15.         {
  16.             VARIANT var;
  17.             VariantInit(&var);
  18.             hr = pProp->GetValue(ec, pPropRange, &var);
  19.             if(S_OK == hr)
  20.             {
  21.                 if(VT_I4 == var.vt)
  22.                 {
  23.                     /*
  24.                     var.lVal is a TfGuidAtom that represents the CLSID of the 
  25.                     text owner. Use ITfCategoryMgr to obtain the CLSID from 
  26.                     the TfGuidAtom.
  27.                     */
  28.                     ITfCategoryMgr  *pCatMgr;
  29.                     hr = CoCreateInstance(  CLSID_TF_CategoryMgr,
  30.                                             NULL, 
  31.                                             CLSCTX_INPROC_SERVER, 
  32.                                             IID_ITfCategoryMgr, 
  33.                                             (LPVOID*)&pCatMgr);
  34.                     if(SUCCEEDED(hr))
  35.                     {
  36.                         hr = pCatMgr->GetGUID((TfGuidAtom)var.lVal, pclsidOwner);
  37.                         if(SUCCEEDED(hr))
  38.                         {
  39.                             /*
  40.                             *pclsidOwner now contains the CLSID of the text 
  41.                             service that owns the text at the selection.
  42.                             */
  43.                         }
  44.                         pCatMgr->Release();
  45.                     }
  46.                 }
  47.                 else
  48.                 {
  49.                     //Unrecognized VARIANT type
  50.                     hr = E_FAIL;
  51.                 }
  52.                 
  53.                 VariantClear(&var);
  54.             }
  55.             
  56.             pPropRange->Release();
  57.         }
  58.         
  59.         pProp->Release();
  60.     }
  61.     return hr;
  62. }

复制代码


  通过 ITfContext::EnumProperties方法返回的 IEnumTfProperties接口,可以列举出所有的属性对象。

属性的永久存储

  通常,属性对于应用程序和使用它们的一个或多个文本服务来说,是临时性的。当应用中出现保存属性数据的需求时(比如保存到一个文件中),应用程序必须把属性数据串接起来(存储过程)或拆解出来(读取过程)。在这种处理模式下,应用程序关心的不是个别属性,而是要列举编辑内容中的所有属性并存储它们。

  在应用程序中要完成属性数据的存储工作就应该通过如下几个步骤:

  1. 调用 ITfContext::EnumProperties方法获得一个属性罗列器。

  2. 通过 IEnumTfProperties::Next方法罗列每一个属性。

  3. 用每个属性的 ITfReadOnlyProperty::EnumRanges方法获得它们各自的文本片段罗列器。

  4. 使用 IEnumTfRanges::Next方法罗列属性中的每个文本片段。

  5. 对于属性中的每个片段,以相应的属性、片段、一个 TF_PERSISTENT_PROPERTY_HEADER_ACP结构和一个由应用程序实现的流对象为参数调用 ITextStoreACPServices::Serialize方法。

  6. 把 TF_PERSISTENT_PROPERTY_HEADER_ACP结构的内容写入永久性存储器。

  7. 把流对象的内容写入永久性存储器。

  8. 重复上述这些步骤把所有属性中的全部文本片段存储起来。

  9. 应用程序需要把某些类型的终止信息写入到流中,这样当读取数据时,就可以通过这些终止信息来标识停止读取的位置。

  以下的示例代码演示了一个方法,应用程序可以用它来串接编辑内容中的属性数据。


  1. HRESULT SaveProperties( ITfContext *pContext, 
  2.                         ITextStoreACPServices *pServices, 
  3.                         TfEditCookie ec, 
  4.                         IStream *pStream)
  5. {
  6.     HRESULT             hr;
  7.     IEnumTfProperties   *pEnumProps;
  8.     TF_PERSISTENT_PROPERTY_HEADER_ACP PropHeader;
  9.     ULONG uWritten;
  10.     
  11.     //Enumerate the properties in the context.
  12.     hr = pContext->EnumProperties(&pEnumProps);
  13.     if(SUCCEEDED(hr))
  14.     {
  15.         ITfProperty *pProp;
  16.         ULONG       uFetched;
  17.         while(SUCCEEDED(pEnumProps->Next(1, &pProp, &uFetched)) && uFetched)
  18.         {
  19.             //Enumerate all the ranges that contain the property.
  20.             IEnumTfRanges   *pEnumRanges;
  21.             hr = pProp->EnumRanges(ec, &pEnumRanges, NULL);
  22.             if(SUCCEEDED(hr))
  23.             {
  24.                 IStream *pTempStream;
  25.                 //Create a temporary stream to write the property data to.
  26.                 hr = CreateStreamOnHGlobal(NULL, TRUE, &pTempStream);
  27.                 if(SUCCEEDED(hr))
  28.                 {
  29.                     ITfRange    *pRange;
  30.                     while(SUCCEEDED(pEnumRanges->Next(1, &pRange, &uFetched)) && uFetched)
  31.                     {
  32.                         LARGE_INTEGER li;
  33.                         //Reset the temporary stream pointer.
  34.                         li.QuadPart = 0;
  35.                         pTempStream->Seek(li, STREAM_SEEK_SET, NULL);
  36.                         
  37.                         //Get the property header and data for the range.
  38.                         hr = pServices->Serialize(pProp, pRange, &PropHeader, pTempStream);
  39.                         /*
  40.                         Write the property header into the primary stream. 
  41.                         The header also contains the size of the property 
  42.                         data.
  43.                         */
  44.                         hr = pStream->Write(&PropHeader, sizeof(PropHeader), &uWritten);
  45.                         //Reset the temporary stream pointer.
  46.                         li.QuadPart = 0;
  47.                         pTempStream->Seek(li, STREAM_SEEK_SET, NULL);
  48.                         //Copy the property data from the temporary stream into the primary stream.
  49.                         ULARGE_INTEGER  uli;
  50.                         uli.QuadPart = PropHeader.cb;
  51.                         hr = pTempStream->CopyTo(pStream, uli, NULL, NULL);
  52.                         pRange->Release();
  53.                     }
  54.                     
  55.                     pTempStream->Release();
  56.                 }
  57.                 
  58.                 pEnumRanges->Release();
  59.             }
  60.             
  61.             pProp->Release();
  62.         }
  63.         
  64.         pEnumProps->Release();
  65.     }
  66.     //Write a property header with zero size and guid into the stream as a terminator
  67.     ZeroMemory(&PropHeader, sizeof(PropHeader));
  68.     hr = pStream->Write(&PropHeader, sizeof(PropHeader), &uWritten);
  69.     return hr;
  70. }

复制代码


  当应用程序调用 TextStoreACPServices::Serialize方法来串接定制属性的时候,TSF管理器将通过 ITfPropertyStore::Serialize方法把属性数据存储到指定的流当中 。

  在应用程序中实现属性数据读取的步骤如下:

  1. 设置流指针让其指向第一个 TF_PERSISTENT_PROPERTY_HEADER_ACP结构开始的地方。

  2. 读取 TF_PERSISTENT_PROPERTY_HEADER_ACP结构。

  3. 通过 TF_PERSISTENT_PROPERTY_HEADER_ACP结构的 guidType成员来调用 ITfContext::GetProperty方法。

  4. 接下来应用程序可以进行以下两项工作之一:

  a. 创建一个 ITfPersistentPropertyLoaderACP对象(由应用程序实现)的实例,然后调用 ITextStoreACPServices::Unserialize方法( pStreamITfPersistentPropertyLoaderACP参数设为空)。

  b. 调用 ITextStoreACPServices::Unserialize(传入输入流参数, pLoader设为空)。

  通常应该首先考虑使用第一个方法,因其效率较高。方法二在调用 ITextStoreACPServices::Unserialize的时候会导致读出流中的所有属性数据,而方法一则只是在最后才读出所需的属性数据。

  5. 重复上述步骤直到所有的属性区块都拆解出来为止。

  以下的示例代码演示了一个方法,应用程序可以用它来把存储在永久性存储器中的属性数据拆解出来。


  1. HRESULT LoadProperties( ITfContext *pContext, 
  2.                         ITextStoreACPServices *pServices, 
  3.                         IStream *pStream)
  4. {
  5.     HRESULT hr;
  6.     ULONG   uRead;
  7.     TF_PERSISTENT_PROPERTY_HEADER_ACP PropHeader;
  8.     /*
  9.     Read each property header and property data from the stream. The 
  10.     list of properties is terminated by a TF_PERSISTENT_PROPERTY_HEADER_ACP 
  11.     structure with a cb member of zero.
  12.     */
  13.     hr = pStream->Read(&PropHeader, sizeof(PropHeader), &uRead);
  14.     while(  SUCCEEDED(hr) && 
  15.             (sizeof(PropHeader) == uRead) && 
  16.             (0 != PropHeader.cb))
  17.     {
  18.         ITfProperty *pProp;
  19.         hr = pContext->GetProperty(PropHeader.guidType, &pProp);
  20.         if(SUCCEEDED(hr))
  21.         {
  22.             /*
  23.             Have TSF read the property data from the stream. This call 
  24.             requests a read-only lock, so be sure it can be granted 
  25.             or else this method will fail.
  26.             */
  27.             CTSFPersistentPropertyLoader *pLoader = new CTSFPersistentPropertyLoader(&PropHeader, pStream);
  28.             hr = pServices->Unserialize(pProp, &PropHeader, NULL, pLoader);
  29.             pProp->Release();
  30.         }
  31.         //Read the next header.
  32.         hr = pStream->Read(&PropHeader, sizeof(PropHeader), &uRead);
  33.     }
  34.     return hr;
  35. }
  36. 公共缓冲池

      在TSF中,客户端程序之间可以通过公共缓冲池来实现数据共享,它为数据共享提供了一个数据存储和消息处理的机制。

      公共缓冲池是通过GUID来标识的,唯有用这个标识号才能访问一个特定的公共缓冲池。客户端程序通过发布GUID来实现一个公共缓冲池的共享,而任意一个别的模块都可以使用公开的GUID来获取相应公共缓冲区的数据。只有已注册为TSF客户端的程序才能修改公共缓冲池的数据,因为在修改数据时必需提供一个TfClientId类型对象。由于公共缓冲池的数据是作为无类型数据存储的,所以在公布一个公共缓冲池的时候,也应该同时把数据类型和格式一并公开。

    公共缓冲池类型

      公共缓冲池分为几种不同的类型:一个全局公共缓冲池,每个线程管理器、文档管理器和编辑内容都可以包含一个公共缓冲池。

      全局公共缓冲池可以让客户端程序之间越过进程来共享数据,用ITfThreadMgr::GetGlobalCompartment方法可以得到全局公共缓冲池管理器。线程管理器包含一个公共缓冲池管理器,它管理着每个线程上的公共缓冲池,这样就实现了线程内的数据共享。用带IID_ITfCompartmentMgr参数的ITfThreadMgr::QueryInterface方法可以获取一个线程管理器的公共缓冲池管理器。

      每创建一个文档管理器就会同时包含一个公共缓冲池管理器,这样就实现了在特定文档管理器中的数据共享。用带IID_ITfCompartmentMgr参数的ITfDocumentMgr::QueryInterface方法可以获取一个文档管理器的公共缓冲池管理器。

      每创建一个编辑内容就会同时包含一个公共缓冲池管理器,这样就实现了在特定编辑内容中的数据共享。用带IID_ITfCompartmentMgr参数的ITfContext::QueryInterface方法可以获取一个编辑内容的公共缓冲池管理器。

    公共缓冲池的创建和删除

      当一个特定的GUID第一次被作为调用ITfCompartmentMgr::GetCompartment方法的参数时,就可以创建一个新的公共缓冲池。在安装客户端程序时应该用ITfCompartment::SetValue方法来初始化公共缓冲池的值,直到设置一个值时,公共缓冲池的值为空为止。之所以要这么做,是因为在调用GetCompartment方法之前,是没有办法检查公共缓冲池是否存在的。为了避免这种情况,在安装客户端程序时应该设置一些初始值,以方便其它客户端程序判断公共缓冲池是否存在。

      用ITfCompartmentMgr::ClearCompartment方法可以清除掉一个公共缓冲池,届时任何对该公共缓冲池的引用都将被标记为无效。

    获取公共缓冲池

      使用ITfCompartmentMgr接口的ITfCompartmentMgr::EnumCompartments方法,客户端程序可以列举出一个公共缓冲池管理器所辖的全部公共缓冲池。该方法通过一个IEnumGUID对象来返回所有已安装的公共缓冲池的GUID

      通过公共缓冲池GUID,就可以借助ITfCompartmentMgr::GetCompartment方法来获取一个指定的公共缓冲池。该方法使用一个ITfCompartment对象来向调用程序返回所要获取的公共缓冲池的数据,同时客户端程序也可以用这个对象来设置(修改)相应公共缓冲池的数据。

    接收公共缓冲池数据更新的消息

      当一个公共缓冲池中的数据内容被改变时,TSF管理器会向所有已安装的信息接收器发送相应的更新消息。安装公共缓冲池更新信息接收器的方法是,创建一个由ITfCompartmentEventSink接口实现的对象,然后用带IID_ITfSource参数的公共缓冲池对象的ITfCompartment::QueryInterface方法,以获取一个可以用来侦测公共缓冲池消息的ITfSource接口。最后通过IID_ITfCompartmentEventSink参数和一个指向ITfCompartmentEventSink对象的指针来调用ITfSource::AdviseSink方法。当公共缓冲池的数据发生更新时,就会触发信息接收器的ITfCompartmentEventSink::OnChange方法,其GUID参数指出了发生更新的那个公共缓冲池。而信息接收器则可以使用ITfCompartment::GetValue方法来获取更新后的数据。
输入组合

  输入组合是一个临时输入状态,它允许一个文本服务把应用程序和输入文字的用户保持在一个不断变化的状态中。应用程序可以且应该获取输入组合的显示属性信息,并使用这个信息来把输入组合状态显示给用户。

  下面来看一个输入组合应用在语音输入中的例子。当用户说话的时候,语音文本服务会创建一个输入组合,该输入组合会被完整保留直到整个语音输入过程完成之后,当会话结束时,语音文本服务就会停止输入组合。

  应用程序根据输入组合是否存在来决定如何显示和显示什么样的文本,并完成相应的处理工作。例如,当用户使用语音输入文字时,应用程序将不在任何输入组合文本上执行任何的拼写和文法检查,因为在输入组合结束前得到的文本被认为是不完整的。

文本服务

  文本服务是通过调用 ITfContextComposition::StartComposition 方法来创建输入组合的,然后可以随时创建一个 ITfCompositionSink 对象来接收输入组合的事件消息。 StartComposition 方法返回一个 ITfComposition 对象,由文本服务维护对该对象的引用并用来更改和结束输入组合。文本服务是通过 ITfComposition::EndComposition 方法来结束输入组合的。

  如果一个文本服务要创建输入组合的话,那它就应该提供在应用程序中区别组合输入文本和常规文本的显示属性支持。有关这方面的更多内容,请查阅 提供显示属性 一节。

应用程序

  通过安装一个 ITfContextOwnerCompositionSink 接收器,应用程序可以跟踪输入组合的创建、更改和结束等情况。当一个输入组合启动时,就会触发 ITfContextOwnerCompositionSink::OnStartComposition 方法。同样地,当输入组合更改或结束时,就会分别触发 ITfContextOwnerCompositionSink::OnUpdateComposition ITfContextOwnerCompositionSink::OnEndComposition 方法。

  以下是一个使用输入组合来更改文档的典型过程:

  1. 使用 ITextStoreACP::InsertTextAtSelection ITextStoreAnchor::InsertTextAtSelection 方法来向输入组合中插入初始文本。

  2. 通过从 InsertTextAtSelection 返回的文本片段,调用 ITfContextComposition::StartComposition 方法来启动输入组合。

  3. 当接收到来自语音或键盘接口的新输入时,应用程序通过 ITextStoreACP::SetText ITextStoreAnchor::SetText 方法来更改输入组合。

  4. 当应用程序确定要结束输入组合时,调用 ITfComposition::EndComposition 方法就可以了。

  应用程序应该使用由文本服务提供的显示属性来随时更改文本的显示,而不仅仅是在输入组合激活的时候。有关这方面的更多内容,请查阅 提供显示属性 一节。

  必要时,应用程序也可以通过 ITfContextOwnerCompositionServices::TerminateComposition 方法来终止一个输入组合。

应用程序

  以下列出的是在 支持TSF的应用程序 中要用到或者需要它去实现的TSF编程要素。

  •  文本存储

  •  文档锁

  •  显示属性的使用

  •  嵌入对象

  •  语言栏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值