Contents Index Topic Contents | ||
Previous Topic: Cooperative and Asynchronous Data Retrieval Next Topic: Other Considerations |
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 of its properties. However, controls that use data paths may 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 an earlier section.
When data is coming in asynchronously like this, the control may not necessarily be ready to handle all requests the container might make of it. Accordingly, the control may 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 like Visual Basic for example, may 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.
Use of E_PENDING
In the lifetime of any particular control that uses data paths, there are generally four (possibly five) distinct states of readiness that a control may 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 may 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 may 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 may 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.
- (NonExistent!): The container has loaded the document but has not yet instantiated 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 IViewObject2::Draw for any of them, and must rely on cached information (extent, label, and so on) to render anything for a control.
- Unintiailized: The container has loaded a control, but the control is completely uninitializedthat 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 wishes to draw an image for the control, it must do so itself using any cached information (like a caption string).
- 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 (like a rectangle and text) through IViewObject2::Draw and should give priority to retrieving its Caption and Text properties first, followed by rendering-related properties (BackColor, ForeColor, DrawStyle, and so on)
- 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 (like a rectangle and text) through IViewObject2::Draw. In simple cases, like a button, the control will already have all its data necessary for rendering such as colors, text, font, and styles.
- 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) may 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).
- Complete: The control has loaded all of 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 may not be operative; that is, they will return E_PENDING, and certain properties may 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/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 rest of IPersist*. Obviously, this puts a some 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 will become 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 may 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 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 hardcoded to do this). In addition, user code outside of a change event may 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 IPeristMoniker::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 our 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 SubControls 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 may not be 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.
Top of Page
© 1997 Microsoft Corporation. All rights reserved. Terms of Use.