For all persistence interfaces other than IPersistMoniker, the authoring tool or container assumes that once IPersist*::Load returns, the control has loaded all of 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 AVI file and begin an asynchronous retrieval of that AVI data before returning from 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 Visual Basic®, for example, might want 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.
In the lifetime of any particular control that uses data paths, there are generally four (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 IViewObject2::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 a control is first instantiated, 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.
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 | IViewObject2::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 maintainthey 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.
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 currently 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 understandfrom the control's documentationwhat 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.