📄 ch10.htm
字号:
<BR>
// did they pass us bad data?<BR>
if(m_state.lAlignment < EALIGN_LEFT || m_state.lAlignment > EALIGN_RIGHT)<BR>
// sure did, lets fix their little red wagon<BR>
m_state.lAlignment = EALIGN_LEFT; <BR>
// force the control to repaint itself<BR>
this->InvalidateControl(NULL);<BR>
// this->InvalidateControl(); <== MFC Version<BR>
} <BR>
// return the result of the function call<BR>
return hResult; <BR>
}</TT></FONT></P>
<P>Use the BaseCtl utility macro <TT>MAKE_ANSIPTR_FROMWIDE</TT> to convert the <TT>BSTR</TT>
to an <TT>ANSI</TT> string. The file UTIL.H contains a number of helper functions
and macros to aid in your control development. This file, along with the BaseCtl
header files for the control and automation object implementations, deserves to be
reviewed just to note the differences between the standard MFC implementation and
the BaseCtl implementation for ActiveX controls. In addition, since documentation
is nonexistent, reviewing the header files will give you a feel for the types of
functions and functionality that are available to you for doing BaseCtl development.
<BR>
<BR>
<IMG SRC="bar.gif" WIDTH="480" HEIGHT="6" ALIGN="BOTTOM" BORDER="0"></P>
<BLOCKQUOTE>
<P><B>NOTE:</B> A number of <I>wrapper </I>functions are defined in the COleControl
header file for performing other common functions, such as invalidating a rectangle.
The wrapper functions allow the control to be created as either windowed or windowless,
without requiring separate code implementations to deal with each unique state. When
writing your control, do <I>not</I> use the control's window handle directly. Cases
like these are why the BaseCtl code should be reviewed carefully prior to making
large implementation investments. And due to the lack of documentation, the little
differences are also why a BaseCtl implementation can be a little more difficult.
</BLOCKQUOTE>
<P><IMG SRC="bar.gif" WIDTH="480" HEIGHT="6" ALIGN="BOTTOM" BORDER="0"><BR>
<BR>
Aside from the string conversion, the <TT>CaptionMethod</TT> varies little from its
MFC and ATL counterpart.</P>
<P>One thing to note though is the use of the method <TT>InvalidateControl</TT> to
force the control's UI to redraw. This is a very good example of the kind of differences
between BaseCtl and MFC in terms of certain basic functionality. In this case, the
function <TT>InvalidateControl</TT> requires that a parameter be passed, thus differing
from the MFC implementation, which has a default parameter of <TT>NULL</TT>. All
differences of this kind are pointed out in the source code.
<H2><A NAME="Heading19"></A>Creating 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 this chapter, you will create all three types of properties:
normal, parameterized, and stock. You will also learn how to use ambient properties.
<H3><A NAME="Heading20"></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
control's Alignment member variable through a property.</P>
<P>Properties are added in much the same way as methods. First add the dispid for
the property to the Dispids.h file.</P>
<P><FONT COLOR="#0066FF"><TT>#define dispidAlignment 1</TT></FONT></P>
<P>Next add the property declaration to the ODL file (see Listing 10.14). The property
declaration is really a pair of methods sharing the same dispid. The <TT>propget</TT>
and <TT>propput</TT> attributes denote the direction with which data is moved to
and from the property. The <TT>propget</TT> method contains a single parameter defined
as the return value of the method, using the <TT>[out, retval]</TT> attributes. The
<TT>propput</TT> method correspondingly accepts a single parameter of the same type.
<H3><A NAME="Heading21"></A>Listing 10.14 <SPACER TYPE="HORIZONTAL" SIZE="10">BCFCONTROL.ODL--Normal
User Defined Property ODL Declaration</H3>
<P><FONT COLOR="#0066FF"><TT>// primary dispatch interface for CBCFControl control<BR>
//<BR>
[uuid(317512F1-3E75-11d0-BEBE-00400538977D), helpstring("BCFControl Control"),<BR>
hidden, dual, odl]<BR>
interface IBCFControl : IDispatch <BR>
{<BR>
// properties<BR>
[id(dispidAlignment), propget] HRESULT Alignment([out, retval] long * Value);<BR>
[id(dispidAlignment), propput] HRESULT Alignment([in] long Value); <BR>
// methods<BR>
[id(dispidCaptionMethod)] HRESULT CaptionMethod([in] BSTR bstrCaption,<BR>
[in, optional] VARIANT varAlignment, [out, retval] long *lRetVal);<BR>
[id(DISPID_ABOUTBOX)] void AboutBox(void); <BR>
};</TT></FONT></P>
<P>As with your method implementation, you need to compile the ODL file in order
to create the C++ interface prototypes that you add to your class definition. After
the ODL is compiled, copy the two new function prototypes from the ODL-generated
header file to the BCFControl.h header file (see Listing 10.15).
<H3><A NAME="Heading22"></A>Listing 10.15 <SPACER TYPE="HORIZONTAL" SIZE="10">BCFCONTROLCTL.H--Property
Function Prototypes</H3>
<P><FONT COLOR="#0066FF"><TT>// IBCFControl methods<BR>
//<BR>
STDMETHOD(get_Alignment)(THIS_ long FAR* Value);<BR>
STDMETHOD(put_Alignment)(THIS_ long Value);<BR>
STDMETHOD(CaptionMethod)(THIS_ BSTR bstrCaption, VARIANT varAlignment,<BR>
long FAR* lRetVal); <BR>
STDMETHOD_(void, AboutBox)(THIS);</TT></FONT></P>
<P>Don't forget to remove the <TT>= 0</TT> from the function prototypes when you
copy them over.</P>
<P>As you can see, Listing 10.16 takes advantage of the member variable <TT>m_state.lAlignment</TT>,
which you added earlier, and uses it to get and set the property value.</P>
<P>The <TT>get_Alignment</TT> function returns only the value stored in the <TT>m_state.lAlignment</TT>
member variable.</P>
<P>The <TT>put_Alignment</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_state.lAlignment</TT> member variable. The function then sets the <TT>m_fDirty</TT>
flag and calls <TT>PropertyChanged</TT> functions (note the MFC equivalent functions
still in the code) to notify the control and the container, respectively, that the
value of the property has changed. <TT>PropertyChanged</TT> has the effect of forcing
the container to refresh its property browser to reflect the new value. This effect
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.</P>
<P>You might be asking, "Why didn't I add <TT>PropertyChanged</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>PropertyChanged</TT>. The <TT>PropertyChanged</TT> function is where you make
use of the dispid constants that were defined earlier. The last thing the <TT>put_Alignment</TT>
method does is invalidate the control's UI so it will repaint using the new information.
<H3><A NAME="Heading23"></A>Listing 10.16 <SPACER TYPE="HORIZONTAL" SIZE="10">BCFCONTROLCTL.CPP--Property
Function Implementation</H3>
<P><FONT COLOR="#0066FF"><TT>STDMETHODIMP CBCFControlControl::get_Alignment(long
FAR* Value)<BR>
{<BR>
HRESULT hResult = S_OK; <BR>
// return our current setting<BR>
*Value = m_state.lAlignment; <BR>
return hResult;<BR>
} <BR>
STDMETHODIMP CBCFControlControl::put_Alignment(long Value)<BR>
{<BR>
HRESULT hResult = S_OK; <BR>
// if we are in the valid range for the property<BR>
if(Value >= EALIGN_LEFT && Value <= EALIGN_RIGHT)<BR>
{<BR>
// set the new property value<BR>
m_state.lAlignment = Value; <BR>
// let the control know that the property has changed<BR>
m_fDirty = TRUE;<BR>
// this->SetModifiedFlag(); <== MFC version <BR>
// refresh the property browser<BR>
this->PropertyChanged(dispidAlignment);<BR>
// this->BoundPropertyChanged(dispidAlignment); <== MFC Version<BR>
} <BR>
return hResult; <BR>
}</TT></FONT></P>
<P>The implementation simply sets or returns the value in your member variable <TT>m_state.lAlignment</TT>.
Again, note the differences between your BaseCtl and your MFC implementation.
<H3><A NAME="Heading24"></A>Creating Parameterized User Defined Properties</H3>
<P>A <I>parameterized property</I> is a property that, besides being of a specific
type (for example, <TT>BSTR</TT> or <TT>long</TT>), accepts one or more additional
parameters to further define the data of the property. Parameterized properties can
be useful for properties that represent collections of data where the additional
parameter is the index into the collection.</P>
<P>You expose your <TT>m_lptstrCaption</TT> member variable as a parameterized property
in addition to your <TT>CaptionMethod</TT> function.</P>
<P>First add the new dispid for the <TT>dispidCaptionProp</TT> (see Listing 10.17).
<H3><A NAME="Heading25"></A>Listing 10.17<SPACER TYPE="HORIZONTAL" SIZE="10"> DISPIDS.H--Dispid
for CaptionProp Property</H3>
<P><FONT COLOR="#0066FF"><TT>// properties & methods<BR>
// <BR>
#define dispidAlignment 1<BR>
#define dispidCaptionMethod 2<BR>
#define dispidCaptionProp 3 <BR>
// events</TT></FONT></P>
<P>Next you add your ODL definition (see Listing 10.18) and compile the type library
to create the header file.
<H3><A NAME="Heading26"></A>Listing 10.18 <SPACER TYPE="HORIZONTAL" SIZE="10">BCFCONTROL.ODL--ODL
Entry for Caption Property</H3>
<P><FONT COLOR="#0066FF"><TT>// properties<BR>
[id(dispidAlignment), propget] HRESULT Alignment([out, retval] long * Value);<BR>
[id(dispidAlignment), propput] HRESULT Alignment([in] long Value); <BR>
// methods<BR>
[id(dispidCaptionMethod)] HRESULT CaptionMethod([in] BSTR bstrCaption,<BR>
[in, optional] VARIANT varAlignment, [out, retval] long * lRetVal);<BR>
[id(dispidCaptionProp), propget] HRESULT CaptionProp(<BR>
[in, optional] VARIANT varAlignment, [out, retval] BSTR * bstrRetVal);<BR>
[id(dispidCaptionProp), propput] HRESULT CaptionProp(<BR>
[in] VARIANT varAlignment, [in] BSTR lpszNewValue); <BR>
[id(DISPID_ABOUTBOX)] void AboutBox(void);</TT></FONT></P>
<P>Copy the new function prototypes from the header file, BCFControlInterfaces.h,
and paste them into the control header file (see Listing 10.19).
<H3><A NAME="Heading27"></A>Listing 10.19 <SPACER TYPE="HORIZONTAL" SIZE="10">BCFCONTROLCTL.H--CaptionProp
Function Prototypes</H3>
<P><FONT COLOR="#0066FF"><TT>// IBCFControl methods<BR>
//<BR>
STDMETHOD(get_Alignment)(THIS_ long FAR* Value);<BR>
STDMETHOD(put_Alignment)(THIS_ long Value);<BR>
STDMETHOD(CaptionMethod)(THIS_ BSTR bstrCaption, VARIANT varAlignment,<BR>
long FAR* lRetVal);<BR>
STDMETHOD(get_CaptionProp)(THIS_ VARIANT varAlignment, BSTR FAR* bstrRetVal);<BR>
STDMETHOD(put_CaptionProp)(THIS_ VARIANT varAlignment, BSTR lpszNewValue); <BR>
STDMETHOD_(void, AboutBox)(THIS);</TT></FONT></P>
<P>Finally you add the implementation of the <TT>CaptionProp</TT> function pair (see
Listing 10.20). The method <TT>get_CaptionProp</TT> is called to return data from
the property. In your implementation, you ignore the alignment parameter because
it is of no use to you in this context; you simply return the caption. You need to
make sure that the string variable, <TT>BSTR FAR * bstrRetVal</TT>, which is passed
to the <TT>get_CaptionProp</TT> function does not already point to another string.
If it does, you need to destroy it. Next <TT>get_CaptionProp</TT> uses the function
<TT>SysAllocString</TT> to create a <TT>BSTR</TT> that is returned from the function
call. Note that it is first necessary to convert the <TT>ANSI</TT> string to an <TT>OLECHAR</TT>
string, with the <TT>OLESTRFROMANSI</TT> macro, and then allocate a <TT>BSTR</TT>
from that.</P>
<P><TT>put_CaptionProp</TT> defers to the <TT>CaptionMethod</TT> implementation because
the <TT>CaptionMethod</TT> already does everything that you need.
<H3><A NAME="Heading28"></A>Listing 10.20 <SPACER TYPE="HORIZONTAL" SIZE="10">BCFCONTROLCTL.CPP--CaptionProp
Property Implementation</H3>
<P><FONT COLOR="#0066FF"><TT>STDMETHODIMP CBCFControlControl::get_CaptionProp(VARIANT
varAlignment,<BR>
BSTR FAR* bstrRetVal)<BR>
{<BR>
// if there is a string<BR>
if(*bstrRetVal);<BR>
{<BR>
// free the string because we are going to replace it<BR>
::SysFreeString(*bstrRetVal); <BR>
// clear the reference just to be safe<BR>
*bstrRetVal = NULL;<BR>
} <BR>
// return the caption as a BSTR<BR>
*bstrRetVal = ::SysAllocString(OLESTRFROMANSI(m_lptstrCaption)); <BR>
return S_OK;<BR>
} <BR>
STDMETHODIMP CBCFControlControl::put_CaptionProp(VARIANT varAlignment, <BR>
BSTR lpszNewValue)<BR>
{<BR>
long lRetVal;<BR>
HRESULT hResult = this->CaptionMethod(lpszNewValue, varAlignment, &lRetVal);
<BR>
// use the "CaptionMethod" implementation<BR>
if(hResult == S_OK && lRetVal)<BR>
// let the container know that the property has changed<BR>
m_fDirty = TRUE;<BR>
// this->SetModifiedFlag(); <== MFC version <BR>
return hResult; <BR>
}</TT></FONT></P>
<H3><A NAME="Heading29"></A>Creating Stock Properties</H3>
<P>A <I>stock property</I> is a property that is understood by both a control and
its container and that has a predefined meaning to both. Stock properties are intended
to provide basic uniform functionality to all the controls and containers that implement
them. Stock properties do not require you to implement a lot of code; you just hook
into the existing property.</P>
<P>You are going to support the stock property <TT>BackColor</TT>. For stock properties,
you do not need to add dispids; rather, you take advantage of the constants defined
by OLE. A complete list of system-defined dispids can be found in OLECTL.H, which
is one of the files included with VC++. Listing 10.21 shows your ODL implementation
of the <TT>BackColor</TT> property.
<H3><A NAME="Heading30"></A>Listing 10.21 <SPACER TYPE="HORIZONTAL" SIZE="10">BCFCONTROL.ODL--BackColor
Stock Property Support</H3>
<P><FONT COLOR="#0066FF"><TT>// properties<BR>
[id(dispidAlignment), propget] HRESULT Alignment(<BR>
[out, retval] long * Value);<BR>
[id(dispidAlignment), propput] HRESULT Alignment([in] long Value);<BR>
[id(DISPID_BACKCOLOR), propget] HRESULT BackColor(<BR>
[out, retval] OLE_COLOR * Value);<BR>
[id(DISPID_BACKCOLOR), propput] HRES
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -