Example: Creating an Edit Control

The following example creates the Simptext.ocx Edit Control using the ActiveX Control Creation Wizard. Some features that you may normally put in an ActiveX control are left out of these steps for the sake of simplicity. Refer to the Simptxt project in the Samples directory, included with this SDK, for the source code to this sample.

This simple bound text box works very much like the TextBox control in Visual Basic.

Creating the Simptext.ocx Edit 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, Simptext.

  3. Type in or browse for the directory where 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, SimpText.

  6. Select the Windows control that you want to subclass—for example, EDIT. Click Next.

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

Once the wizard has successfully completed, you can begin working on the new ActiveX Framework control project.

Modifying the .odl File

The first step to adding a property to a control is to modify its .odl file. See "Adding Atributes to the .odl File" earlier in this chapter for details about modifying the .odl file to add the data-binding attributes.

To add a default bindable property to Simptext, add the following property to the interface definition for the ISimpText declaration. Because the default bindable property should best describe a control's nature, the Text property is appropriate with an Edit control. Open the Simptext.odl file:

[id(DISPID_TEXT), propget, bindable, requestedit, displaybind, defaultbind] 
HRESULT Text([out, retval] BSTR *pbstrText);
[id(DISPID_TEXT), propput, bindable, requestedit, displaybind, defaultbind] 
HRESULT Text([in] BSTR bstrText);

DISPID_TEXT is defined in Olectl.h because it is a stock property (a property that is very common among ActiveX controls).

After adding a property declaration to the .odl file, you must also add the corresponding declaration to the control’s class definition in its C++ header file. Open SimpTextCtl.h and add the following method declaration to the CSimpTextControl class definition:

STDMETHOD(get_Text)(BSTR * pbstrText);
STDMETHOD(put_Text)(BSTR bstrText);

Managing the Property Value

So far, you've created the simple bound property. In the following procedures, you add a member variable to store the actual value of the Text property, as well as initialize and store the property.

To add a member variable to store the Text property, open the Simptextctl.h file and find the structure definition for SIMPTEXTCTLSTATE. Add the following structure member to the structure:

BSTR bstrText;

Note   In the rest of the example, you will be adding code to the Simptextctl.cpp file.

Initializing State Variables

When you first create your control, you want your text property to be the same as the name of your control, just like the Visual Basic TextBox control. The first time you place a new control on a form, the framework's COleControl::InitializeNewState method is called, so this is where you put the code to initialize the m_state.bstrText and other state variables that may apply to this case.

The implementation of CSimpTextControl::InitializeNewState looks like this:

BOOL CSimpTextControl::InitializeNewState(void)
{
   // Make the text property the same as the control name
   // the first time you place the control on the form.
   if (!GetAmbientProperty(DISPID_AMBIENT_DISPLAYNAME, VT_BSTR, (void*)&m_state.bstrText))
      // If this is unsuccessful, then use the base control name.
      m_state.bstrText = SysAllocString(L"SimpText");

   // TRUE to proceed
   // FALSE to fail
   return TRUE;   
}

Initializing the Control Window

After the control object is created by the container and the control state is initialized, the host activates the control in place. The control framework prepares the control for display, including creating the control window. To allow the control to hook into this process, COleControl provides two methods: BeforeCreateWindow and AfterCreateWindow.

Call BeforeCreateWindow to allow controls to set up their window styles and captions. CSimpTextControl::BeforeCreateWindow is implemented as follows:

BOOL CSimpTextControl::BeforeCreateWindow
(
  DWORD *pdwWindowStyle,
  DWORD *pdwExWindowStyle,
  LPSTR  pszWindowTitle
)
{
   // Allow text to scroll past the right border.
   //
   *pdwWindowStyle |= ES_AUTOHSCROLL;

   // Give the border a 3-D effect.
   //
   *pdwExWindowStyle |= WS_EX_CLIENTEDGE;

   // Fill in pszWindowTitle if the Text property is initialized.
   //
   if (m_state.bstrText)
   {
      // It doesn’t matter whether conversion succeeds.
      // If it does not succeed, then the Edit control 
      // is cleared.
      //
      MAKE_ANSIPTR_FROMWIDE(szText, m_state.bstrText);

      if (szText)
      {
         // The framework allocates a buffer of
         // 128 bytes, so be careful not to exceed
         // this limit.
         //
         lstrcpyn(pszWindowTitle, szText, 127);
      }
      // szText will deallocate when it loses scope.
      // MAKE_ANSIPTR_FROMWIDE is in the framework's UTIL.H file.
   }
  return TRUE;
}

Implement AfterCreateWindow to perform initialization after the control window has been created. If necessary, you can initialize your control during its creation process. To do this, add code to handle the WM_NCCREATE or WM_CREATE messages in your control’s implementation of WindowProc. While the control window is being created, the process is running in a critical section of code. Therefore, any initialization done during WM_NCCREATE and or WM_CREATE must take this into account. If possible, do your initialization in AfterCreateWindow after your control window is been created.

Keeping the Member Variable Persistent

Now that you can store your value in the m_state.bstrText member variable, you need to make sure it is saved and restored whenever the control is restarted. There are two types of persistence:

This SDK provides persistence helpers in Persist.h and Persist.cpp to facilitate persisting properties of the most common types. For more information about implementing persistence, see “Using the Framework” in Appendix A.

Managing the Text Property

Managing the Text property requires that the control handle EN_CHANGE notifications from the Edit window itself. These notifications occur when the user edits the control’s contents. The control also needs to implement CSimpTextControl::get_Text so that it can return a copy of m_state.bstrText to the host.

Returning the Text

CSimpTextControl::get_Text is called when the Text property value is needed. The control may call this method internally, but the host calls it in most cases. The idea here is to return a copy of the text stored in m_state.bstrText. The following implementation of CSimpTextControl::get_Text demonstrates this:

STDMETHODIMP CSimpTextControl::get_Text(BSTR *pbstrText)
{
   CHECK_POINTER(pbstrText);

   if (m_state.bstrText)
      // Make a copy.
      //
      *pbstrText = bstralloc(m_state.bstrText);
   else
      // Return an empty string.
      //
      *pbstrText = SysAllocString(L"");
   
   return NULL == *pbstrText ? E_OUTOFMEMORY : S_OK;
}

Handling the EN_CHANGE Text Box Message

Any time the value in the text box changes, you want to notify the container of that change. In this case, add code to respond to the EN_CHANGE text box message to notify the container. The Windows Edit control sends the EN_CHANGE notification code with the WM_COMMAND to its owner. This message is reflected back to the ActiveX control through the OCM_COMMAND message. ActiveX Framework controls can handle reflected messages in their implementation of WindowProc. Here is an example of how CSimpTextControl::WindowProc is implemented to handle the EN_CHANGE notification:

LRESULT CSimpTextControl::WindowProc
(
  UINT msg,
  WPARAM wParam,
  LPARAM lParam
)
{
  // TODO: handle any messages here, like in a normal window
  // proc.  Note that for special keys, you'll want to override and
  // implement OnSpecialKey.
    //

   switch (msg)
   {
   // WM_COMMAND message is sent to the container and is
   // reflected back as OCM_COMMAND.
   //
   case OCM_COMMAND:
      // The Text property needs to be updated when
      // the edit control's contents change. The 
      // control must request permission from the
      // container to update the text here and in
      // put_Text.
      //
      if (HIWORD(wParam) == EN_CHANGE)
      {
         if (RequestPropertyEdit(DISPID_TEXT))
         {
            // You got permission, so get the new text
            // from the edit control, if any.
            //
            int nLen = GetWindowTextLength(m_hwnd);

            if (nLen)
            {
               // Allocate a buffer for text and a
               // NULL terminator.
               //
               char *szText = new char[++nLen];

               if (NULL != szText)
               {
                  GetWindowText(m_hwnd, szText, nLen);
                  BSTR bstrText = bstralloc(szText);
                  delete [] szText;

                  if (bstrText)
                  {
                     if (m_state.bstrText)
                        SysFreeString(m_state.bstrText);

                     // Assign a new Text value and notify the 
                     // container of the change.
                     //
                     m_state.bstrText = bstrText;
                     PropertyChanged(DISPID_TEXT);
                     return 0;
                  }
               }
            }
            else
            {
               // The Edit control is empty, so free any text.
               // Otherwise, nothing needs to be done.
               //
               if (m_state.bstrText)
               {
                  SysFreeString(m_state.bstrText);
                  m_state.bstrText = NULL;
                  PropertyChanged(DISPID_TEXT);
               }
               return 0;
            }
         }
         // NOTE: This code is executed if the container
         // refused the change request, or if the above
         // allocation failed. In either case,
         // you need to call SyncText to keep the edit
         // control content in sync with m_state.Text.
         //
         SyncText();
      }
      // You don't need to pass on to a subclass
      // because this is an OLE-specific message.
      //
      return 0;
   }
   return CallWindowProc((FARPROC)SUBCLASSWNDPROCOFCONTROL(OBJECT_TYPE_CTLSIMPTEXT), m_hwnd, msg, wParam, lParam);
}

Setting the Text

To change the Text property, the control must first notify the host that it’s about to make a change. This is done by calling COleControl::RequestPropertyEdit on the dispatch ID of the property in question. If RequestPropertyEdit returns True, the host has given permission to allow the property to be changed. Then, after a successful change, the control must notify the host that the property has changed by calling COleControl::PropertyChanged on the dispatch ID of the property that was changed.

The implementation of CSimpTextControl::put_Text demonstrates the procedure to notify the host of the change in the Text property:

STDMETHODIMP CSimpTextControl::put_Text(BSTR bstrText)
{
   // Request permission to edit the property
   // and throw an exception if the container
   // does not permit it.
   //
   if (!RequestPropertyEdit(DISPID_TEXT))
      return Exception(CTL_E_SETNOTPERMITTED, 0, 0);

   if (bstrText)
   {
      // Make a copy of the text and return
      // E_OUTOFMEMORY if allocation failed.
      //
      bstrText = bstralloc(bstrText);
      RETURN_ON_NULLALLOC(bstrText);
   }
   // Free the current property. 
   //
   if (m_state.bstrText)
      SysFreeString(m_state.bstrText);

   // Assign a new property value. This can
   // be a NULL BSTR.
   //
   m_state.bstrText = bstrText;

   // Synchronize the Edit control's contents
   // with m_state.bstrText.
   //
   SyncText();

   // Notify the container of changes.
   //
   PropertyChanged(DISPID_TEXT);

   return S_OK;
}

Building the Control

To build and register the control