An Overview of COM Collections

 < Free Open Study > 



A COM collection, like a COM enumerator, is a coclass responsible for managing other related COM objects. Unlike a COM enumerator, COM collections expose their services using IDispatch, and therefore all COM clients are able to access the subobjects they contain.

A traditional COM collection provides methods that allow the object user to access an individual item by name, obtain the count of all items, and add and remove individual items. Contrary to what you may expect, there is no standard interface that represents a COM collection. As we have seen from our examination of enumerators, the items a given collection will hold depend on your specific development effort.

Each collection will support the same set of methods, with more or less the same signatures. When you develop your collection objects, you do have a good deal of flexibility; however, if you stick to the accepted format, users of your collection will already have a heads-up as to how to use it.

Standard Methods of a COM Collection

Although you have flexibility as to the methods of a collection object, most users will expect the following:

COM Collection Method

Meaning in Life

Item()

This allows a client to fetch a single named item from the collection. Items in a collection can be referred to by position (the tenth CoBoat) or some tag (the boat named "Viper").

_NewEnum

_NewEnum is a read-only property that allows your collection to be iterated over using the For...Each syntax of Visual Basic.

Add()

Allows a client to insert another COM object into the collection.

Remove()

Allows a client to remove an item from the collection.

Count()

This read-only property can be called by a client to obtain the current number of objects in the collection. As we will see, a collection can grow and shrink throughout its lifetime, and it is handy for the client to know how many items are currently being managed.

Although many COM collections do indeed support all five members, only Count(), Item(), and _NewEnum are mandatory. Therefore, let's begin to develop a collection that supports these first three methods, and tackle Add() and Remove() a bit later in the chapter.

Building a Simple COM Collection

 On the CD   If you would like to see the first iteration of the CoSquiggle collection for yourself, consult your companion CD-ROM (Squiggle Collection 1).

To illustrate building a COM collection, we will be creating a coclass that maintains ATL CoSquiggle objects. As you recall from Chapter 10, CoSquiggle was a wonderful (and production level) coclass that supported the dual interface, ICoSquiggle. In this incarnation of ICoSquiggle, we find the following layout:

// A CoSquiggle supports the following behavior. [ object, uuid(9FB9E736-59BA-11D3-B926-0020781238D4), dual,   helpstring("ICoSquiggle Interface"), pointer_default(unique) ] interface ICoSquiggle : IDispatch {      [id(1), helpstring("method Draw")]      HRESULT Draw();      [propget, id(2), helpstring("property Name")]      HRESULT Name([out, retval] BSTR *pVal);      [propput, id(2), helpstring("property Name")]      HRESULT Name([in] BSTR newVal); }; 

The implementation of CoSquiggle allows each squiggle to be drawn (read: display a message box) as well as be assigned a pet name that is maintained in a private CComBSTR. To follow a popular convention used with collection objects, we will give the name CoSquiggles to the collection that manages CoSquiggle objects. Typically, you will find COM collection objects taking the plural form of the object they are in charge of.

CoSquiggles maintains a fixed array of IDispatch pointers, which represent the set of CoSquiggle objects it will maintain. To begin implementing the collection, we set each index of the array to a new CoSquiggle during the FinalConstruct() invocation. As we are dealing with COM objects, FinalRelease() will be used to release the IDispatch references for each coclass:

// Create a set of CoSquiggles upon startup, and kill them when shutting down. class ATL_NO_VTABLE CCoSquiggles:      public CComObjectRootEx<CComSingleThreadModel>,      public CComCoClass<CCoSquiggles, &CLSID_CoSquiggles>,      public IDispatchImpl<ICoSquiggles, &IID_ICoSquiggles,                       &LIBID_SQUIGGLECOLLECTIONLib> { public: ...      HRESULT FinalConstruct()     // Create the squiggles!      {           for(int i = 0; i < MAX_SQUIGGLES; i++)           {                CComObject<CCoSquiggle> *pSquig = NULL;                CComObject<CCoSquiggle>::CreateInstance(&pSquig);                IDispatch* pDisp;                pSquig->QueryInterface(IID_IDispatch, (void**)&pDisp);                m_pArrayOfSquigs[i] = pDisp;           }           return S_OK;      }      void FinalRelease()          // Kill the squiggles!      {           for(int i = 0; i < 10; i++)           {                m_pArrayOfSquigs[i]->Release();           }      } ... private:      // Array of IDispatch pointers to represent the squiggles.      IDispatch* m_pArrayOfSquigs[MAX_SQUIGGLES]; };

Notice that CoSquiggles is only smart enough to maintain a fixed array of CoSquiggle objects no greater than MAX_SQUIGGLES. At this stage of the game, this is permissible, as we will not yet allow clients to add or remove an item. Later on, we will rework this collection to make use of an STL vector to allow the number of CoSquiggle objects to grow and shrink on demand.

Now that we have set up some subobjects in the collection, we must provide a way in which the COM client can access them. Our collection supports a single dual interface, ICoSquiggles, which provides a minimal way for clients to work with the collection:

// Our collection supports the following members. [ object, uuid(9FB9E734-59BA-11D3-B926-0020781238D4), dual,   helpstring("ICoSquiggles Interface"), pointer_default(unique) ] interface ICoSquiggles : IDispatch {      [id(DISPID_VALUE), helpstring("method Item")]      HRESULT Item([in] VARIANT index, [out, retval] VARIANT* pItem);      [propget, id(1), helpstring("property Count")]      HRESULT Count([out, retval] long *pVal);      [propget, restricted, id(DISPID_NEWENUM)]      HRESULT _NewEnum([out, retval] LPUNKNOWN *pVal); }; 

For the most part, this is a standard dual interface described in IDL. However, notice that we have specified some predefined DISPIDs for the Item() and _NewEnum members. This is not optional. Most COM language mappings will be on the lookout for the DISPID_VALUE and DISPID_NEWENUM constants when working with a collection object.

DISPID_VALUE is used to specify that this member of the dispinterface is the default method. Some COM language mappings (such as VB) recognize this DISPID, and thus specifying the item is optional, as it will be called automatically if no other method is present. By the same token, DISPID_NEWENUM is used to signal that this collection supports For...Next syntax. As _NewEnum is a hidden member of the dispinterface which is called secretly by Visual Basic, we have added the [restricted] attribute in its declaration. As far as Count() goes, any unique DISPID (greater than zero) will do.

Implementing Count()

This method of a collection simply returns the current number of items. As CoSquiggles will always have a fixed number, the implementation of Count() is trivial:

// Return the number of squiggles in the collection. STDMETHODIMP CCoSquiggles::get_Count(long *pVal) {      *pVal = MAX_SQUIGGLES;      return S_OK; }

Implementing Item()

If you looked closely at the IDL definition of Item(), you will notice that we specified two VARIANTs:

// Give me a VARIANT and I will give you back a VARIANT. HRESULT Item([in] VARIANT index, [out, retval] VARIANT* pItem);

Most developers who use collections want some degree of flexibility. A well-written Item() method should allow a client to specify a given member by name (for example, a text literal) or position. Nothing says parameter polymorphism like the VARIANT. Our implementation of Item() will require the user to specify a numerical index when referring to the CoSquiggle they are interested in obtaining. Once we find the correct item from our IDispatch* array, we set the [out, retval] to this particular IDispatch interface (e.g., the squiggle they asked for). Thus:

// Using the index, set the [out, retval] to the correct IDispatch pointer, // thereby giving the client access to the correct CoSquiggle. STDMETHODIMP CCoSquiggles::Item(VARIANT index, VARIANT *pItem) {      // Be sure we have a numerical index.      if(index.vt == VT_I2)      {           // Be sure we are in range.           if(index.lVal >=0 && index.lVal <= MAX_SQUIGGLES)           {                // Find the correct squiggle.                IDispatch* pDisp = m_pArrayOfSquigs[index.lVal];                // Add a ref!                pDisp->AddRef();                // Set the type of VARIANT and return                pItem->vt = VT_DISPATCH;                pItem->pdispVal = pDisp;                return S_OK;           }      }      return E_INVALIDARG; }

There is not too much to say about this code block, as it is typical VARIANT programming. The only caution to be aware of is that we must be sure to AddRef() our new IDispatch reference, as we have another client making use of it.

Exercising Count() and Item() from within Visual Basic

Although we have not yet implemented _NewEnum, our collection object is ready to be taken out for an initial test drive. Assume we have a Standard EXE project, which has the following Click routine for a button named btnGetSquiggle3:

' Get access to the squiggle three and draw it. ' Private Sub btnGetSquiggle3_Click()      Dim squigColl As New CoSquiggles      Dim o As CoSquiggle      Set o = squigColl.Item(3)      o.Draw End Sub

Here, we create a brand new squiggle collection, and grab the third item in the collection. Once we have a reference to this CoSquiggle, we may access the Draw() method accordingly. As we have seen, the Item() method has been given a special predefined DISPID of DISPID_VALUE. This marks Item() as the default member in the dispinterface, and therefore we can shorten our VB code just a bit:

' DISPID_VALUE provides yet another way to save some keystrokes. ' Private Sub btnGetSquiggle3_Click()      Dim squigColl As New CoSquiggles      Dim o As CoSquiggle      Set o = squigColl(3)      o.Draw End Sub

If we really wanted some streamlined code, we could shorten things a bit more:

' Optimized access to the third item in the collection. ' Private Sub btnGetSquiggle3_Click()      Dim squigColl As New CoSquiggles      squigColl(3).Draw End Sub 

Now, what if we wanted to iterate over all items in the collection, and grab each CoSquiggle's name to place in a list box? First off, assume that the collection object has set each item's Name property to some value upon startup (currently each CoSquiggle is unnamed). We could make use of the Count property, and loop over each member in the collection as so:

' Loop over the items in the collection, and get ' each one's name. ' Private Sub btnGetAllSquiggles_Click()      Dim squigColl As New CoSquiggles      Dim max As Long      Dim i As Integer      max = squigColl.Count      For i = 1 To max           List1.AddItem squigColl(i).Name      Next i End Sub


Figure 11-5: Using our initial collection.

Implementing _NewEnum

We have a fine start for a custom collection object. However, we must also support the _NewEnum property to be a compliant COM collection. Visual Basic developers are used to iterating over a COM collection using a special bit of syntax. Using For...Each, a collection may be exercised as so:

' Looping through a collection using _NewEnum. ' Private Sub btnGet_Click()      Dim o as New CoSquiggles      Dim s As CoSquiggle      For Each s In o           List1.AddItem s.Name      Next End Sub

What this code is basically saying is "for every CoSquiggle in the CoSquiggles collection, call the get_Name() method." Note that we do not ever need to access the Count property or Item() method. The For...Next syntax allows us to define a placeholder (in this case, CoSquiggle) to morph itself among each item in the collection. However, if we were to try this block of code, we would receive a runtime error. The offending line of code would be "For Each s In squigColl." The reason? VB is asking our collection to Invoke the member of our dispinterface that has been assigned the DISPID_NEWENUM value:

' VB calls _NewEnum whenever the For...Each syntax is used. ' For Each s In squigColl          ' Calls Invoke() for DISPID_NEWENUM      List1.AddItem s.Name Next s 

To implement _NewEnum, we must create a COM enumerator that supports the IEnumVARIANT interface (recall that COM does define some stock enumerator interfaces). The code behind get__NewEnum will look very much like the ATL code that provided access to our custom IEnumPeople interface. Here is the code behind get_NewEnum:

// This block of code allows VB-like iteration. STDMETHODIMP CCoSquiggles::get__NewEnum(LPUNKNOWN *pVal) {      // Make a temp array of VARIANTS and fill with the current CoSquiggles.      VARIANT* pVar = new VARIANT[MAX_SQUIGGLES];      for(int i = 0; i < MAX_SQUIGGLES; i++)      {           pVar[i].vt = VT_DISPATCH;           pVar[i].pdispVal = m_pArrayOfSquigs[i];      }      // Now make the enum with the monster template.      typedef CComObject< CComEnum< IEnumVARIANT,                        &IID_IEnumVARIANT, VARIANT,                        _Copy< VARIANT > > > enumVar;      enumVar* pEnumVar = new enumVar;      pEnumVar->Init(&pVar[0], &pVar[MAX_SQUIGGLES], NULL, AtlFlagCopy);      delete[] pVar;      // Return the enum.      return pEnumVar->QueryInterface(IID_IUnknown, (void**)pVal); }

As we have an array of IDispatch pointers in our CoSquiggles collection, we need to convert these items to an array of VARIANTs before we can hand them over to CComEnum<>. Once we have done so, we can again make use of the ATL enumeration templates; however, this time we must specify the _Copy<VARIANT> template rather than _CopyInterface<T>. Furthermore, as IEnumVARIANT has already been defined (in <oaidl.idl>) we do not need to write any IDL code for this enumeration interface. We can just send on IEnumVARIANT and IID_EnumVARIANT as the first and second parameters to CComEnum<>, and return the IUnknown interface to VB. With this, our bare bones collection is complete.

Adding and Removing CoSquiggles

A COM collection must support the Count, _NewEnum, and Item() members. Add() and Remove() are optional, but most collections would like the ability to be a bit more dynamic than our current CoSquiggles collection. To allow the client to insert and remove members, we first need a way to allow CoSquiggles to store a varying number of IDispatch pointers, so clearly our fixed array won't do.

It is very commonplace to make use of the Standard Template Library (STL) when working with ATL projects. STL is a collection of generic C++ templates that provide a number of (non-COM) collections, dynamic arrays, string support, and so forth. We will be making use of the STL vector<> template, as this will allow us to work with any number of IDispatch pointers.

To begin, we will need to replace our static IDispatch array with a new STL vector. FinalConstruct() will fill the vector with three initial CoSquiggles using the push_back() member function. As always, FinalRelease() will call Release() on all interfaces. Here, we can make use of the size() method of the STL vector<> template to obtain the current number of CoSquiggles. Here are the changes thus far:

// This iteration of the squiggle collection maintains a vector of IDispatch interfaces. class ATL_NO_VTABLE CSquiggleCollection2 : ... { public: ...      HRESULT FinalConstruct()      {           for(int i = 0; i < 3; i++)           {                // First make a squiggle.                CComObject< CCoSquiggle > *pSquig = NULL;                CComObject< CCoSquiggle >::CreateInstance(&pSquig);                // Fill squiggle with some data...                // Get the IDispatch pointer and place in the STL vector.                LPDISPATCH pDisp;                if(SUCCEEDED(pSquig->QueryInterface(IID_IDispatch,                                                (void**)&pDisp)))                {                     m_vecSquiggles.push_back(pDisp);                }           }           return S_OK;      }      void FinalRelease()      {           // Release all the squiggles from the vector.           int size = m_vecSquiggles.size();           for(int i = 0; i < size; i++)           {                LPDISPATCH pDisp;                pDisp = m_vecSquiggles[i];                pDisp -> Release();           }      } ... private:      std::vector< IDispatch* > m_vecSquiggles; };

Before we get to Add() and Remove(), we should update our current implementation of Count, given that the size of the vector could be anything (get__NewEnum and Item() could be reworked in a similar manner, by making use of the vector's size() method):

// Grab the size of the vector. STDMETHODIMP CSquiggleCollection2::get_Count(long *pVal) {      *pVal = m_vecSquiggles.size();      return S_OK; }

The Add() method is trivial; however, we do have some design issues to contend with. What parameters do we wish to have in our version of Add()? We have two choices. We can assume the client will create a CoSquiggle first, and send it into our collection. If this is the case, we may have an IDL definition as so:

// This Add() method takes a brand new CoSquiggle. [id(2), helpstring("method Add")] HRESULT Add([in] IDispatch *pnewSquiggle);

Alternatively, we may configure Add() to take state data, which we use to create a new CoSquiggle ourselves. This IDL might look like the following:

// This Add() method takes the name of a CoSquiggle, inferring that we create one // ourselves in the coclass. [id(2), helpstring("method Add")] HRESULT Add([in] BSTR name);

We will assume the client will configure a new CoSquiggle and give it to us when it wishes to add it into our collection. Thus, all we have to do is place the new IDispatch* into the vector and call AddRef() on the incoming interface:

// Add in the new CoSquiggle. STDMETHODIMP CSquiggleCollection2::Add(IDispatch *pnewSquiggle) {      // Here we are going to add a new squiggle the client gives us.      m_vecSquiggles.push_back(pnewSquiggle);      pnewSquiggle->AddRef();      return S_OK; }

Remove() is also very easy to implement, but again we have a design choice. How would we like to identify the item to remove? By numerical index? By an embedded key? Good question, but there is no hard and fast rule. If you wish to give your user maximum flexibility, you may define Remove() to take a VARIANT parameter, test for the type, and remove accordingly. For our purposes, let's assume the client identifies the number of the CoSquiggle to remove:

// Give me an index to remove... STDMETHODIMP CSquiggleCollection2::Remove(long index) {      // Be sure we are in range.      if(index >=0 && index <= m_vecSquiggles.size())      {           // Find the correct squiggle.           IDispatch* pDisp = m_vecSquiggles[index];           // Remove it.           pDisp->Release();           m_vecSquiggles.erase(m_vecSquiggles.begin() + index);           return S_OK;      }      return E_FAIL; }

After a quick check to ensure the client has sent us an index within range, we gain a reference to the squiggle and call Release(), thereby reducing its reference count by one. As well, we need to remove this item from the vector using the erase() method. With these minor modifications, we now have a language-neutral way to maintain a variable number of COM objects. To wrap it up, here is some VB code making use of these new items:

' Assume a CoSquiggles object named coll. ' Private Sub btnAdd_Click()      Dim o As New CoSquiggle      o.Name = txtName      coll.Add o End Sub Private Sub btnRemove3_Click()      coll.Remove 3 End Sub

Lab 11-2: Building a Complete Custom Collection with ATL

This lab will allow you to create a custom collection with support for adding and removing coclasses, all of which are held in an STL vector. COM collections have the same general look and feel as COM enumerators, although a collection usually exports the underlying objects in an [oleautomation] friendly manner.

To illustrate this last point, your companion CD contains a VBScript client that grabs the current objects in the collection and exercises each one.

 On the CD   The solution for this lab can be found on your CD-ROM under:
Labs\Chapter 11\Squiggle Collection 2
Labs\Chapter 11\Squiggle Collection 2\VB Client
Labs\Chapter 11\Squiggle Collection 2\VBScript Client

Step One: Create the Subobject

Begin by creating a new ATL DLL project named ATLCollection. Use the ATL Object Wizard to insert a new Simple Object named CoSquiggle. This simple coclass supports a single dual interface, which defines a property called Name and a method named Draw(). Populate your initial interface with these items, and implement them in your coclass. Be sure your Draw() logic informs the end user which item is being drawn. Here are the details behind each item:

// The Name property. STDMETHODIMP CCoSquiggle::get_Name(BSTR *pVal) {      *pVal = m_bstrName.Copy();      return S_OK; } STDMETHODIMP CCoSquiggle::put_Name(BSTR newVal) {      m_bstrName = newVal;      return S_OK; } // The Draw() method. STDMETHODIMP CCoSquiggle::Draw() {      USES_CONVERSION;      CComBSTR temp;      temp = "Drawing a squiggle named ";      temp += m_bstrName;      MessageBox(NULL, W2A(temp.m_str),           "Information about this squiggle", MB_OK | MB_SETFOREGROUND);      return S_OK; } 

Step Two: Create the COM Collection Interface

Now, insert another ATL Simple Object named SquiggleCollection2, and again be sure that the [default] interface is configured as a dual. Begin by adding the expected items to your ISquiggleCollection2 interface, allowing the client to work with the inner set of CoSquiggle objects. Here is the IDL:

// The custom collection interface. interface ISquiggleCollection2 : IDispatch {      [id(DISPID_VALUE), helpstring("method Item")]      HRESULT Item([in] VARIANT index, [out, retval] VARIANT* pItem);      [propget, id(1), helpstring("property Count")]      HRESULT Count([out, retval] long *pVal);      [id(2), helpstring("method Add")]      HRESULT Add([in] IDispatch *pnewSquiggle);      [id(3), helpstring("method Remove")]      HRESULT Remove([in] long index);      [propget, restricted, id(DISPID_NEWENUM)]      HRESULT _NewEnum([out, retval] LPUNKNOWN *pVal); };

Remember, the _NewEnum property must be assigned the default DISPID, DISPID_ NEWENUM, while Item() must be assigned DISPID_VALUE. Also notice that unlike the previous ATL enumerator, we are working with IDispatch* interfaces rather than a custom vTable interface. After all, that is the whole point of a COM collection: allow all COM-enabled languages to access the objects.

Step Three: Implement the Initial Collection Object

Before we implement the methods of the ISquiggleCollection2 interface, we need to set up some subobjects. Begin by overriding FinalConstruct() to fill up a vector of CoSquiggle objects, named m_vecSquiggles. As the client can add and remove items to this collection, configure three initial items:

// This time we are making use of the STL to hold our IDispatch interfaces. #include <vector> #include "cosquiggle.h" // Create three squiggles automatically. // Class defines: std::vector< IDispatch* > m_vecSquiggles; HRESULT FinalConstruct() {      for(int i = 0; i < 3; i++)      {           // First make a squiggle.           CComObject< CCoSquiggle > *pSquig = NULL;           CComObject< CCoSquiggle >::CreateInstance(&pSquig);           // Give the new squiggle a name.           CComBSTR temp[3] = {L"Mandy", L"Whoppie", L"Eric"};           pSquig->put_Name(temp[i]);           // Get the IDispatch pointer and place in the STL vector.           LPDISPATCH pDisp;           if(SUCCEEDED(pSquig->QueryInterface(IID_IDispatch, (void**)&pDisp)))           {                m_vecSquiggles.push_back(pDisp);           }      }      return S_OK; } 

Your collection will need to release all items in the vector when it is destroyed, so make a call to FinalRelease():

// Release all the squiggles from the vector. void FinalRelease() {      int size = m_vecSquiggles.size();      for(int i = 0; i < size; i++)      {           LPDISPATCH pDisp;           pDisp = m_vecSquiggles[i];           pDisp -> Release();      } }

Now that we have some default data, we can begin to implement each of the methods in the collection. The Count property is used by the client to obtain the current number of items held by the collection object. To implement this method, make a call to size() from your internal vector object:

// Return the current count. STDMETHODIMP CSquiggleCollection2::get_Count(long *pVal) {      *pVal = m_vecSquiggles.size();      return S_OK; }

The Item() method allows a client to retrieve a given subobject from the collection. For our purposes, the Item() method will only allow the client to ask for a given squiggle by numerical index:

// Return the correct squiggle. STDMETHODIMP CSquiggleCollection2::Item(VARIANT index, VARIANT *pItem) {      // Be sure we have a number.      if(index.vt == VT_I2)      {           // Be sure we are in range.           if(index.lVal >=0 && index.lVal <= m_vecSquiggles.size())           {                // Find the correct squiggle.                IDispatch* pDisp = m_vecSquiggles[index.lVal];                // Add the ref!                pDisp->AddRef();                // Set the type of VARIANT and return.                pItem->vt = VT_DISPATCH;                pItem->pdispVal = pDisp;                return S_OK;           }      }      return E_INVALIDARG; } 

Finally, we will need to implement _NewEnum. This member will allow a VB client to iterate over our collection using an IEnumVARIANT enumerator:

// This block of code allows VB-like iteration. STDMETHODIMP CSquiggleCollection2::get__NewEnum(LPUNKNOWN *pVal) {      // Make a temp array of VARIANTS and fill with the current CoSquiggles.      int size = m_vecSquiggles.size();      VARIANT* pVar = new VARIANT[size];      for(int i = 0; i < size; i++)      {           pVar[i].vt = VT_DISPATCH;           pVar[i].pdispVal = m_vecSquiggles[i];      }      // Now make the CComEnum object.      typedef CComObject< CComEnum< IEnumVARIANT, &IID_IEnumVARIANT,                VARIANT, _Copy< VARIANT > > > enumVar;      enumVar* pEnumVar = new enumVar;      pEnumVar->Init(&pVar[0], &pVar[size], NULL, AtlFlagCopy);      delete[] pVar;      // return the enum.      return pEnumVar->QueryInterface(IID_IUnknown, (void**)pVal); }

At this point, we have provided a way for a COM client to obtain any of our three CoSquiggle objects. To finish up this collection, we need to provide a way to add and remove items.

Step Four: Add and Remove Items

First, we will implement the Add() item. We have specified this member such that the client is responsible for creating a new CoSquiggle, setting the state, and sending it our direction. This comes to us as an IDispatch*, which we can insert into the vector:

// Here we are going to add a new squiggle the client gives us. STDMETHODIMP CSquiggleCollection2::Add(IDispatch *pnewSquiggle) {      m_vecSquiggles.push_back(pnewSquiggle);      pnewSquiggle->AddRef();      return S_OK; }

Removing an item is just about as easy:

// Remove the item from the vector, and release the reference. STDMETHODIMP CSquiggleCollection2::Remove(long index) {      // Be sure we are in range.      if(index >=0 && index <= m_vecSquiggles.size())      {           // Find the correct squiggle.           IDispatch* pDisp = m_vecSquiggles[index];           // Remove it.           pDisp->Release();           m_vecSquiggles.erase(m_vecSquiggles.begin() + index);           return S_OK;      }      return E_FAIL; } 

Now the collection is complete! To finish up this chapter, let's build a VB client.

Step Five: A Visual Basic Client

Open up VB and create a new Standard EXE project (don't forget to set a reference to your type library). Create a GUI that will display each CoSquiggle in a VB ListBox object. As well, we would like to have the client select a member from the list box and ask that item to draw itself. Figure 11-6 shows the initial design.


Figure 11-6: Grab the squiggles!

Here's the code behind this form:

' A helper function to refresh the list box. ' Private Sub RefreshGUI()      List1.Clear      Dim s As CoSquiggle      For Each s In coll           List1.AddItem s.Name      Next End Sub ' Draw the selected squiggle (-1 indicates there is no item selected). ' Private Sub btnDraw_Click()      Dim i As Integer      If List1.ListIndex <> -1 Then           coll.Item(List1.ListIndex).Draw      End If End Sub ' Get all squiggles and put the name in the list box. ' Private Sub btnGet_Click()      RefreshGUI End Sub

Next, we need to extend our GUI to allow the user to insert and remove items, as well as see the current count. See Figure 11-7 for an example.


Figure 11-7: Squiggle driver.

The code behind the Add button will create a temporary CoSquiggle and send it into the collection object:

' Create a new CoSquiggle and place into the collection. ' Private Sub btnAdd_Click()      If txtName.Text <> "" Then           Dim o As New CoSquiggle           o.Name = txtName           coll.Add o           RefreshGUI      Else           MsgBox "You did not enter a name for this squiggle..."      End If End Sub

When the user double-clicks on an item in the list box, we will ask the collection to remove this item from the vector and refresh the display:

' Remove the correct squiggle. ' Private Sub List1_DblClick()      coll.Remove List1.ListIndex      RefreshGUI End Sub

Finally, update RefreshGUI to set the label object with the current number of squiggles. That about wraps things up for this chapter. Don't forget that your collection is usable from the web (your lab solution contains a simple VBScript tester). In Chapter 12, we will examine the connectable object architecture, which (as fate would have) also makes use of enumerator interfaces.



 < Free Open Study > 



Developer's Workshop to COM and ATL 3.0
Developers Workshop to COM and ATL 3.0
ISBN: 1556227043
EAN: 2147483647
Year: 2000
Pages: 171

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net