Example: Creating a List Box Control

The following example creates the Oledblst.ocx list box control using the ActiveX Control Creation Wizard. Some features and details have been left out of the example for the sake of simplicity. Refer to the Oledblst project in the Samples directory, included with this SDK, to find the complete source code for this sample.

In addition to the DataSource and DataMember properties, this complex bound list control has a ListColumn property to select the column specified for the display data. At run time, the list control displays all rows of the data for the selected column. It also has the ability to update or display the current record by setting or retrieving the Text property.

Note   This sample subclasses a standard Windows list box control; therefore, the maximum size of the list may vary depending on your operating system. Also, the sample does not support duplicate fields in the column to which it is bound.

Creating the Oledblst.ocx List Box Control

The ActiveX Control Creation Wizard is part of the ActiveX Controls Framework.

To create an ActiveX control in Visual Basic

  1. Follow the instructions in Appendix A to start the control wizard.

  2. Type the name of your project—for example, Oledblst.

  3. Type in or browse for the directory in which you want the project to be saved.

  4. Select the project type; the default project is "ActiveX Control." Click Next.

  5. Type the name of your control—for example, OledbList.

  6. Select the Windows control that you want to subclass—for example, LISTBOX.

  7. Select the Complex Data Bound Control check box. This adds property definitions in the .odl file, necessary data members for the control class, implementation stubs for the DataSource and DataMember properties, and necessary header files to your control sources.

  8. Select the Implement IPerPropertyBrowsing check box. This adds code necessary to support IPerPropertyBrowsing for the control. Click Next.

  9. Click Finish. The wizard creates a new ActiveX Framework Control in the designated directory.

Once the wizard has successfully completed its work, you can start working on the new ActiveX Framework Control project.

Adding Rowset Support

This sample control uses CRowset, the IRowset wrapper class implemented in Rowset.cpp and defined in Rowset.h, to simplify rowset handling. Refer to Chapter 1, “Consuming Rowsets,” for a review.

To use CRowset, open Oledblistctl.h and add the following include statement:

#include <adbctl.h>   // Data bind control library

Next, add CRowset to the COLEDBListControl class inheritance list:

class COLEDBListControl : public COleControl, ….,
                  // Add this line for CRowset
                  public CRowset

Now the control is ready to consume rowsets.

Initializing the Rowset

For complex data-bound controls, the wizard adds declarations for m_pDataSource to the COLEDBListControl class definition, and dmDataMember to the OLEDBLISTCTLSTATE structure definition. These definitions are located in the OleDBListCtl.h header file. It is the control’s responsibility to persist the DataMember property. The DataSource property does not need to be persisted because it is set either by the container or in code. Stub implementations for the DataSource and DataMember properties were also added to Oledblistctl.cpp. The next code fragment illustrates the stubs with code additions highlighted in bold.

HRESULT COLEDBListControl::putref_DataSource(DataSource *pDataSource)
{
   HRESULT hr = S_OK;

   if (m_pDataSource)
   {
      // TODO: Add code to do cleanup.
      AttachRowset(NULL);
      
      // Clear the previous data source.
      //
      m_pDataSource->Release();
   }
   // Cache the new data source.   
   //
   m_pDataSource = pDataSource;

   if (m_pDataSource)
   {
      // Take ownership.
      //
      m_pDataSource->AddRef();

      // TODO: Add code to process the new data source.
   }
   // Repaint the contents at run time.
   //
   if (!DesignMode())
      InvalidateControl(NULL);

   // Notify sinks.
   //
   PropertyChanged(DISPID_DATASOURCE);

   return hr;
}

HRESULT COLEDBListControl::put_DataMember(DataMember dmDataMember)
{
   HRESULT hr = S_OK;

   // Make a copy of the data member.
   //
   if (dmDataMember)
   {
      dmDataMember = (DataMember)COPYBSTR(dmDataMember);
      RETURN_ON_NULLALLOC(dmDataMember);
   }
   // Free the current data member.
   //
   if (m_state.dmDataMember)
      SysFreeString((BSTR)m_state.dmDataMember);
   
   // Keep the new data member.
   //
   m_state.dmDataMember = dmDataMember;

   // TODO: Add code to handle the change in the data member.
   AttachRowset(NULL);

   // Repaint the contents at run time.
   //
   if (!DesignMode())
      InvalidateControl(NULL);

   // Notify sinks.
   //
   PropertyChanged(DISPID_DATAMEMBER);

   return hr;
}

COLEDBListControl::AttachRowset takes a pointer to an IUnknown interface that can be an IRowset or an IRowPosition interface. When the pointer is Null, the method performs necessary cleanup to detach itself from the currently attached rowset. This method also calls the base class CRowset::Attach method to allow the base class to perform initialization and cleanup. AttachRowset is implemented as follows:

HRESULT COLEDBListControl::AttachRowset(IUnknown *pUnk)
{
   // If you have a previous Rowset, then clean up first.
   //
   if (CRowset::GetRowset())
   {
      // Free the ColumnInfo array.
      FreeColumns();

      // Disconnect notifications.
      m_notifications.Disconnect();

      // Release the row accessor.
      FreeRowAccessor();

      // Clear the list contents.
      ClearContents();

      // Let base class do cleanup.
      CRowset::Attach(NULL);
   }
   // If you are not given a new interface, 
   // then the work is done.
   if (NULL == pUnk)
      return S_OK;

   // First let the base class attach to the
   // new rowset.
   HRESULT hr = CRowset::Attach(pUnk);
   RETURN_ON_FAILURE(hr);

   // Connect notifications.
   hr = m_notifications.Connect();

   return hr;
}

To initialize the rowset, you need to call COLEDBListControl::AttachRowset with a pointer to an IRowPosition or an IRowset interface. You want to use an IRowPosition interface and place the initialization code in its own method so you can call it on demand:

HRESULT COLEDBListControl::InitRowset(void)
{
   // If GetRowset returns a value then
   // the rowset is already attached, so
   // return success.
   //
   if (CRowset::GetRowset())
      return S_OK;

   if (m_pDataSource)
   {
      // Request IRowPostion from the data source.
      //
      IUnknown *pRowPos;
      HRESULT hr = m_pDataSource->getDataMember(m_state.dmDataMember, IID_IRowPosition, &pRowPos);
      RETURN_ON_FAILURE(hr);

      // If an interface was returned, then data is 
      // available, so attach to the interface.
      // 
      if (pRowPos)
      {
         hr = AttachRowset(pRowPos);
         pRowPos->Release();
         return hr;
      }
      // IDataSource::getDataMember succeeds but
      // did not return an interface, so return
      // S_FALSE for no data.
      //
      return S_FALSE;
   }
   // No data source, so return failure.
   //
   return E_FAIL;
}

InitRowset is called before any base-class CRowset methods are accessed to ensure that everything is set up correctly. Calling InitRowset on demand postpones the initialization code until it is needed. The example uses this style of delayed initialization throughout.

Getting Column Information

Column information is relevant at design time to support property browsing for the ListColumn property, and at run time for data binding. Upon request, COLEDBListControl queries for a list of columns and caches this list until it is no longer needed.

Getting the Columns

The columns are cached in a COLUMNINFO array stored in the m_pColumns class member variable. The columns count is stored in the m_pColumns class member variable. The array and its count are initialized in InitColumns and freed in FreeColumns. InitColumns is called prior to accessing the m_pColumns member variable to ensure that the array is initialized. Again, this is an example of delayed initialization.

Calling CRowset::GetColumns

This base-class method allocates a COLUMNINFO (see Adbhelp.h and Adbhelp.cpp) array, and returns the array and column count in the corresponding arguments passed to the method. The implementation of COLEDBListControl::InitColumns demonstrates this:

HRESULT COLEDBListControl::InitColumns(void)
{
   // If you already have columns, then return.
   if (m_pColumns)
      return S_OK;

   // Ensure that the rowset is attached.
   HRESULT hr = InitRowset();
   
   // Call base class to get COLUMINFO array   if (S_OK == hr)
      hr = CRowset::GetColumns(m_pColumns, m_nColumns);

   return hr;
}

CRowset::GetColumns allocates the COLUMNINFO array on behalf of the caller using the new operator. Callers must explicitly free this array. COLEDBListControl::FreeColumns performs this task:

HRESULT COLEDBListControl::FreeColumns(void)
{
   if (m_pColumns)
   {
      delete [] m_pColumns;
      m_pColumns = NULL;
      m_nColumns = 0;
   }
   return S_OK;
}

Browsing the Column Names

COLEDBListControl implements the ListColumn property, which allows users to specify in which column in the data source the list will display. ListColumn can be set in code if the user knows beforehand what columns are available in the data source. At design time, the control can give the container a list of available values to assign to the ListColumn property by implementing the COM interface IPerPropertyBrowsing.

Adding the ListColumn Property

As with any other property, adding the ListColumn property requires modifying some files. In brief, you need to make the following modifications to the corresponding files.

Add the following define statement to Dispid.h:

#define DISPID_LISTCOLUMN   102

Add the following properties to the interface definition of IOLEDBList in Oledblst.odl:

// ListColumn property
[id(DISPID_LISTCOLUMN), propget] 
HRESULT ListColumn([out, retval] BSTR* pbstrColumn);
[id(DISPID_LISTCOLUMN), propput] 
HRESULT ListColumn([in] BSTR bstrColumn);

Add the following member to the definition of OLEDBLISTCTLSTATE structure in Oledblistctl.h:

BSTR      bstrListColumn;

Add the following put and get methods to the class definition of COLEDBListControl in Oledblistctl.h:

STDMETHOD(get_ListColumn)(BSTR *pbstrColumn);
STDMETHOD(put_ListColumn)(BSTR bstrColumn);

You can find the implementation for the put and get methods for the ListColumn property in Oledblistctl.cpp.

Implementing IPerPropertyBrowsing

During the control-creation process, the Implements IPerPropertyBrowsing check box was selected. This resulted in the code generated to implement IPerPropertyBrowsing. Stub implementations of IPerPropertyBrowsing::GetPredefinedStrings and IPerPropertyBrowsing::GetPredefinedValue were added to Oledblistctl.cpp.

IPerPropertyBrowsing::GetPredefinedStrings provides the list of strings, whereas the IPerPropertyBrowsing::GetPredefinedValue function returns the string associated with the key or cookie value that was assigned to it by IPerPropertyBrowsing::GetPredefinedStrings.

Implementing IPerPropertyBrowsing::GetPredefinedStrings

This method is called when the container needs the list of strings describing possible values for a property. Containers may also call this method with NULL pointers. If so, then E_POINTER should be returned if the property does not support browsing, and S_OK if it does. Following is the implementation of the method with code additions highlighted in bold.

STDMETHODIMP COLEDBListControl::GetPredefinedStrings(DISPID dispid, CALPOLESTR *prgOleStr, CADWORD *prgCookie)
{
   // GUID of enum type for the property
   //
  const GUID *puuid = NULL;

   // Properties for which we support browsing   
   //
   switch (dispid) 
  {
   // TODO: Add cases for properties here.

   case DISPID_LISTCOLUMN:
      return GetColumnDisplayStrings(prgOleStr, prgCookie);

   case DISPID_UNKNOWN:   // Handles default w/no case warning.
   default:
      // Containers will call with NULL arguments
      // to test whether we support browsing for
      // a property. When this happens, E_POINTER
      // should be returned for properties that
      // we do not support browsing for; and
      // S_OK for those that we do support.      
      //
      if (!prgOleStr || !prgCookie)
         return E_POINTER;

      // We don't support browsing for 
      // the requested property.
      //
      return S_FALSE;
  }
   // GetStrings will search the TypeLib for information
   // about the type identified by *puuid.
   //
  return GetStrings(prgOleStr, prgCookie, OBJECT_TYPE_CTLOLEDBLIST, *puuid);
}

COLEDBListControl::GetColumnDisplayStrings fills in the CALPOLESTR and CADWORD structures using the columns information. See the implementation of COLEDBListControl::GetColumnDisplayStrings in Oledblistctl.cpp for more details.

Implementing IPerPropertyBrowsing::GetPredefinedValue

This method is called when the container needs the value of a property associated with the cookie. The cookie value is the same cookie matched with the property value in IPerPropertyBrowsing::GetPredefinedStrings. Here is the implementation of the method with code additions highlighted in bold.

STDMETHODIMP COLEDBListControl::GetPredefinedValue(DISPID dispid, DWORD dwCookie, VARIANT *pVarValue)
{
   switch (dispid) 
   {
   // TODO: Add cases for properties here

   case DISPID_LISTCOLUMN:
      if ((ULONG)dwCookie < m_cColumns)
      {
         // Return column name at index formatted as
         // a Variant BSTR since this is the property
         // of for ListColumn
         //
         V_VT(pVarValue) = VT_BSTR;
         V_BSTR(pVarValue) = bstralloc(m_pColumns[dwCookie].pwszName);
         return V_BSTR(pVarValue) == NULL ? E_OUTOFMEMORY : S_OK;
      }
      return E_UNEXPECTED;

   case DISPID_UNKNOWN:   // Handles default w/no case warning
   default:
      // Don't recognize the property
      //
      return E_FAIL;
   }
   // Lookup the property value based on the cookie
   return GetValue(pVarValue, dwCookie);
}
 

The cookie is interpreted as the zero base index of the corresponding column in the COLUMNINFO array. The inserted code checks to ensure that the cookie does not over-index the array and simply looks up the column name.

Getting Row Data

True data-bound controls, such as those supplied by Visual Basic, employ some implementation of a row cache, which greatly reduces the number of rows that the provider has to bring into memory. To simplify the process, at run time OledbList fills its content with the text from the column designated by the ListColumn property for all the rows in the data source. This is not the optimal behavior, because it would hurt performance and would be a huge drain on system resources for any consumer to attempt to access all the rows at once in a large data source.

To get row data, a consumer usually does the following:

  1. Decides which column to get data for using the rowset's column information.

  2. Defines how the column data should be formatted.

  3. Sets up column bindings and creates an accessor for the bindings.

  4. Fetches rows.

  5. Gets data for each row using the row handle, accessor handle, and a data buffer.

Following this sequence, OledbList performs the following steps:

  1. Determines whether the rowset has a column with the same name as the value of the ListColumn property.

  2. Requests the column data to be formatted as a BSTR.

  3. Sets up a column binding and creates an accessor.

  4. Fetches all the rows sequentially until the end of the rowset is reached.

  5. Gets data for each row fetched and adds it to the list content.

In this procedure, steps 1, 2, and 3 are handled in COLEDBListControl::InitRowAccessor, and steps 4 and 5 are handled in COLEDBListControl::FillListContents.

Creating an Accessor

To display row data at run time, OledbList must first bind to the list column. It does this by first establishing whether the list column is a member of the columns of the attached rowset. Once membership is established, a DBBINDING structure is set up to associate the list column with a data buffer large enough to hold a BSTR. An accessor is then created with the binding information. All this is handled in COLEDBListControl::InitRowAccessor, which is called on demand to ensure that the class member variable m_hRowAccessor is initialized.

COLEDBListControl::InitRowAccessor

Following is the implementation of COLEDBListControl::InitRowAccessor, which can be found in Oledblistctl.cpp:

HRESULT COLEDBListControl::InitRowAccessor(void)
{
   // Return success if already initialized.
   //
   if (m_hRowAccessor)
      return S_OK;

   // ListColumn property must be set; if not,
   // then return S_FALSE for no data.
   //
   if (!m_state.bstrListColumn || '\0' == m_state.bstrListColumn[0])
      return S_FALSE;

   // Ensure that m_pColumns is initialized.
   //
   HRESULT hr = InitColumns();
   if (S_OK != hr)
      return hr;

   // Search for the ListColumn in the Columns array.
   //
   LONG nIndex = m_cColumns;
   while (nIndex--)
   {
      // wstrcmp is in strutils.h
      if (!wstrcmpi((WCHAR*)m_state.bstrListColumn, m_pColumns[nIndex].pwszName))
         break;
   }
   // If ListColumn is not one of the columns,
   // then return S_FALSE for no data.
   //
   if (nIndex < 0)
      return S_FALSE;

   // Zero out the binding structure.
   //
   DBBINDING   dbBinding;
   ZeroMemory((void*)&dbBinding, sizeof dbBinding);
   
   // Set the column ordinal.
   //
   dbBinding.iOrdinal   = m_pColumns[nIndex].iOrdinal;

   // Save the ordinal for the list column.
   //
   m_listColumnOrdinal = m_pColumns[nIndex].iOrdinal;

   // Set the offset of the start of data in the buffer.
   //
   dbBinding.obValue   = 0;

   // Request data to be returned as a BSTR.
   //
   dbBinding.wType      = DBTYPE_BSTR;

   // Maximum length of the buffer is sizeof(BSTR).
   //
   dbBinding.cbMaxLen   = sizeof(BSTR);

   // We want only the value part of the column data.
   //
   dbBinding.dwPart   = DBPART_VALUE;

   // Create an accessor with this column binding.
   // DBSTATUS is an optional argument. If this
   // fails, you can check the status in the 
   // debugger.
   //
   DBSTATUS dbStatus;
   hr = CRowset::CreateAccessor(&dbBinding, 1, m_hRowAccessor, &dbStatus);

   return hr;
}

Freeing the row accessor is implemented as follows:

HRESULT COLEDBListControl::FreeRowAccessor(void)
{
   HRESULT hr = S_OK;

   if (m_hRowAccessor)
   {
      hr = CRowset::ReleaseAccessor(m_hRowAccessor);
      m_hRowAccessor = NULL;
      m_listColumnOrdinal = -1;
   }
   return hr;
}

Getting Data

Once an accessor is created and rows are fetched, the accessor can be used in conjunction with any row handle to get data for that row. Getting data requires a buffer allocated by the consumer that is large enough to hold row data as defined by the binding information used to create the accessor. It is also the responsibility of the consumer to deallocate memory that was allocated by the provider on the consumer's behalf. These operations are illustrated by COLEDBListControl::FillListContent.

COLEDBListControl::FillListContent

This method sequentially fetches all the rows in the attached rowset and fills the list content with text data from the list column. It is implemented as follows:

void COLEDBListControl::FillListContent(void)
{
   // Don't do anything if this is called at design time
   // or if an attempt to fill the list has 
   // been done since the last call to ClearContent.
   //
   if (DesignMode() || m_fFilledListContent)
      return;

   // Ensure that the row accessor is initialized.
   //
   HRESULT hr = InitRowAccessor();
   if (S_OK != hr)
      return;

   // Turn off redraw while the list is
   // being populated.
   //
   SendMessage(m_hwnd, WM_SETREDRAW, 0, 0);

   BSTR bstrData;   // Buffer to hold row data
   HROW *phRows;   // Array of row handles returned by callee
   ULONG cRows;   // Number of rows actually fetched

   // Fetch until the end of the rowset.
   //
   do
   {
      // Fetch at offset zero from the current fetch
      // position. ROWS_REQUEST is an arbitrary 
      // request If there is discontinuity in the
      // rowset due to deletions; or the number of rows
      // requested is greater then the number of active
      // rows allowed, then the number of rows fetched
      // may be less than the number of rows requested.
      //
      phRows = NULL;   // Must be NULL for callee to allocate

      hr = CRowset::GetNextRows(0, ROWS_REQUEST, phRows, cRows);

      // If no rows were returned then try another fetch. 
      // 
      if (!cRows)
         continue;

      for (ULONG n = 0; n < cRows; n++)
      {
         // Get data for the row.
         //
         bstrData = NULL;
         CRowset::GetData(phRows[n], m_hRowAccessor, (void*)&bstrData);

         // bstrData may be NULL.
         //
         if (bstrData)
         {
            // Convert to ANSI text.
            MAKE_ANSIPTR_FROMWIDE(szText, bstrData);

            // Free the BSTR.
            SysFreeString(bstrData);
            if (szText)
               SendMessage(m_hwnd, LB_ADDSTRING, 0, (LPARAM)szText);
            else
            {
               hr = E_OUTOFMEMORY;
               break;
            }
         }
         else
            // Add a blank line if data is NULL.
            SendMessage(m_hwnd, LB_ADDSTRING, 0, (LPARAM)(LPSTR)"");
      }
      // Release row handles and free the 
      // callee-allocated array.
      //
      CRowset::FreeRows(phRows, cRows);
   }
   // Continue until an operation fails
   // or until the end of the rowset.
   //

while (!(FAILED(hr) || DB_S_ENDOFROWSET == hr));

   // Turn redraw back on and invalidate
   // the control.
   //
   SendMessage(m_hwnd, WM_SETREDRAW, (WPARAM)TRUE, 0);
   m_fFilledListContent = TRUE;

   // Update the Text property.
   //
   UpdateText(DB_NULL_HROW);
}

Adding Notifications

OledbList needs to receive notifications to detect the following conditions:

Respectively, each of the items in the list is handled by implementing IDataSourceListener, IRowPositionChange, and IRowsetNotify.

Implementing the IDataSourceListener Interface

Implementing IDataSourceListener is as simple as declaring a class that inherits from the interface, then providing implementations for all of its methods. IDataSource calls IDataSourceListener methods after the operation that caused the notification has finished. Consumers can use these notifications for informational purposes only.

Implementing the IRowPositionChange Interface

IRowPositionChange is a COM interface; therefore, its implementation must also include the implementation of the IUnknown interface. To implement IRowPositionChange, declare a class that inherits from the interface, then implement all IRowPositionChange and IUnknown methods.

Implementing the IRowsetNotify Interface

IRowsetNotify is also a COM interface. To implement IRowsetNotify, declare a class that inherits from the interface, then implement all IRowsetNotify and IUnknown methods.

COLEDBListControl::CNotifications

This class illustrates how to implement the notification interfaces using the ActiveX Control Framework. CUnknownObject provides COM capabilities to classes derived from it, and greatly reduces the complexity of implementing COM interfaces and aggregation.

COLEDBListControl defines CNotifications in line for its m_notifications member variable. The definition of COLEDBListControl::CNotifications is in Oledblistctl.cpp. It is defined as follows:

   class CNotifications :    public CUnknownObject,
                           public IRowPositionChange,
                    public IRowsetNotify,
                    public IDataSourceListener
   {
   public:
      CNotifications();
      ~CNotifications();

   public:
      // IUnknown
      //
      DECLARE_STANDARD_UNKNOWN();

      // IRowPositionChange methods
      //
      STDMETHOD(OnRowPositionChange)(DBREASON eReason, DBEVENTPHASE ePhase, BOOL fCantDeny);

      // IRowsetNotify methods
      //
      STDMETHOD(OnFieldChange)(IRowset *prowset, HROW hRow, ULONG cColumns, ULONG rgColumns[], DBREASON eReason, DBEVENTPHASE ePhase, BOOL fCantDeny);
      STDMETHOD(OnRowChange)(IRowset *prowset, ULONG cRows, const HROW rghRows[], DBREASON eReason, DBEVENTPHASE ePhase, BOOL fCantDeny);
      STDMETHOD(OnRowsetChange)(IRowset *prowset, DBREASON eReason, DBEVENTPHASE ePhase, BOOL fCantDeny);

      // IDataSourceListener methods
      //
      STDMETHOD(dataMemberChanged)(THIS_ DataMember bstrDM);
      STDMETHOD(dataMemberAdded)(THIS_ DataMember bstrDM);
      STDMETHOD(dataMemberRemoved)(THIS_ DataMember bstrDM);

   // Overrideables
   protected:
      // Aggregation query interface support
      //
      virtual HRESULT InternalQueryInterface(REFIID riid, void **ppvObjOut);

   // Implementation
   protected:
      CConnectionsArray   m_connections;   // Manages notifcation connections

      COLEDBListControl* OLEDBList(void);

   // Operations
   public:
      HRESULT Connect(void);
      HRESULT Disconnect(void);
 
   // Operators
   public:
      operator IUnknown *() {return PrivateUnknown();}
   }
   m_notifications;   // Notification object
   friend class CNotifications;
};

Making the Connection

COLEDBListControl::AttachRowset appropriately calls m_notifications.Connect and m_notifications.Disconnect to engage and disengage notifications. The implementation of CNotifications::Connect and CNotifications::Disconnect uses CConnectionsArray (see Advcon.h and Advcon.cpp) to manage connection links for IRowPositionChange and IRowsetNotify. They call IDataSource::addDataSourceListener and IDataSource::removeDataSourceListener for IDataSourceListener. See the implementation of these methods in Oledblistctl.cpp for more details.

Handling Notifications

It is not necessary to respond to all notifications, even though all methods for a particular notification interface must be implemented. When a notification is not of interest, its implementation should return S_OK. For IRowsetNotify notifications, DB_S_UNWANTEDREASON or DB_S_UNWANTEDPHASE may also be returned.

The following section describes how CNotifications implements its notification handlers.

CNotifications::dataSourceChanged (IDataSourceListener)

The data member argument is compared, without case sensitivity, to the data member that OledbList is using. If they match, the COLEDBListControl::AttachRowset is called with a Null argument to reset the list.

STDMETHODIMP COLEDBListControl::CNotifications::dataMemberChanged(DataMember bstrDM)
{
   // Compare DataMembers.
   //
   if ((NULL == OLEDBList()->m_state.dmDataMember && (NULL == bstrDM || '\0' == bstrDM[0]))
    || !bstrcmpi(bstrDM, (BSTR)OLEDBList()->m_state.dmDataMember))
   {
      // Reset because the attached rowset may
      // no longer be valid or may have changed.
      //
      OLEDBList()->AttachRowset(NULL);
   }
   return S_OK;
}

CNotifications::OnRowPositionChange (IRowPositionChange)

This notification is used to detect when the current row position has changed. Two things must happen in response to this notification:

STDMETHODIMP COLEDBListControl::CNotifications::OnRowPositionChange(DBREASON eReason, DBEVENTPHASE ePhase, BOOL fCantDeny)
{
   // Get the handle of the current row.
   //
   DBPOSITIONFLAGS   posFlags    = NULL;
   HROW         hrow       = NULL;
   HRESULT         hr         = OLEDBList()->GetCurrentRow(hrow, &posFlags);

   if (SUCCEEDED(hr))
   {
      // Prior to row position change
      //
      if (DBEVENTPHASE_OKTODO == ePhase)
         OLEDBList()->UpdateCurrentRow(hrow);

      // After row position change
      //
      else if (DBEVENTPHASE_DIDEVENT == ePhase)
         OLEDBList()->UpdateText(hrow);

      // Must release the row
      //
      OLEDBList()->ReleaseRows(&hrow, 1);
   }
   return S_OK;
}

CNotifications::OnFieldChange (IRowsetNotify)

This notification is sent when columns in a row are being changed. OledbList responds only after the list column has changed:

STDMETHODIMP COLEDBListControl::CNotifications::OnFieldChange(IRowset *prowset, HROW hRow, ULONG cColumns, ULONG rgColumns[], DBREASON eReason, DBEVENTPHASE ePhase, BOOL fCantDeny)
{
   if (DBEVENTPHASE_DIDEVENT == ePhase)
   {
      // Proceed only if the ListColumn is one
      // of the columns that has changed.
      //
      while (cColumns--)
      {
         if (rgColumns[cColumns] == OLEDBList()->m_listColumnOrdinal)
            break;
      }
      if (cColumns < 0)
         return S_OK;

      // Update the Text property only if it has
      // not been changed.
      // 
      if (!OLEDBList()->m_fTextDirty)
      {
         DBPOSITIONFLAGS   posFlags    = NULL;
         HROW         hrowCurrent   = NULL;
         HRESULT         hr         = OLEDBList()->GetCurrentRow(hrowCurrent, &posFlags);

         // Update Text property if this is the
         // current row.
         //
         if (SUCCEEDED(hr) && hrowCurrent == hRow)
            OLEDBList()->UpdateText(hRow);

         // Must release the row
         //
         OLEDBList()->ReleaseRows(&hrowCurrent, 1);
      }
      // Update the corresponding row in the list.
      //
      OLEDBList()->UpdateListRow(hRow);
   }
   return DB_S_UNWANTEDPHASE;
}

CNotifications IRowsetNotify::OnRowChange (IRowsetNotify)

This notification is sent when rows are changing. Rows may change for a number of reasons. OledbList responds only after rows are inserted or deleted.

STDMETHODIMP COLEDBListControl::CNotifications::OnRowChange(IRowset *prowset, ULONG cRows, const HROW rghRows[], DBREASON eReason, DBEVENTPHASE ePhase, BOOL fCantDeny)
{
   // Respond only after the row change.
   //
   if (DBEVENTPHASE_DIDEVENT == ePhase)
   {
      switch(eReason)
      {
      case DBREASON_ROW_UNDODELETE:
      case DBREASON_ROW_UNDOINSERT:
      case DBREASON_ROW_DELETE:
      case DBREASON_ROW_INSERT:
         // Clear the list content so 
         // it will be refilled when the
         // control repaints.
         //
         // This is not the optimal response
         // but it gets the job done.
         //
         OLEDBList()->ClearContent();
         break;

      default:
         return DB_S_UNWANTEDREASON;
      }   
      return S_OK;
   }
   return DB_S_UNWANTEDPHASE;
}

CNotifications IRowsetNotify::OnRowsetChange (IRowsetNotify)

This notification occurs after the rowset is released by the provider or has changed. The response is to detach the rowset.

STDMETHODIMP COLEDBListControl::CNotifications::OnRowsetChange(IRowset *prowset, DBREASON eReason, DBEVENTPHASE ePhase, BOOL fCantDeny)
{
   // Assume the rowset is no longer valid.
   //
   OLEDBList()->AttachRowset(NULL);
   return S_OK;
}

Building the Control

To build and register the control