属性(通用元素)

文本服务框架 (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”将正常显示,“彩色”将为绿色,斜体和“文本”将倾斜。

同一类型的属性不能重叠。 例如,不允许出现以下情况,因为“is”和“colored”具有相同类型的重叠值。

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

属性类型

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

属性类型 说明
Static 静态属性对象使用文本存储属性数据。 它还存储属性应用于的每个范围的文本信息范围。 ITfReadOnlyProperty::GetType 返回GUID_TFCAT_PROPSTYLE_STATIC类别。
Static-Compact 静态压缩属性对象与静态属性对象相同,但静态压缩属性不存储范围数据。 请求静态压缩属性覆盖的范围时,会为每个相邻属性组创建一个范围。 静态压缩属性是按字符存储属性的最有效方法。 ITfReadOnlyProperty::GetType 返回GUID_TFCAT_PROPSTYLE_STATICCOMPACT类别。
自定义 自定义属性对象存储属性适用的每个范围的范围信息。 但是,它不存储属性的实际数据。 相反,自定义属性存储 ITfPropertyStore 对象。 TSF 管理器使用此对象来访问和维护属性数据。ITfReadOnlyProperty::GetType 返回GUID_TFCAT_PROPSTYLE_CUSTOM类别。

 

使用属性

属性值和属性是使用 ITfReadOnlyProperty 接口获取的,并使用 ITfProperty 接口进行修改。

如果需要特定的属性类型,则 使用 ITfContext::GetPropertyITfContext::GetProperty 需要一个 GUID 来标识要获取的属性。 TSF 定义一组 使用的预定义属性标识符 ,或者文本服务可以定义其自己的属性标识符。 如果使用自定义属性,则属性提供程序必须发布属性 GUID 和获取的数据的格式。

例如,若要获取文本范围的所有者的 CLSID,请调用 ITfContext::GetProperty 以获取属性对象,调用 ITfProperty::FindRange 以获取完全涵盖该属性的范围,然后调用 ITfReadOnlyProperty::GetValue 以获取表示拥有文本的文本服务的 CLSID 的 TfGuidAtom。 下面的示例演示了一个函数,给定上下文、范围和编辑 Cookie 后,将获取 拥有文本的文本服务的 CLSID

HRESULT GetTextOwner(   ITfContext *pContext, 
                        ITfRange *pRange, 
                        TfEditCookie ec, 
                        CLSID *pclsidOwner)
{
    HRESULT     hr;
    ITfProperty *pProp;

    *pclsidOwner = GUID_NULL;

    hr = pContext->GetProperty(GUID_PROP_TEXTOWNER, &pProp);
    if(S_OK == hr)
    {
        ITfRange    *pPropRange;

        hr = pProp->FindRange(ec, pRange, &pPropRange, TF_ANCHOR_START);
        if(S_OK == hr)
        {
            VARIANT var;

            VariantInit(&var);
            hr = pProp->GetValue(ec, pPropRange, &var);
            if(S_OK == hr)
            {
                if(VT_I4 == var.vt)
                {
                    /*
                    var.lVal is a TfGuidAtom that represents the CLSID of the 
                    text owner. Use ITfCategoryMgr to obtain the CLSID from 
                    the TfGuidAtom.
                    */
                    ITfCategoryMgr  *pCatMgr;

                    hr = CoCreateInstance(  CLSID_TF_CategoryMgr,
                                            NULL, 
                                            CLSCTX_INPROC_SERVER, 
                                            IID_ITfCategoryMgr, 
                                            (LPVOID*)&pCatMgr);
                    if(SUCCEEDED(hr))
                    {
                        hr = pCatMgr->GetGUID((TfGuidAtom)var.lVal, pclsidOwner);
                        if(SUCCEEDED(hr))
                        {
                            /*
                            *pclsidOwner now contains the CLSID of the text 
                            service that owns the text at the selection.
                            */
                        }

                        pCatMgr->Release();
                    }
                }
                else
                {
                    //Unrecognized VARIANT type 
                    hr = E_FAIL;
                }
                
                VariantClear(&var);
            }
            
            pPropRange->Release();
        }
        
        pProp->Release();
    }

    return hr;
}

还可以通过从 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. 应用程序应将某种类型的终止符写入流,以便在还原数据时,可以识别停止点。
HRESULT SaveProperties( ITfContext *pContext, 
                        ITextStoreACPServices *pServices, 
                        TfEditCookie ec, 
                        IStream *pStream)
{
    HRESULT             hr;
    IEnumTfProperties   *pEnumProps;
    TF_PERSISTENT_PROPERTY_HEADER_ACP PropHeader;
    ULONG uWritten;
    
    //Enumerate the properties in the context. 
    hr = pContext->EnumProperties(&pEnumProps);
    if(SUCCEEDED(hr))
    {
        ITfProperty *pProp;
        ULONG       uFetched;

        while(SUCCEEDED(pEnumProps->Next(1, &pProp, &uFetched)) && uFetched)
        {
            //Enumerate all the ranges that contain the property. 
            IEnumTfRanges   *pEnumRanges;
            hr = pProp->EnumRanges(ec, &pEnumRanges, NULL);
            if(SUCCEEDED(hr))
            {
                IStream *pTempStream;

                //Create a temporary stream to write the property data to. 
                hr = CreateStreamOnHGlobal(NULL, TRUE, &pTempStream);
                if(SUCCEEDED(hr))
                {
                    ITfRange    *pRange;

                    while(SUCCEEDED(pEnumRanges->Next(1, &pRange, &uFetched)) && uFetched)
                    {
                        LARGE_INTEGER li;

                        //Reset the temporary stream pointer. 
                        li.QuadPart = 0;
                        pTempStream->Seek(li, STREAM_SEEK_SET, NULL);
                        
                        //Get the property header and data for the range. 
                        hr = pServices->Serialize(pProp, pRange, &PropHeader, pTempStream);

                        /*
                        Write the property header into the primary stream. 
                        The header also contains the size of the property 
                        data.
                        */
                        hr = pStream->Write(&PropHeader, sizeof(PropHeader), &uWritten);

                        //Reset the temporary stream pointer. 
                        li.QuadPart = 0;
                        pTempStream->Seek(li, STREAM_SEEK_SET, NULL);

                        //Copy the property data from the temporary stream into the primary stream. 
                        ULARGE_INTEGER  uli;
                        uli.QuadPart = PropHeader.cb;

                        hr = pTempStream->CopyTo(pStream, uli, NULL, NULL);

                        pRange->Release();
                    }
                    
                    pTempStream->Release();
                }
                
                pEnumRanges->Release();
            }
            
            pProp->Release();
        }
        
        pEnumProps->Release();
    }

    //Write a property header with zero size and guid into the stream as a terminator 
    ZeroMemory(&PropHeader, sizeof(PropHeader));
    hr = pStream->Write(&PropHeader, sizeof(PropHeader), &uWritten);

    return hr;
}

ITextStoreACPServices::SerializeITfPropertyStore::Serialize

还原属性数据时,应用程序应执行以下步骤。

  1. 将流指针设置为第一个 TF_PERSISTENT_PROPERTY_HEADER_ACP 结构的开头。

  2. 读取TF_PERSISTENT_PROPERTY_HEADER_ACP结构。

  3. 使用 TF_PERSISTENT_PROPERTY_HEADER_ACP 结构的 guidType 成员调用 ITfContext::GetProperty

  4. 此时,应用程序可以执行以下两项操作之一。

    1. 创建应用程序必须实现的 ITfPersistentPropertyLoaderACP 对象的实例。 然后为 pStream 和 ITfPersistentPropertyLoaderACP 指针调用 ITextStoreACPServices::Unserialize NULL
    2. 将输入流传递给 ITextStoreACPServices::UnserializeNULL (对于 pLoader)。

    第一种方法是首选方法,因为它是最有效的方法。 实现第二种方法会导致在 ITextStoreACPServices::Unserialize 调用期间从流中读取所有属性数据。 第一种方法会导致属性数据在以后按需读取。

  5. 重复上述步骤,直到所有属性块都未序列化。

HRESULT LoadProperties( ITfContext *pContext, 
                        ITextStoreACPServices *pServices, 
                        IStream *pStream)
{
    HRESULT hr;
    ULONG   uRead;
    TF_PERSISTENT_PROPERTY_HEADER_ACP PropHeader;

    /*
    Read each property header and property data from the stream. The 
    list of properties is terminated by a TF_PERSISTENT_PROPERTY_HEADER_ACP 
    structure with a cb member of zero.
    */
    hr = pStream->Read(&PropHeader, sizeof(PropHeader), &uRead);
    while(  SUCCEEDED(hr) && 
            (sizeof(PropHeader) == uRead) && 
            (0 != PropHeader.cb))
    {
        ITfProperty *pProp;

        hr = pContext->GetProperty(PropHeader.guidType, &pProp);
        if(SUCCEEDED(hr))
        {
            /*
            Have TSF read the property data from the stream. This call 
            requests a read-only lock, so be sure it can be granted 
            or else this method will fail.
            */
            CTSFPersistentPropertyLoader *pLoader = new CTSFPersistentPropertyLoader(&PropHeader, pStream);
            hr = pServices->Unserialize(pProp, &PropHeader, NULL, pLoader);

            pProp->Release();
        }

        //Read the next header. 
        hr = pStream->Read(&PropHeader, sizeof(PropHeader), &uRead);
    }

    return hr;
}