Communicating Control Readiness

For all persistence interfaces other than IPersistMoniker, the authoring tool or container assumes that once IPersist*::Load returns, the control has loaded all its properties. However, controls that use data paths might not actually have all of their data at this time. That is, within IPersistStreamInit::Load, for instance, a control might load a data path property for an Audio-Video Interleaved (AVI) file and begin an asynchronous retrieval of that AVI data before returning from IPersistStreamInit::Load, as shown in a previous section.

When data is coming in asynchronously like this, the control might not be ready to handle all requests the container makes of it. Accordingly, the control might return E_PENDING from some interface member functions. The next section describes the use of E_PENDING.

In addition, certain user-supplied code, in a container such as Microsoft Visual Basic, for example, might need to take action on the current readiness state of a control, such as enabling or disabling one control based on the ability of another control to accept certain calls. The second section below describes a standard property, readyState, that reflects this aspect of a control, and a standard event, onreadystatechange, that informs the container that the state has changed. Because the ReadyState property is primarily of interest to user-supplied code, this standard event is used instead of IPropertyNotifySink::OnChanged to make the event visible in programming tools, and to pass the new ReadyState value along with the event.

Use of E_PENDING

In the lifetime of any particular control that uses data paths, there are generally four, and possibly five, distinct states of readiness that a control might have.

State Description
Uninitialized The control is waiting to be initialized through IPersist*::Load.
Loading The control is initializing itself through one or more asynchronous properties. Some property values might not be available.
Loaded/Can-Render The control has returned from IPersist*::Load so that all its properties are available and it has started any asynchronous data transfers. The control's properties are available and it is ready to draw at least something through IViewObject::Draw. Making properties available doesn't necessarily require the control's type information.
Interactive The control is capable of interacting with the user in at least some limited sense and can supply its type information. Full interaction may not be available until all asynchronous data arrives.
Complete The control is completely ready for all requests.

 

These states are listed in order of priority, which is to say that a control should strive to makes its properties and some basic rendering capabilities available as soon as it can, even if the rendering is to just draw a rectangle with text in it, before going on to become interactive and before being able to fulfill requests that depend on asynchronous data.

From the point in time when an instance of a control is created, certain interface members might return E_PENDING to indicate that the control has not retrieved enough data yet to carry out the request. Note that all interfaces must be available through QueryInterface, although none of their members might do anything yet. To understand the implications, it is useful to step through the moments in time during the loading of a document and the creation and initialization of the controls in that document.

  1. (NonExistent!): The container has loaded the document but has not yet created an instance of any control. At this point, the container can display the document with nothing but rectangle placeholders for each visible control. That is, because no controls are yet loaded, the container cannot call IViewObject::Draw for any of them, and must rely on cached information—extent, label, and so on—to render anything for a control.
  2. Uninitialized: The container has loaded a control, but the control is completely uninitialized—that is, the container has not yet called IPersist*::Load. The control must be loaded first before the container can expect to call any other interface member. If the container wants to draw an image for the control, it must do so itself using any cached information, such as a caption string.
  3. Loading: A control has returned from IPersist*::Load and is making properties available as they arrive. The control must be capable of rendering something minimal, such as a rectangle and text, through IViewObject::Draw and should give priority to retrieving its Caption and Text properties first, followed by rendering-related properties (IDM_BACKCOLOR, IDM_FORECOLOR, DrawStyle, and so on).
  4. Loaded/Can-Render: The control has returned from a call to IPersist*::Load so that it has loaded its properties, or a control that was loading has now retrieved all its immediate properties. At this point, the control must be prepared to at least render something minimal, such as a rectangle and text, through IViewObject::Draw. In simple cases, such as a button, the control will already have all its data necessary for rendering, such as colors, text, font, and styles.
  5. Interactive: The control has enough information to be mostly interactive: it can be in-place activated and perform layout negotiation, handle all property operations, accept user input, and so on. The control also has its type information available. In addition, progressively higher detailed renderings, or other streamed data, might be coming into the control at this point so that the control sends IAdviseSink::OnViewChange notifications to tell the container that the control should be redrawn—that is, if the control doesn't have a window; if it has a window, it just redraws when it wants to.
  6. Complete: The control has loaded all its data, including that obtained through paths, so that it can do more sophisticated renderings using possibly medium- and high-detail graphics.

During each state other than Complete, various interface member functions might not be operative; that is, they will return E_PENDING, and certain properties might not be available. The following table describes which interfaces and methods must be ready in each of these states, if the control supports those interfaces at all.

State Required interfaces/members/reference/properties
Uninitialized IPersist*::Load and IPersist*::InitNew
Loading IViewObject::Draw, IDispatch::Invoke for some properties.
Loaded/Can-Render All of IViewObject2, IDispatch::Invoke for all properties that don't depend on extra data.
Interactive All of IDispatch (methods and type information), all of IRunnableObject, IOleObject, IOleInPlaceObject (and IOleInPlaceActiveObject), IProvideClassInfo2, ISpecifyPropertyPages, IDataObject, IOleControl, IConnectionPointContainer, and IConnectionPoint, with the exception of any operation that depends on asynchronous data that is not yet available, such as IDataObject::GetData for certain formats.
Complete Everything is ready, including other members of IPersist*.

 

Obviously, this puts a burden on a control, which needs to maintain one or more readiness flags that are checked on entry to various interface members so that the function returns E_PENDING if the flag says "not ready." The more distinct states the control chooses to support, the more complex this becomes internally; however, the container doesn't need to differentiate these states: it simply handles E_PENDING by providing some default action of its own where appropriate, then trying the operation again later.

In other words, these states are not something that the container has to internally maintain—they are simply a guide to how a control implementation should be structured to work best in the Internet environment.

Many controls will never have occasion to differentiate between Loaded and Interactive because nearly all simple controls have data dependencies for becoming interactive. The differentiation between the states is made here because some controls with data paths might not be ready for Interactive until at least some data is received asynchronously. Nevertheless, a control should attempt to become interactive as soon as possible.

The OnReadyStateChange Event and ReadyState Property

How does a container know when any given control is ready to render itself, can be interactive with the user, or has completely retrieved all its necessary data for full operation? That is, how does a container know the "readiness" state of a control?

A simple scenario demonstrates the need for the container to have such knowledge. Imagine that you have a form on which there is a video control and a Play button that calls the Play method in the video control. The video control has a VideoPath property set to some URL. When this form is first opened, the video control will not have any data that it will play, so the container will want to disable the Play button immediately. When the video control is asked to load itself, it will load its embedded data and begin an asynchronous transfer of its AVI file. When the transfer is complete, the container will want to enable the Play button.

More precisely, user code will pick up some event from the video control and use that event to enable the button; the container would not generally be hard-coded to do this. In addition, user code outside of a change event might need to know the current readiness state of a control.

For the purposes described here, readiness is defined as one of the following values, each of which corresponds to one of the states described in the previous section.

enum
    {
    READYSTATE_UNINITIALIZED=0, // Never used, except as default
                                // initialization state.
    READYSTATE_LOADING=1,       // Control is loading its properties.
    READYSTATE_LOADED=2,        // Control has been initialized via
                                // IPersist*::Load.
    READYSTATE_INTERACTIVE=3,   // Control is interactive but not all 
                                // data is available.
    READYSTATE_COMPLETE=4       // Control has all its data.
    }

Again, the loading state is only used for controls that are initializing themselves asynchronously through IPersistMoniker::Load. Otherwise, controls begin with READYSTATE_LOADED.

A control makes its current state available through the standard ReadyState property (DISPID_READYSTATE, of type long). Until a control is initialized with IPersist*::Load, the value of this property is undefined—and unavailable, because the control is not ready at all. When a control has loaded its properties, the property must be at least READYSTATE_LOADED. In many cases, the control will not differentiate between loaded and interactive; in that case, the control is interactive immediately and this property reflects at least READYSTATE_INTERACTIVE. In addition, some controls do not differentiate between "loaded" and any other state (such as buttons that load all their properties and have no external data to retrieve); in that case, the property should always be READYSTATE_COMPLETE. A control need not support the ReadyState property at all; in that case, the container always assumes READYSTATE_COMPLETE.

When a control differentiates at least two of these states, it must notify the container of the state transition with the standard control event called onreadystatechange (DISPID_ONREADYSTATECHANGE), which has the following prototype:

[id(DISPID_ONREADYCHANGE)]
    void OnReadyStateChange(long lReadyState);

The state flag passed with this event is one of the READYSTATE_* flags shown above. The general meaning of these states was described in the last section, but to make use of the event, the programmer must understand—from the control's documentation—what methods and properties or other features of the control are available in any given state. In the example, the video control might not say it's interactive until it has at least some of its data, and its definition of complete might mean that it's ready to play. On the other hand, a graphical hyperlink control might describe itself as interactive as soon as it's loaded, because it can receive user input and then only send the complete state when it has actually retrieved all of its graphics.

Because this event is designed for use in user code, the programmer is responsible for understanding what each particular state means for whatever control might be sending it. In the video example, the programmer has read that the video's Play method is not available until the ready state is complete; in that case, the Visual Basic code enables the Play button on READYSTATE_COMPLETE only.

Private Sub video1.OnReadyStateChange(ReadyState as Long)

    if ReadyState=READYSTATE_COMPLETE
        buttonPlay.Enabled=TRUE

End Sub

Controls that do not differentiate between all the states can simply choose to send only the appropriate ones, such as READYSTATE_COMPLETE, if the control is completely ready as soon as it's loaded. Sending "complete" implies that the control has already passed through "loaded" and "interactive." Sending "interactive" implies that "loaded" has already passed but "complete" has not been reached. Again, because user code is what's generally interested in this event, the programmer is assumed to understand any particular control's behavior with this event. User code should not depend on all three states being sent explicitly.

Of course, any control that is not ready to receive certain requests still has to protect itself in case a container makes some request that the control cannot yet fulfill. In that case, the control returns E_PENDING from the interface member function.