?? ch20.htm
字號:
Though the size of the Dieroll OCX file is unchanged, Web pages with this control
should load more quickly because the window isn't created until the user first clicks
on the die. If you reload the Web page with the die in it, you'll see the first value
immediately, even though the control is inactive. The window is created to catch
mouse clicks, not to display the die roll.</P>
<P>There are more optimizations available. Figure 20.4 shows the list of advanced
options for ActiveX ControlWizard, reached by clicking the Advanced button on Step
2. You can choose each of these options when you first build the application through
the ControlWizard. They can also be changed in an existing application, saving you
the trouble of redoing AppWizard and adding your own functionality again. The options
are</P>
<UL>
<LI>Windowless Activation
<P>
<LI>Unclipped Device Context
<P>
<LI>Flicker-Free Activation
<P>
<LI>Mouse Pointer Notifications When Inactive
<P>
<LI>Optimized Drawing Code
<P>
<LI>Loads Properties Asynchronously
</UL>
<P><A HREF="javascript:popUp('20uvc04.gif')"><B>FIG. 20.4</B></A><B> </B><I>The Advanced
button on Step 2 of the ActiveX ControlWizard leads to a choice of optimizations.</I></P>
<P>Windowless activation is going to be very popular because of the benefits it provides.
If you want a transparent control or one that isn't a rectangle, you must use windowless
activation. However, because it reduces code size and speeds execution, every control
should consider using this option. Modern containers provide the functionality for
the control. In older containers, the control creates the window anyway, denying
you the savings but ensuring that the control still works.</P>
<P>To implement the Windowless Activation option in Dieroll, override CDierollCtrl::GetControlFlags()
like this:</P>
<P>
<PRE>DWORD CDierollCtrl::GetControlFlags()
{
return COleControl::GetControlFlags()| windowlessActivate;
}
</PRE>
<P>Add the function quickly by right-clicking CDierollCtrl in ClassView and choosing
Add Member Function. If you do this to Dieroll, build it, and reload the Web page
that uses it, you'll notice no apparent effect because Dieroll is such a lean control.
You'll at least notice that it still functions perfectly and doesn't mind not having
a window.</P>
<P>The next two options, Unclipped Device Context and Flicker-Free Activation, are
not available to windowless controls. In a control with a window, choosing Unclipped
Device Context means that you are completely sure that you never draw outside the
control's client rectangle. Skipping the checks that make sure you don't means your
control runs faster, though it could mean trouble if you have an error in your draw
code. If you were to do this in Dieroll, the override of GetControlFlags() would
look like this:</P>
<P>
<PRE>DWORD CDierollCtrl::GetControlFlags()
{
return COleControl::GetControlFlags()& ~clipPaintDC;
}
</PRE>
<P>Don't try to combine this with windowless activation: It doesn't do anything.</P>
<P>Flicker-free activation is useful for controls that draw their inactive and active
views identically. (Think back to Chapter 15, "Building an ActiveX Server Application,"
in which the server object was drawn in dimmed colors when the objects were inactive.)
If there is no need to redraw, because the drawing code is the same, you can select
this option and skip the second draw. Your users won't see an annoying flicker as
the control activates, and activation will be a tiny bit quicker. If you were to
do this in Dieroll, the GetControlFlags() override would be</P>
<P>
<PRE>DWORD CDierollCtrl::GetControlFlags()
{
return COleControl::GetControlFlags()| noFlickerActivate;
}
</PRE>
<P>Like unclipped device context, don't try to combine this with windowless activation:
It doesn't do anything.</P>
<P>Mouse pointer notifications, when inactive, enable more controls to turn off the
Activates When Visible option. If the only reason to be active is to have a window
to process mouse interactions, this option will divert those interactions to the
container through an IPointerInactive interface. To enable this option in an application
that is already built, you override GetControlFlags()again:</P>
<P>
<PRE> DWORD CDierollCtrl::GetControlFlags()
{
return COleControl::GetControlFlags()| pointerInactive;
}
</PRE>
<P>Now your code will receive WM_SETCURSOR and WM_MOUSEMOVE messages through message
map entries, even though you have no window. The container, whose window your control
is using, will send these messages to you through the IPointerInactive interface.</P>
<P>The other circumstance under which you might want to process window messages while
still inactive, and so without a window, is if the user drags something over your
control and drops it. The control needs to activate at that moment so that it has
a window to be a drop target. You can arrange that with an override to GetActivationPolicy():</P>
<P>
<PRE>DWORD CDierollCtrl::GetActivationPolicy()
{
return POINTERINACTIVE_ACTIVATEONDRAG;
}
</PRE>
<P>Don't bother doing this if your control isn't a drop target, of course.</P>
<P>The problem with relying on the container to pass on your messages through the
IPointerInactive interface is that the container might have no idea such an interface
exists and no plans to pass your messages on with it. If you think your control might
end up in such a container, don't remove the OLEMISC_ACTIVATEWHENVISIBLE flag from
the block of code shown previously in in Listing 20.5</P>
<P>Instead, combine another flag, OLEMISC_IGNOREACTIVATEWHENVISIBLE, with these flags
using the bitwise or operator. This oddly named flag is meaningful to containers
that understand IPointerInactive and means, in effect, "I take it back-- don't
activate when visible after all." Containers that don't understand IPointerInactive
don't understand this flag either, and your control will activate when visible and
thus be around to catch mouse messages in these containers.</P>
<P>Optimized drawing code is only useful to controls that will be sharing the container
with a number of other drawing controls. As you might recall from Chapter 5, "Drawing
on the Screen," the typical pattern for drawing a view of any kind is to set
the brush, pen, or other GDI object to a new value, saving the old. Then you use
the GDI object and restore it to the saved value. If there are several controls doing
this in turn, all those restore steps can be skipped in favor of one restore at the
end of all the drawing. The container saves all the GDI object values before instructing
the controls to redraw and afterwards restores them all.</P>
<P>If you would like your control to take advantage of this, you need to make two
changes. First, if a pen or other GDI object is to remain connected between draw
calls, it must not go out of scope. That means any local pens, brushes, and fonts
should be converted to member variables so that they stay in scope between function
calls. Second, the code to restore the old objects should be surrounded by an if
statement that calls COleControl::IsOptimizedDraw() to see whether the restoration
is necessary. A typical draw routine would set up the colors and proceed like this:</P>
<P>
<PRE>...
if(!m_pen.m_hObject)
{
m_pen.CreatePen(PS_SOLID, 0, forecolor);
}
if(!m_brush.m_hObject)
{
m_brush.CreateSolidBrush(backcolor);
}
CPen* savepen = pdc->SelectObject(&m_pen);
CBrush* savebrush = pdc->SelectObject(&m_brush);
...
// use device context
...
if(!IsOptimizedDraw())
{
pdc->SelectObject(savepen);
pdc->SelectObject(savebrush);
}
...
</PRE>
<P>The device context has the addresses of the member variables, so when it lets
go of them at the direction of the container, their m_hObject member becomes NULL.
As long as it isn't NULL, there is no need to reset the device context, and if this
container supports optimized drawing code, there is no need to restore it either.</P>
<P>If you select this optimized drawing code option from the Advanced button in AppWizard
Step 2, the if statement with the call to IsOptimizedDraw() is added to your draw
code, with some comments to remind you what to do.</P>
<P>The last optimization option, Loads Properties Asynchronously, is covered in the
next section.</P>
<P>
<H2><A NAME="Heading6"></A>Speeding Control Loads with Asynchronous Properties</H2>
<P><I>Asynchronous</I> refers to spreading out activities over time and not insisting
that one activity be completed before another can begin. In the context of the Web,
it's worth harking back to the features that made Netscape Navigator better than
Mosaic, way back when it was first released. The number one benefit cited by people
who were on the Web then was that the Netscape browser, unlike Mosaic, could display
text while pictures were still loading. This is classic asynchronous behavior. You
don't have to wait until the huge image files have transferred, to see what the words
on the page are and whether the images are worth waiting for.</P>
<P>Faster Internet connections and more compact image formats have lessened some
of the concerns about waiting for images. Still, being asynchronous is a good thing.
For one thing, waiting for video clips, sound clips, and executable code has made
many Web users long for the good old days when they had to wait only 30 seconds for
pages to find all their images.</P>
<P>
<H3><A NAME="Heading7"></A>Properties</H3>
<P>The die that comes up in your Web page is the default die appearance. There's
no way for the user to access the control's properties. The Web page developer can,
using the <PARAM> tag inside the <OBJECT> tag. (Browsers that ignore
OBJECT also ignore PARAM.) Here's the PARAM tag to add to your HTML between <OBJECT>
and </OBJECT> to include a die with a number instead of dots:</P>
<P>
<PRE><PARAM NAME="Dots" value="0">
</PRE>
<P>The PARAM tag has two attributes: NAME provides a name that matches the external
ActiveX name (Dots), and value provides the value (0, or FALSE). The die displays
with a number.</P>
<P>To demonstrate the value of asynchronous properties, Dieroll needs to have some
big properties. Because this is a demonstration application, the next step is to
add a big property. A natural choice is to give the user more control over the die's
appearance. The user (which means the Web page designer if the control is being used
in a Web page) can specify an image file and use that as the background for the die.
Before you learn how to make that happen, imagine what the Web page reader will have
to wait for when loading a page that uses Dieroll:</P>
<UL>
<LI>The HTML has to be loaded from the server.
<P>
<LI>The browser lays out the text and nontext elements and starts to display text.
<P>
<LI>The browser searches the Registry for the control's CLSID.
<P>
<LI>If necessary, the control is downloaded, using the CODEBASE parameter.
<P>
<LI>The control properties are initialized, using the PARAM tags.
<P>
<LI>The control runs and draws itself.
</UL>
<P>When Dieroll gains another property--an image file that might be quite large--there
will be another delay while the image file is retrieved from wherever it is kept.
If nothing happens in the meantime, the Web page reader will eventually tire of staring
at an empty square and go to another page. Using asynchronous properties means that
the control can roughly draw itself and start to be useful, even while the large
image file is still being downloaded. For Dieroll, drawing the dots on a plain background,
using GetBackColor(), will do until the image file is ready.</P>
<P>
<H3><A NAME="Heading8"></A>Using BLOBs</H3>
<P>A <I>BLOB</I> is a binary large object. It's a generic name for things like the
image file you are about to add to the Dieroll control. The way a control talks to
a BLOB is through a <I>moniker</I>. That's not new. It's just that monikers have
always been hidden away inside OLE. If you already understand them, you still have
a great deal more to learn about them because things are changing with the introduction
of asynchronous monikers. If you've never heard of them before, no problem. Eventually
there will be all sorts of asynchronous monikers, but at the moment only URL monikers
have been implemented. These are a way for ActiveX to connect BLOB properties to
URLs. If you're prepared to trust ActiveX to do this for you, you can achieve some
amazing things. The remainder of this subsection explains how to work with URL monikers
to load BLOB properties asynchronously.</P>
<P>Remember, the idea here is that the control will start drawing itself even before
it has all its properties. Your OnDraw() code will be structured like this:</P>
<P>
<PRE>// prepare to draw
if(AllPropertiesAreLoaded)
{
// draw using the BLOB
}
else
{
// draw without the BLOB
}
//cleanup after drawing
</PRE>
<P>There are two problems to solve here. First, what will be the test to see whether
all the properties are loaded? Second, how can you arrange to have OnDraw() called
again when the properties are ready, if it's already been called and has already
drawn the control the BLOBless way?</P>
<P>The first problem has been solved by adding two new functions to COleControl.
GetReadyState()returns one of these values:</P>
<UL>
<LI>READYSTATE_UNINITIALIZED means the control is completely unitialized.
<P>
<LI>READYSTATE_LOADING means the control properties are loading.
<P>
<LI>READYSTATE_LOADED means the properties are all loaded.
<P>
<LI>READYSTATE_INTERACTIVE means the control can talk to the user but isn't fully
loaded yet.
<P>
<LI>READYSTATE_COMPLETE means there is nothing more to wait for.
</UL>
<P>The function InternalSetReadyState() sets the ready state to one of these values.</P>
<P>The second problem, getting a second call to OnDraw() after the control has already
been drawn without the BLOB, has been solved by a new class called CDataPathProperty
and its derived class CCachedDataPathProperty. These classes have a member function
called OnDataAvailable() that catches the Windows message generated when the property
has been retrieved from the remote site. The OnDataAvailable() function invalidates
the control, forcing a redraw.</P>
<P>
<H3><A NAME="Heading9"></A>Changing Dieroll</H3>
<P>Make a copy of the Dieroll folder you created in Chapter 17 and change it to windowless
activation as described earlier in this chapter. Now you're ready to begin. There
is a lot to do to implement asynchronous properties, but each step is straightforward.</P>
<P><B>Add the <I>CDierollDataPathProperty</I> Class  </B>Bring up ClassWizard,
click the Automation tab, and click the Add Class button. From the drop-down menu
that appears under the button, choose New. This brings up the Create New Class dialog
box. Name the class CDierollDataPathProperty. Click the drop-down box for Base Class
and choose CCachedDataPathProperty. The dialog box will resemble Figure 20.5. Click
OK to create the class and add it to the project.</P>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -