📄 ch21.htm
字号:
{
*pVal = m_bDots;
return S_OK;
}
STDMETHODIMP CDieRoll::put_Dots(BOOL newVal)
{
if (FireOnRequestEdit(dispidDots) == S_FALSE)
{
return S_FALSE;
}
m_bDots = newVal;
SetDirty(TRUE);
FireOnChanged(dispidDots);
FireViewChange();
return S_OK;
</PRE>
<PRE>}
</PRE>
<P>The code in the two get functions is simple and straightforward. The put_dots()
code is more complex because it fires notifications. FireOnRequestEdit() notifies
all the IPropertyNotifySink interfaces that this property is going to change. Any
one of these interfaces can deny the request, and if one does, this function will
return S_FALSE to forbid the change.</P>
<P>Assuming the change is allowed, the member variable is changed, and the control
is marked as modified (dirty) so that it will be saved. The call to FireOnChange()
notifies the IPropertyNotifySink interfaces that this property has changed, and the
call to FireViewChange() tells the container to redraw the control.</P>
<P>
<H3><A NAME="Heading12"></A>Initializing the Properties</H3>
<P>Having added the code to get and set these properties, you should now change the
CDieRoll constructor to initialize all the stock and custom properties, as shown
in Listing 21.5. A stub for the constructor is in the header file for you to edit.</P>
<P>
<H4>Listing 21.5  Excerpt from DieRoll.h--Constructor</H4>
<PRE> CDieRoll()
{
srand( (unsigned)time( NULL ) );
m_nReadyState = READYSTATE_COMPLETE;
m_clrBackColor = 0x80000000 | COLOR_WINDOW;
m_clrForeColor = 0x80000000 | COLOR_WINDOWTEXT;
m_sNumber = Roll();
m_bDots = TRUE;
</PRE>
<PRE> }
</PRE>
<P>At the top of the header, add this line to bring in a declaration of the time()
function:</P>
<P>
<PRE>#include "time.h"
</PRE>
<P>Just as you did in the MFC version of this control, you initialize m_sNumber to
a random number between 1 and 6, returned by the Roll() function. Add this function
to CDieRoll by right-clicking on the classname in ClassView and choosing Add Member
Function from the shortcut menu. Roll() is protected takes no parameters and returns
a short. The code for Roll() is in Listing 21.6 and is explained in Chapter 17.</P>
<P>
<H4>Listing 21.6  CDieRoll::Roll()</H4>
<PRE>short CDieRoll::Roll()
{
double number = rand();
number /= RAND_MAX + 1;
number *= 6;
return (short)number + 1;
</PRE>
<PRE>}
</PRE>
<P>It's a good idea to build the project at this point to be sure you haven't made
any typos or missed any steps.</P>
<P>
<H3><A NAME="Heading13"></A>Adding the Asynchronous Property</H3>
<P>Just as in Chapter 20, the Image property represents a bitmap to be loaded asynchronously
and used as a background image. Add the property to the interface just as <B>Number</B>
and <B>Dots</B> were added. Use <B>BSTR</B> for the type and <B>Image</B> for the
name. Update the enum in the idl file so that <B>dispidImage</B> is 3, and edit the
<B>propget</B> and <B>propput</B> lines in the idl file to use the enum value:</P>
<P>
<PRE>[propget, id(dispidImage), helpstring("property Image")]
HRESULT Image([out, retval] BSTR *pVal);
[propput, id(dispidImage), helpstring("property Image")]
HRESULT Image([in] BSTR newVal);
</PRE>
<P>Add a member variable, m_bstrImage, to the class after the five properties you
have already added:</P>
<P>
<PRE>CComBSTR m_bstrImage;
</PRE>
<P>CComBSTR is an ATL wrapper class with useful member functions for manipulating
a BSTR.</P>
<P>A number of other member variables must be added to handle the bitmap and the
asynchronous loading. Add these lines to DieRoll.h after the declaration of m_bstrImage:</P>
<P>
<PRE>HBITMAP hBitmap;
BITMAPINFOHEADER bmih;
char *lpvBits;
BITMAPINFO *lpbmi;
HGLOBAL hmem1;
HGLOBAL hmem2;
BOOL BitmapDataLoaded;
char *m_Data;
unsigned long m_DataLength;
</PRE>
<P>The first six of these new variables are used to draw the bitmap and won't be
discussed. The last three combine to achieve the same behavior as the data path property
used in the MFC version of this control.</P>
<P>Add these three lines to the constructor:</P>
<P>
<PRE>m_Data = NULL;
m_DataLength = 0;
BitmapDataLoaded = FALSE;
</PRE>
<P>Add a destructor to CDieRoll (in the header file) and add the code in Listing
21.7.</P>
<P>
<H4>Listing 21.7  CDieRoll::~CDieRoll()</H4>
<PRE> ~CDieRoll()
{
if (BitmapDataLoaded)
{
GlobalUnlock(hmem1);
GlobalFree(hmem1);
GlobalUnlock(hmem2);
GlobalFree(hmem2);
BitmapDataLoaded = FALSE;
}
if (m_Data != NULL)
{
delete m_Data;
}
</PRE>
<PRE> }
</PRE>
<P>The Image property has get and put functions. Code them as in Listing 21.8.</P>
<P>
<H4>Listing 21.8  DieRoll.cpp--get_Image() and put_Image()</H4>
<PRE>STDMETHODIMP CDieRoll::get_Image(BSTR * pVal)
{
*pVal = m_bstrImage.Copy();
return S_OK;
}
STDMETHODIMP CDieRoll::put_Image(BSTR newVal)
{
USES_CONVERSION;
if (FireOnRequestEdit(dispidImage) == S_FALSE)
{
return S_FALSE;
}
// if there was an old bitmap or data, delete it
if (BitmapDataLoaded)
{
GlobalUnlock(hmem1);
GlobalFree(hmem1);
GlobalUnlock(hmem2);
GlobalFree(hmem2);
BitmapDataLoaded = FALSE;
}
if (m_Data != NULL)
{
delete m_Data;
}
m_Data = NULL;
m_DataLength = 0;
m_bstrImage = newVal;
LPSTR string = W2A(m_bstrImage);
if (string != NULL && strlen(string) > 0)
{
// not a null string so try to load it
BOOL relativeURL = FALSE;
if (strchr(string, `:') == NULL)
{
relativeURL = TRUE;
}
m_nReadyState = READYSTATE_LOADING;
HRESULT ret = CBindStatusCallback<CDieRoll>::Download(this,
OnData, m_bstrImage, m_spClientSite, relativeURL);
}
else
{
// was a null string so don't try to load it
m_nReadyState = READYSTATE_COMPLETE;
FireViewChange();
}
SetDirty(TRUE);
FireOnChanged(dispidImage);
return S_OK;
}
</PRE>
<PRE></PRE>
<P>As with Numbers and Dots, the get function is straightforward, and the put function
is more complicated. The beginning and end of the put function are like put_Dots(),
firing notifications to check whether the variable can be changed and then other
notifications that it was changed. In between is the code unique to an asynchronous
property.</P>
<P>To start the download of the asynchronous property, this function will call CBindStatusCallback<CDieRoll>::Download(),
but first it needs to determine whether the URL in m_bstrImage is a relative or absolute
URL. Use the ATL macro W2A to convert the wide BSTR to an ordinary C string so that
the C function strchr() can be used to search for a : character in the URL. An URL
with no : in it is assumed to be a relative URL.</P>
<BLOCKQUOTE>
<P>
<HR>
<strong>NOTE:</strong> A BSTR is a wide (double-byte) character on all 32-bit Windows platforms.
It is a narrow (single-byte) string on a PowerMac. 
<HR>
</BLOCKQUOTE>
<P>In the MFC version of the DieRoll control with an asynchronous image property,
whenever a block of data came through, the OnDataAvailable() function was called.
The call to Download() arranges for a function called OnData() to be called when
data arrives. You will write the OnData() function. Add it to the class with the
other public functions and add the implementation shown in Listing 21.9 to DieRoll.cpp.</P>
<P>
<H4>Listing 21.9  DieRoll.cpp--CDieRoll::OnData()</H4>
<PRE>void CDieRoll::OnData(CBindStatusCallback<CDieRoll>* pbsc,
BYTE * pBytes, DWORD dwSize)
{
char *newData = new char[m_DataLength + dwSize];
memcpy(newData, m_Data, m_DataLength);
memcpy(newData+m_DataLength, pBytes, dwSize);
m_DataLength += dwSize;
delete m_Data;
m_Data = newData;
if (ReadBitmap())
{
m_nReadyState = READYSTATE_COMPLETE;
FireViewChange();
}
</PRE>
<PRE>}
</PRE>
<P>Because there is no realloc() when using new, this function uses new to allocate
enough chars to hold the data that has already been read (m_DataLength) and the new
data that is coming in (dwSize); it then copies m_Data to this block, and the new
data (pBytes) after m_Data. Then it attempts to convert into a bitmap the data that
has been received so far. If this succeeds, the download must be complete, so the
ready state notifications are sent, and the call to FireViewChange() sends a notification
to the container to redraw the view. You can obtain the ReadBitmap() function from
the Web site and add it to your project. It's much like the MFC version, but it doesn't
use any MFC classes such as CFile. Add the function and its code to CDieRoll.</P>
<P>Once again, build the control, just to be sure you haven't missed any steps or
made any typos.</P>
<P>
<H2><A NAME="Heading14"></A>Drawing the Control</H2>
<P>Now that all the properties have been added, you can code OnDraw(). Although the
basic structure of this function is the same as in the MFC version of Chapter 20.
A lot more work must be done because you can't rely on MFC to do some of it for you.
A more detailed explanation of the OnDraw() design is in Chapter 20.</P>
<P>The structure of OnDraw() is</P>
<P>
<PRE>HRESULT CDieRoll::OnDraw(ATL_DRAWINFO& di)
// if the bitmap is ready, draw it
// else draw a plan background using BackColor
// if !Dots draw a number in ForeColor
// else draw the dots
</PRE>
<P>First, you need to test whether the bitmap is ready and to draw it, if possible.
This code is in Listing 21.10: Add it to dieroll.cpp and remove the OnDraw()code
left in dieroll.h by AppWizard. (Leave the declaration of OnDraw() in the header
file.) Notice that if ReadyState is READYSTATE_COMPLETE, but the call to CreateDIBitmap()
doesn't result in a valid bitmap handle, the bitmap member variables are cleared
away to make subsequent calls to this function give up a little faster. This chapter
doesn't discuss how to draw bitmaps.</P>
<P>
<H4>Listing 21.10  CDieRoll::OnDraw()--Use the Bitmap</H4>
<PRE>HRESULT CDieRoll::OnDraw(ATL_DRAWINFO& di)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -