📄 ch06.htm
字号:
<PRE></PRE>
<BLOCKQUOTE>
<PRE><FONT COLOR="#0066FF">MyObject.CaptionMethod "Hello"</FONT></PRE>
<P>or
</BLOCKQUOTE>
<PRE></PRE>
<BLOCKQUOTE>
<PRE><FONT COLOR="#0066FF">MyObject.CaptionMethod "Hello", True</FONT></PRE>
<P>from Visual Basic. Visual Basic automatically passes in a<TT>VARIANT</TT> set
to the type <TT>VT_ERROR</TT> for any parameters that are omitted from a method call.
For automation controllers that call the standard OLE <TT>Invoke</TT> method, such
as C++, all the parameters <I>must</I> be defined, even though they may not be used.
In this case, when calling the method, the programmer has the option of setting the
<TT>VARIANT</TT> type to <TT>VT_ERROR</TT> or <TT>VT_EMPTY</TT>.
</BLOCKQUOTE>
<DL>
<DL>
<DT>When processing optional parameters, relying on the OLE <TT>VARIANT</TT> conversion
routines in addition to looking for specific <TT>VARIANT</TT> data types guarantees
that your control can handle any data passed to it.</DT>
</DL>
</DL>
<P><IMG SRC="bar.gif" WIDTH="480" HEIGHT="6" ALIGN="BOTTOM" BORDER="0"><BR>
<BR>
If the <TT>VARIANT</TT> is of a valid data type other than <TT>VT_I4</TT>, the method
tries to convert it to a <TT>VT_I4</TT> type. This is for cases where a user passes
valid data in the form of a different data type, for example, a short or a string.</P>
<P>One thing to note is the use of the function <TT>VariantInit</TT>. It is very
important that <I>all</I> <TT>VARIANT</TT> variables be initialized prior to their
use. This practice will guarantee that the <TT>VARIANT</TT> does not contain invalid
data type information or invalid values. This follows the basic C++ tenet of initializing
all member variables to ensure that they do not contain invalid information.</P>
<P>If the requirements of your control demand that you deal with only specific data
types, you can also add code (error messages, exceptions, and so on) to deal with
the fact that the method did not receive a valid data type. If the function <TT>VariantChangeType</TT>
was unable to convert the data, the method exits and returns a value of <TT>FALSE</TT>.
A<TT> </TT>return of<TT> FALSE</TT> indicates to the caller of the method that the
method didn't succeed. Again, you can add additional error handling code to the method
to give the user more information about the error that occurred. See <A HREF="ch03.htm">Chapters
3</A> through <A HREF="ch05.htm">5</A> on generating OLE exceptions for more information.</P>
<P>Before proceeding, the method ensures that the <TT>m_lAlignment</TT> member variable
contains valid data.</P>
<P>If the method received valid data, or converted the data to a valid value, as
indicated by the variable <TT>lResult</TT> equaling <TT>TRUE</TT>, it stores the
caption and the alignment values in the class member variables, invalidates the control
so it will redraw its User Interface (UI) based on the new information, and exits
the function.
<H3><A NAME="Heading9"></A>Listing 6.5 MFCCONTROLWINCTL.CPP--CaptionMethod Implementation</H3>
<P><FONT COLOR="#0066FF"><TT>long CMFCControlWinCtrl::CaptionMethod(LPCTSTR lpctstrCaption,
const VARIANT FAR& varAlignment) <BR>
{ <BR>
// return value initialized to failure result <BR>
long lResult = FALSE; <BR>
// if the variant is a long just use the value <BR>
if(VT_I4 == varAlignment.vt) <BR>
{ <BR>
// assign the value to our member variable <BR>
m_lAlignment = varAlignment.lVal; <BR>
// set the return value <BR>
lResult = TRUE; <BR>
} <BR>
// if the user didn't supply an alignment parameter we will use whatever is already
there <BR>
else if(VT_ERROR == varAlignment.vt || VT_EMPTY == varAlignment.vt) <BR>
{ <BR>
// set the return value <BR>
lResult = TRUE; <BR>
} <BR>
else <BR>
{ <BR>
// get a variant that we can use for conversion purposes <BR>
VARIANT varConvertedValue; <BR>
// initialize the variant <BR>
::VariantInit(&varConvertedValue); <BR>
// see if we can convert the data type to something useful - VariantChangeTypeEx()
could also be used <BR>
if(S_OK == ::VariantChangeType(&varConvertedValue, (VARIANT *) &varAlignment,
0, VT_I4)) <BR>
{ <BR>
// assign the value to our member variable <BR>
switch(varConvertedValue.lVal) <BR>
{ <BR>
case EALIGN_CENTER: <BR>
m_lAlignment = EALIGN_CENTER; <BR>
break; <BR>
case EALIGN_RIGHT: <BR>
m_lAlignment = EALIGN_RIGHT; <BR>
break; <BR>
default: <BR>
m_lAlignment = EALIGN_LEFT; <BR>
break; <BR>
} <BR>
// set the return value <BR>
lResult = TRUE; <BR>
} <BR>
else <BR>
{ <BR>
// at this point we could either throw an error indicating there was a problem converting
<BR>
// the data or change the return type of the method and return the HRESULT value
from the <BR>
// the "VariantChangeType" call. <BR>
} <BR>
} <BR>
// if the alignment value is invalid <BR>
if(m_lAlignment < EALIGN_LEFT || m_lAlignment > EALIGN_RIGHT) <BR>
// set to the default value <BR>
m_lAlignment = EALIGN_LEFT; <BR>
// if everything was OK <BR>
if(TRUE == lResult) <BR>
{ <BR>
// if we have a string <BR>
if(lpctstrCaption != NULL) <BR>
// assign the string to our member variable <BR>
m_cstrCaption = lpctstrCaption; <BR>
<BR>
// did they pass us bad data? <BR>
if(m_lAlignment < EALIGN_LEFT || m_lAlignment > EALIGN_RIGHT) <BR>
// sure did, lets fix their little red wagon <BR>
m_lAlignment = EALIGN_LEFT; <BR>
// force the control to repaint itself <BR>
this->InvalidateControl(); <BR>
} <BR>
// return the result of the function call <BR>
return lResult; <BR>
} </TT></FONT></P>
<P>To complete your implementation of the <TT>CaptionMethod</TT>, you need to modify
the ODL file. When methods, properties, and events are added to a class with the
ControlWizard, the ODL file is also updated with the new entries. The ODL file is
compiled into a type library, which is essentially a description of the component
and its interfaces that can be used by other applications to access the component.
You need to add the keyword <TT>[optional]</TT> to the last parameter in the <TT>CaptionMethod</TT>
as in Listing 6.6. Doing so lets the container application know that the last parameter
is optional and should be dealt with accordingly. <BR>
<BR>
<IMG SRC="bar.gif" WIDTH="480" HEIGHT="6" ALIGN="BOTTOM" BORDER="0"></P>
<BLOCKQUOTE>
<P><B>NOTE:</B> Take care when modifying the ODL file by hand. The ODL file must
match your implemen- tation exactly. If the file is changed incorrectly, it can introduce
strange bugs and/or implementation problems with your component. <BR>
<BR>
The ClassWizard automatically updates the ODL file when methods, properties, and
events are added or removed. Adding or removing information from the ODL could prevent
the ClassWizard from correctly managing the file. <BR>
<BR>
Keywords such as <TT>[optional]</TT> do not affect the ClassWizard and its capability
to automatically update the file when changes are made to a class.
</BLOCKQUOTE>
<P><IMG SRC="bar.gif" WIDTH="480" HEIGHT="6" ALIGN="BOTTOM" BORDER="0">
<H3><A NAME="Heading10"></A>Listing 6.6 MFCCONTROL.ODL--Keyword [optional] Added
to the CaptionMethod ODL Definition</H3>
<P><FONT COLOR="#0066FF"><TT>methods: <BR>
// NOTE - ClassWizard will maintain method information here. <BR>
// Use extreme caution when editing this section. <BR>
//{{AFX_ODL_METHOD(CMFCControlWinCtrl) <BR>
[id(1)] long CaptionMethod(BSTR lpctstrCaption, [optional] VARIANT varAlignment);
<BR>
//}}AFX_ODL_METHOD </TT></FONT></P>
<H2><A NAME="Heading11"></A>Properties</H2>
<P><I>Properties</I> can be categorized as user defined, stock, or ambient.</P>
<P><I>User defined properties</I> are properties that are implementation-specific
and have meaning only to the component that contains them. User defined properties
can be further broken into those properties that are defined only as their specific
data type (<I>normal</I> properties) and those with additional parameters (<I>parameterized</I>
properties).</P>
<P><I>Stock properties</I> are a set of properties that are already defined by OLE
in terms of the basic meaning. Stock properties are not implemented in the control
of the container by default. They still require implementation by the control developer.
They are predefined only to imply a certain level of uniformity between various control
implementations.</P>
<P><I>Ambient properties,</I> on the other hand, are properties that are supported
by the container to provide a default value to the control that uses them.</P>
<P>In the remainder of the chapter, you will create three types of properties: normal,
parameterized, and stock. You will also learn how to use ambient properties.
<H3><A NAME="Heading12"></A>Creating Normal User Defined Properties</H3>
<P>A <I>normal property</I> is a property that is declared as a single type, for
example, <TT>long</TT> or <TT>BSTR</TT>, and has no parameters. You will expose your
controls' Alignment member variable through a property.</P>
<P>Properties are added in much the same way as methods. Open the ClassWizard, select
the class <TT>CMFCControlWinCtrl</TT>, switch to the Automation tab, and click the
Add P<U>r</U>operty button.</P>
<P>In the Add Property dialog (see fig. 6.9), set the <U>E</U>xternal name to <TT>Alignment</TT>,
the <U>T</U>ype to <TT>long</TT>, and the Implementation to <TT>Get/Set</TT> <U>m</U>ethods.
Click OK to confirm the entry and close the dialog. <B><BR>
<BR>
</B><A HREF="Art/06/f_fig09.jpg"><B>FIG. 6.9</B></A> <BR>
<I>Add the Alignment property to the Class Definition.</I></P>
<P>Double-click the <TT>Alignment</TT> property entry in the <U>E</U>xternal name
list box to open the source file so you can add your code to the <TT>Get/Set</TT>
<U>m</U>ethods.</P>
<P>As you can see, Listing 6.7 takes advantage of the member variable <TT>m_Alignment</TT><I>,
</I>which you added earlier, and uses it to get and set the property value.</P>
<P>The <TT>GetAlignment</TT> function is simple in that it returns only the value
stored in the <TT>m_lAlignment</TT> member variable.</P>
<P>The <TT>SetAlignment</TT> function does a little more. This function checks to
see if the value is within the valid ranges of values and, if so, stores the value
in the <TT>m_lAlignment</TT> member variable. The function then calls the <TT>SetModifiedFlag</TT>
and the <TT>BoundPropertyChanged</TT> functions to notify the control and the container,
respectively, that the value of the property has changed. <TT>BoundPropertyChanged</TT>
has the effect of forcing the container to refresh its property browser to reflect
the new value. This is very important because the value of the property could change
without the container's knowledge, either through the control's property sheet or,
in some cases, in response to another function call. You might be asking yourself,
"Why didn't I add <TT>BoundPropertyChanged</TT> to the <TT>CaptionMethod</TT>?"
Well, you could have, but it wouldn't do much because the <TT>CaptionMethod</TT>
can never be executed while the control is in design mode, which is the purpose of
<TT>BoundPropertyChanged</TT>.</P>
<P>The last thing the <TT>SetAlignment</TT> method does is invalidate the control's
UI so it will repaint using the new information. <BR>
<BR>
<IMG SRC="bar.gif" WIDTH="480" HEIGHT="6" ALIGN="BOTTOM" BORDER="0"></P>
<BLOCKQUOTE>
<P><B>NOTE:</B> Even though a property appears as a single member of an object, it
is really a pair of related functions used to operate on a single piece of data.
The ODL entries for a property will differ depending on its implementation. <BR>
<BR>
For interfaces inherited from a <TT>dispinterface</TT> class, it is enough to have
a single entry in the properties section of the class. <TT>IDispatch</TT> inherited
interfaces will define two separate functions: one declared as <TT>propertyget</TT>,
and the other defined as <TT>propertyput</TT>, both sharing the same dispid. Parameterized
properties are always defined by using the latter method because it is impossible
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -