Transitory Extensions, or, how to get full text store support in TSF-unaware controls

As I mentioned earlier, TSF provides very basic services in applications that are not TSF-aware.  In particular, TSF provides only transitory documents and contexts that represent short-lived text compositions.  In Windows Vista, TSF adds full text store support for some frequently-used text controls - plain edit controls, richedit controls (that don't enable TSF), and Trident edit controls in Internet Explorer.

However, text services do not automatically get this text store support; services must inquire if the support is available.  That is, TSF-unaware controls have two documents (and contexts) - the real (transitory) document, and the (TSF-provided) virtual document.  In general, text services may wish to use both documents, depending on the editing purpose - one-shot insertions and corrections should use the virtual document, while the real (transitory) document should be used for keyboard input compositions and other kinds of inputs with transitory states.

So, how does a text service get the virtual document?

  • Your text service will need to implement ITfThreadMgrEventSink, and in the ITfThreadMgrEventSink::OnSetFocus handler, you should check to see if the context on the top of the document stack is transitory:

HRESULT CMyTextService::OnSetFocus(ITfDocumentMgr* pdimFocus, ITfDocumentMgr pdimPrevFocus)

{

bool isTransitory = false;

ITfContext* pITfContext(NULL);

if (SUCCEEDED(pdimFocus->GetTop(&pITfContext))

{

TF_STATUS tfStatus;

if (SUCCEEDED(pITfContext->GetStatus(&tfStatus))

{

     isTransitory = (tsStatus & TS_SS_TRANSITORY) == TS_SS_TRANSITORY;

}

}

  • If the context is transitory, text services can obtain the IUnknown interface pointer to the virtual document from the compartment of the transitory document (the compartment id is GUID_COMPARTMENT_TRANSITORYEXTENSION_PARENT):

CComPtr<ITfDocumentMgr> spParentDIM;

CComQIPtr<ITfCompartmentMgr> spCompartmentMgr(pDimFocus);

CComPtr<ITfCompartment> spCompartment;

if (SUCCEEDED(spCompartmentMgr->GetCompartment(

GUID_COMPARTMENT_TRANSITORYEXTENSION_PARENT, &spCompartment))

{

if (SUCCEEDED(spCompartment->GetValue(&var)) &&
var.vt == VT_UNKNOWN && var.punkVal)

{

     var.punkVal->QueryInterface(
IID_ITfDocumentMgr,
(void**)&spParentDIM);

           VariantClear(&var);

}

}

Once you have the virtual document, your focus change code can use the virtual document as if it were the main document.

Some things to keep in mind:

  • TSF's document focus is not Windows focus. It's by IMM32 and TSF design, that, when there's an open out-of-context composition, and Windows focus is getting switched from one text control to another, the current composition is re-attached along as is, and the transitory document stays focused. That means that to track Windows focus text services shouldn't rely only on TSF's DIM focus change notifications when the focused DIM is transitory, but also should listen to changes in GUID_COMPARTMENT_TRANSITORYEXTENSION_PARENT compartment (advise IID_ITfCompartmentEventSink) on the transitory document, and handle that as if it were a focus change.
  • Text services should not query for the virtual document if it's not going to use it. The virtual documents are instantiated on demand. Once instantiated, it starts listening to the application events and keeps itself synchronized. If the text service does all changes via the transitory document and is not interested in the virtual document, it shouldn't query for ITfDocumentMgr of the virtual document.
  • Text services that use the virtual document should perform all read and write operations via TSF API only, and not mix TSF calls with direct access via windows messages or native API of the applications/controls. That's because the content of TSF-unaware controls may temporarily become out of synch with the virtual document when there are pending updates.