📄 ch14.htm
字号:
<LI>OnChangeItemPosition()
<P>
<LI>AssertValid()
<P>
<LI>Dump()
<P>
<LI>Serialize()
</UL>
<P>The constructor simply passes the document pointer along to the base class. The
destructor does nothing. GetDocument() and GetActiveView() are inline functions that
return member variables inherited from the base class by calling the base class function
with the same name and casting the result.</P>
<P>OnChange() is the first of these functions that has more than one line of code
(see Listing 14.17).</P>
<P>
<H4>Listing 14.17  CntrItem.cpp--CShowStringCntrItem::OnChange()</H4>
<PRE>void CShowStringCntrItem::OnChange(OLE_NOTIFICATION nCode,
DWORD dwParam)
{
ASSERT_VALID(this);
COleClientItem::OnChange(nCode, dwParam);
// When an item is being edited (either in-place or fully open)
// it sends OnChange notifications for changes in the state of the
// item or visual appearance of its content.
// TODO: invalidate the item by calling UpdateAllViews
// (with hints appropriate to your application)
GetDocument()->UpdateAllViews(NULL);
// for now just update ALL views/no hints
</PRE>
<PRE>}
</PRE>
<P>Actually, there are only three lines of code. The comments are actually more useful
than the code. When the user changes the contained item, the server notifies the
container. Calling UpdateAllViews() is a rather drastic way of refreshing the screen,
but it gets the job done.</P>
<P>OnActivate() (shown in Listing 14.18) is called when a user double-clicks an item
to activate it and edit it in place. ActiveX objects are usually <I>outside-in</I>,
which means that a single click of the item selects it but doesn't activate it. Activating
an outside-in object requires a double-click, or a single click followed by choosing
the appropriate OLE verb from the Edit menu.</P>
<P>
<H4>Listing 14.18  CntrItem.cpp--CShowStringCntrItem::OnActivate()</H4>
<PRE>void CShowStringCntrItem::OnActivate()
{
// Allow only one in-place activate item per frame
CShowStringView* pView = GetActiveView();
ASSERT_VALID(pView);
COleClientItem* pItem = GetDocument()->GetInPlaceActiveItem(pView);
if (pItem != NULL && pItem != this)
pItem->Close();
COleClientItem::OnActivate();
</PRE>
<PRE>}
</PRE>
<P>This code makes sure that the current view is valid, closes the active items,
if any, and then activates this item.</P>
<P>OnGetItemPosition() (shown in Listing 14.19) is called as part of the in-place
activation process.</P>
<P>
<H4>Listing 14.19  CntrItem.cpp--CShowStringCntrItem::OnGetItemPosition()</H4>
<PRE>void CShowStringCntrItem::OnGetItemPosition(CRect& rPosition)
{
ASSERT_VALID(this);
// During in-place activation,
// CShowStringCntrItem::OnGetItemPosition
// will be called to determine the location of this item.
// The default implementation created from AppWizard simply
// returns a hard-coded rectangle. Usually, this rectangle
// would reflect the current position of the item relative
// to the view used for activation. You can obtain the view
// by calling CShowStringCntrItem::GetActiveView.
// TODO: return correct rectangle (in pixels) in rPosition
rPosition.SetRect(10, 10, 210, 210);
</PRE>
<PRE>}
</PRE>
<P>Like OnChange(), the comments are more useful than the actual code. At the moment,
the View's OnDraw() function draws the contained object in a hard-coded rectangle,
so this function returns that same rectangle. You are instructed to write code that
asks the active view where the object is.</P>
<P>OnDeactivateUI() (see Listing 14.20) is called when the object goes from being
active to inactive.</P>
<P>
<H4>Listing 14.20  CntrItem.cpp--CShowStringCntrItem::OnDeactivateUI()</H4>
<PRE>void CShowStringCntrItem::OnDeactivateUI(BOOL bUndoable)
{
COleClientItem::OnDeactivateUI(bUndoable);
// Hide the object if it is not an outside-in object
DWORD dwMisc = 0;
m_lpObject->GetMiscStatus(GetDrawAspect(), &dwMisc);
if (dwMisc & OLEMISC_INSIDEOUT)
DoVerb(OLEIVERB_HIDE, NULL);
</PRE>
<PRE>}
</PRE>
<P>Although the default behavior for contained objects is outside-in, as discussed
earlier, you can write <I>inside-out objects</I>. These are activated simply by moving
the mouse pointer over them; clicking the object has the same effect that clicking
that region has while editing the object. For example, if the contained item is a
spreadsheet, clicking might select the cell that was clicked. This can be really
nice for the user, who can completely ignore the borders between the container and
the contained item, but it is harder to write.</P>
<P>OnChangeItemPosition() is called when the item is moved during in-place editing.
It, too, contains mostly comments, as shown in Listing 14.21.</P>
<P>
<H4>Listing 14.21  CntrItem.cpp--CShowStringCntrItem::OnChangeItemPosition()</H4>
<PRE>BOOL CShowStringCntrItem::OnChangeItemPosition(const CRect& rectPos)
{
ASSERT_VALID(this);
// During in-place activation
// CShowStringCntrItem::OnChangeItemPosition
// is called by the server to change the position
// of the in-place window. Usually, this is a result
// of the data in the server document changing such that
// the extent has changed or as a result of in-place resizing.
//
// The default here is to call the base class, which will call
// COleClientItem::SetItemRects to move the item
// to the new position.
if (!COleClientItem::OnChangeItemPosition(rectPos))
return FALSE;
// TODO: update any cache you may have of the item's rectangle/extent
return TRUE;
</PRE>
<PRE>}
</PRE>
<P>This code is supposed to handle moving the object, but it doesn't really. That's
because OnDraw() always draws the contained item in the same place.</P>
<P>AssertValid() is a debug function that confirms this object is valid; if it's
not, an ASSERT will fail. ASSERT statements are discussed in Chapter 24, "Improving
Your Application's Performance." The last function in CShowStringCntrItem is
Serialize(), which is called by COleDocument::Serialize(), which in turn is called
by the document's Serialize(), as you've already seen. It is shown in Listing 14.22.</P>
<P>
<H4>Listing 14.22  CntrItem.cpp--CShowStringCntrItem::Serialize()</H4>
<PRE>void CShowStringCntrItem::Serialize(CArchive& ar)
{
ASSERT_VALID(this);
// Call base class first to read in COleClientItem data.
// Because this sets up the m_pDocument pointer returned from
// CShowStringCntrItem::GetDocument, it is a good idea to call
// the base class Serialize first.
COleClientItem::Serialize(ar);
// now store/retrieve data specific to CShowStringCntrItem
if (ar.IsStoring())
{
// TODO: add storing code here
}
else
{
// TODO: add loading code here
}
</PRE>
<PRE>}
</PRE>
<P>All this code does at the moment is call the base class function. COleDocument::Serialize()
stores or loads a number of counters and numbers to keep track of several different
contained items, and then calls helper functions such as WriteItem() or ReadItem()
to actually deal with the item. These functions and the helper functions they call
are a bit too "behind-the-scenes" for most people, but if you'd like to
take a look at them, they are in the MFC source folder (C:\Program Files\Microsoft
Visual Studio\VC98\MFC\SRC on many installations) in the file olecli1.cpp. They do
their job, which is to serialize the contained item for you.</P>
<P><B>Shortcomings of This Container  </B>This container application isn't
ShowString yet, of course, but it has more important things wrong with it. It isn't
a very good container, and that's a direct result of all those TODO tasks that haven't
been accomplished. Still, the fact that it is a functioning container is a good measure
of the power of the MFC classes COleDocument and COleClientItem. So why not build
the application now and run it? After it's running, choose Edit, Insert New Object
and insert a bitmap image. Now that you've seen the code, it shouldn't be a surprise
that Paint is immediately launched to edit the item in place, as you see in Figure
14.7.</P>
<P><A HREF="javascript:popUp('14uvc07.gif')"><B>FIG. 14.7</B></A><B> </B><I>The boilerplate
container can contain items and activate them for in-place editing, like this bitmap
image being edited in Paint.</I></P>
<P>Click outside the bitmap to deselect the item and return control to the container;
you see that nothing happens. Click outside the document, and again nothing happens.
You're probably asking yourself, "Am I still in ShowString?" Choose File,
New, and you see that you are. The Paint menus and toolbars go away, and a new ShowString
document is created. Click the bitmap item again, and you are still editing it in
Paint. How can you insert another object into the first document when the menus are
those of Paint? Press Esc to cancel in-place editing so the menus become ShowString
menus again. Insert an Excel chart into the container, and the bitmap disappears
as the new Excel chart is inserted, as shown in Figure 14.8. Obviously, this container
leaves a lot to be desired.</P>
<P>Press Esc to cancel the in-place editing, and notice that the view changes a little,
as shown in Figure 14.9. That's because CShowStringView::OnDraw() draws the contained
item in a 200*200 pixel rectangle, so the chart has to be squeezed a little to fit
into that space. It is the server--Excel, in this case--that decides how to fit the
item into the space given to it by the container.</P>
<P><A HREF="javascript:popUp('14uvc08.gif')"><B>FIG. 14.8</B></A><B> </B><I>Inserting
an Excel chart gets you a default chart, but it completely covers the old bitmap.</I></P>
<P><A HREF="javascript:popUp('14uvc09.gif')"><B>FIG. 14.9</B></A><B> </B><I>Items
can look quite different when they are not active.</I></P>
<P>As you can see, there's a lot to be done to make this feel like a real container.
But first, you have to turn it back into ShowString.</P>
<P>
<H3><A NAME="Heading3"></A>Returning the ShowString Functionality</H3>
<P>This section provides a quick summary of the steps presented in Chapter 8, "Building
a Complete Application: ShowString." Open the files from the old ShowString
as you go so that you can copy code and resources wherever possible. Follow these
steps:</P>
<DL>
<DT></DT>
<DD><B>1. </B>In ShowStringDoc.h, add the private member variables and public Get
functions to the class.
<P>
<DT></DT>
<DD><B>2. </B>In CShowStringDoc::Serialize(), paste the code that saves or restores
these member variables. Leave the call to COleDocument::Serialize() in place.
<P>
<DT></DT>
<DD><B>3. </B>In CShowStringDoc::OnNewDocument(), paste the code that initializes
the member variables.
<P>
<DT></DT>
<DD><B>4. </B>In CShowStringView::OnDraw(), add the code that draws the string before
the code that handles the contained items. Remove the TODO task about drawing native
data.
<P>
<DT></DT>
<DD><B>5. </B>Copy the Tools menu from the old ShowString to the new container ShowString.
Choose File, Open to open the old ShowString.rc, open the IDR_SHOWSTTYPE menu, click
the Tools menu, and choose Edit, Copy. Open the new ShowString's IDR_SHOWSTTYPE menu,
click the Window menu, and choose Edit, Paste. Don't paste it into the IDR_SHOWSTTYPE_CNTR_IP
menu.
<P>
<DT></DT>
<DD><B>6. </B>Add the accelerator Ctrl+T for ID_TOOLS_OPTIONS as described in Chapter
8, "Building a Complete Application: ShowString." Add it to the IDR_MAINFRAME
accelerator only.
<P>
<DT></DT>
<DD><B>7. </B>Delete the IDD_ABOUTBOX dialog box from the new ShowString. Copy IDD_ABOUTBOX
and IDD_OPTIONS from the old ShowString to the new.
<P>
<DT></DT>
<DD><B>8. </B>While IDD_OPTIONS has focus, choose View, Class Wizard. Create the
COptionsDialog class as in the original ShowString.
<P>
<DT></DT>
<DD><B>9. </B>Use the Class Wizard to connect the dialog controls to COptionsDialog
member variables, as described in Chapter 10.
<P>
<DT></DT>
<DD><B>10. </B>Use the Class Wizard to arrange for CShowStringDoc to catch the ID_TOOLS_OPTIONS
command.
<P>
<DT></DT>
<DD><B>11. </B>In ShowStringDoc.cpp, replace the Class Wizard version of CShowStringDoc::OnToolsOptions()
with the OnToolsOptions() from the old ShowString, which puts up the dialog box.
<P>
<DT></DT>
<DD><B>12. </B>In ShowStringDoc.cpp, add <B>#include "OptionsDialog.h"</B>
after the #include statements already present.
</DL>
<P>Build the application, fix any typos or other simple errors, and then execute
it. It should run as before, saying Hello, world! in the center of the view. Convince
yourself that the Options dialog box still works and that you have restored all the
old functionality. Then resize the application and the view as large as possible,
so that when you insert an object it doesn't land on the string. Insert an Excel
chart as before, and press Esc to stop editing in place. There you have it: A version
of ShowString that is also an ActiveX container. Now it's time to get to work making
it a <I>good</I> container.</P>
<P>
<H2><A NAME="Heading4"></A>Moving, Resizing, and Tracking</H2>
<P>The first task you want to do, even when there is only one item contained in ShowString,
is to allow the user to move and resize that item. It makes life simpler for the
user if you also provide a <I>tracker rectangle</I>, a hashed line around the contained
item. This is easy to do with the MFC class CRectTracker.</P>
<P>The first step is to add a member variable to the container item (CShowStringCntrItem)
definition in CntrItem.h, to hold the rectangle occupied by this container item.
Right-click CShowStringCntrItem in ClassView and choose Add Member Variable. The
variable type is CRect, the declaration is m_rect; leave the access public.</P>
<P>m_rect needs to be initialized in a function that is called when the container
item is first used and then never again. Whereas view classes have OnInitialUpdate()
and document classes have OnNewDocument(), container item classes have no such called-only-once
function except the constructor. Initialize the rectangle in the constructor, as
shown in Listing 14.23.</P>
<P>
<H4>Listing 14.23  CntrItem.cpp--Constructor</H4>
<PRE>CShowStringCntrItem::CShowStringCntrItem(CShowStringDoc* pContainer)
: COleClientItem(pContainer)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -