Volume 8, Number 6 / June 2004

Swart, A TDataSouce Component Editor


A TDataSource

Component Editor
by Bob Swart

 

 

I

n the January 2004 issue, I wrote about C++Builder property editors, and demonstrated how we can write our own property editor in C++ (or Delphi) and then add them to the IDE. This time, I'll show how we can enhance the design-time power of C++Builder even further  by creating component editors.

 

Component Editors

Figure A

 

TDataSource component editor.

Component editors are like property editors; they are used to enhance the integrated development environment of Delphi and C++Builder. And, as with property editors, they are basically derived from a single base class where some abstract methods need to be overridden and redefined in order to give the component editor the desired behaviour. In contrast to property editors however, component editors are component-specific, not property-specific, entities. They are bound to a particular component type, and they are generally executed by a click of the right mouse button on the component (when dropped on a form). This way of activation is a bit different than property editors, but other than that, the process of writing your own component editor is essentially the same.

A component editor is created for each component that is selected in the form designer based on the component's type (see also GetComponentEditor() and RegisterComponentEditor() in DesignIntf.pas). When the component is double-clicked, the Edit() method is called. When the context menu for the component is invoked, the GetVerbCount() and GetVerb() methods are called to build the menu. If one of the verbs is selected, ExecuteVerb() is called. Copy() is called whenever the component is pasted to the clipboard. You only need to create a component editor if you wish to add verbs to the context menu, change the default double-click behaviour, or paste an additional clipboard format. The interface definition for the IComponentEditor interface contains the six virtual

methods (Edit, ExecuteVerb(), GetVerb(), GetVerbCount(), PrepareItem(), and Copy()), and as with a property editor, we can override any of these six virtual methods to build the special behavior inside our component editor.

 

Default Component Editor

Apart from the general TComponentEditor type, there is a default component editor—called TDefaultEditor—that is used by most components (unless another component editor is installed to override the default one). The TDefaultEditor class implements Edit() to search the properties of the component and to generate the (or navigate to an already existing) OnCreate, OnChange or OnClick event (whichever it finds first), or the first alphabetic event handler that's available.

Whenever the component editor modifies the component it must call the Modified() method of the IDE's designer to inform the designer that (the component on) the form has been modified. If we only use the component editor to display some information (like a general about box, for example) there is no need to inform the designer.

 

Example: TDataSourceComponentEditor

With this information at hand, we can build a useful component editor. Consider the TDataSource component, that lives for one main purpose: to be connected to a TDataSet (or TDataSet-derived) component and act as connector between this TDataSet and other data-aware components (including other TDataSets).

Although we can use the Object Inspector to assign a value to the DataSet property, sometimes it's quicker if you do not have to switch from your form or data module to the Object Inspector, but if you can simply assign a value

Listing A: DSCompEdit.h

#ifndef DSCompEditH

#define DSCompEditH

 

#include "DesignIntf.hpp"

#include "VCLEditors.hpp"

 

class TDataSourceComponentEditor:

  public TDefaultEditor

{

  typedef TDefaultEditor inherited;

 

public:

  virtual void __fastcall Edit(void);

  virtual int __fastcall GetVerbCount(void);

  virtual AnsiString __fastcall GetVerb(int index);

  virtual void __fastcall ExecuteVerb(int index);

 

public:

  #pragma option push -w-inl

  inline __fastcall virtual

    TDataSourceComponentEditor(

      Classes::TComponent* AComponent,

      _di_IDesigner ADesigner):

    TDefaultEditor(AComponent, ADesigner) { }

  #pragma option pop

 

public:

  #pragma option push -w-inl

  inline __fastcall virtual

    ~TDataSourceComponentEditor(void) { }

  #pragma option pop

};

 

#endif  // DSCompEditH

to the DataSet property by connecting it to one of the available TDataSets on the current form or data module. This is the behaviour that I want to implement in my TDataSource Component Editor (called TDataSourceComponentEditor).

First, we need to decide which base class to use. For the TDataSource component, it appears that some (default) behavior is already implemented if you double-click on the component, so the TDefaultEditor is the best choice as parent class.

We then need to override and implement a number of methods. Let's start with GetVerbCount(), which returns the number of menu entries that appear when we right-click on the TDataSource component. I want at to include one menu entry for an About box. And for every TDataSet (or derived) component I would like to have a dynamic menu entry that says "Connect to ..." followed by the name of this TDataSet component. This means that we can simply right-click on a TDataSource component and it will show a "Connect to ..." option for all available datasets (which makes it quick and easy to connect them to the TDataSource). In order to find out how many datasets are available, we need to look at the current form or data module. The component itself is owned by this form or data module, so we only have to look at the owner of the current component, and then walk through all components owned by the owner. This is defined by the owner's Components array, which holds ComponentCount components (counting from 0 to ComponentCount minus 1).

Once we've defined how many menu entries to show (returned by the GetVerbCount()), it's not difficult to return the individual menu entries (with GetVerb()). Even the actual ExecuteVerb() is straightforward: just walk through the components of the owner, find the right TDataSet component (specified by the index number) and assign it to the DataSet property of the current TDataSource component.

As final touch, I've also overridden the Edit() method in order to show an About box if you double-click on the TDataSource component (note that this would hide the default behaviour, so I've also called the TDefaultEditor::Edit() method to compensate for that). The definition of the TDataSourceComponentEditor, with the four virtual methods that I've implemented, can be seen in Listing A.

The actual implementation of the TDataSourceComponentEditor can be seen in Listing B. Note that in order to determine whether or not a component is a TDataSet (or derived) component, I use dynamic_cast, which returns a valid pointer if the component is a TDataSet (or a derived type) but a NULL pointer otherwise. This is an effective test that proves very powerful, since it will, in one step, identify TDataSet or derived classes (which are the only classes that can be assigned to the DataSet property of the TDataSource component).

Listing B: DSCompEdit.cpp

#include "DSCompEdit.h"

#include "DB.hpp"

#pragma package(smart_init)

 

void __fastcall

  TDataSourceComponentEditor::Edit(void)

{

  MessageDlg("TDataSourceComponentEditor",

    mtInformation, TMsgDlgButtons() << mbOK, 0);

  TDefaultEditor::Edit(); // inherited

}

 

int __fastcall

  TDataSourceComponentEditor::GetVerbCount(void)

{

  TComponent* ComponentOwner = Component->Owner;

  int DataSets = 1; // first one for About...

  if (ComponentOwner)

  {

    for (int i=0;

      i < ComponentOwner->ComponentCount; i++)

      if (dynamic_cast<TDataSet*>

        (ComponentOwner->Components[i]))

        DataSets++;

  }

  return DataSets;

}

 

AnsiString __fastcall

  TDataSourceComponentEditor::GetVerb(int index)

{

  if (index == 0) return

    "&About TDataSourceComponentEditor...";

  else

  {

    TComponent* ComponentOwner = Component->Owner;

    int DataSets = 0;

    if (ComponentOwner)

    {

      for (int i=0;

        i < ComponentOwner->ComponentCount; i++)

      if (dynamic_cast<TDataSet*>

        (ComponentOwner->Components[i]))

      {

        DataSets++;

        if (DataSets == index)

          return "&Connect to " +

            (ComponentOwner->Components[i])->Name;

      }

    }

  }

}

void __fastcall TDataSourceComponentEditor::

  ExecuteVerb(int index)

{

  if (index == 0) Edit(); // about box

  else

  {

    TComponent* ComponentOwner =

      Component->Owner;

    int DataSets = 0;

    if (ComponentOwner)

    {

      for (int i=0; i < ComponentOwner->

        ComponentCount; i++)

      if (dynamic_cast<TDataSet*>

        (ComponentOwner->Components[i]))

      {

        DataSets++;

        if (DataSets == index)

        {

          (dynamic_cast<TDataSource*>(Component))

           ->DataSet = dynamic_cast<TDataSet*>

           (ComponentOwner->Components[i]);

          Designer->Modified();

        }

      }

    }

  }

}

 

namespace Dscompedit

{

   void __fastcall PACKAGE Register()

   {

      RegisterComponentEditor(

        __classid(TDataSource),

        __classid(TDataSourceComponentEditor)

        );

   }

}

Finally, notice the Register() method (which must be placed in the namespace that corresponds to the file-name), where we call the RegisterComponentEditor() method to register the TDataSourceComponentEditor for the TDataSource component.

After you've installed this component editor, for example in the dclusr.dpk package (see the article of January 2004 for more installation details), you can see the component editor in action by right-clicking on the TDataSource component. See, for example, Figure A where I've used four different dbExpress datasets to illustrate the behavior.

Although it doesn't save a whole lot of time compared to moving to the Object Inspector, this component editor enables you to extend the current functionality. Possible ideas that I've considered with but never implemented are to append the name of the dataset with additional information, like the number of records, whether the dataset is currently open ("live") or closed, and—in case of the TClientDataSet—if the data are from a local or remote source. This would lead to functionality that the Object Inspector can not easily offer, and is left as an exercise for the reader (perhaps a topic for a follow-up article if you've enjoyed this one—feel free to let me know).

 

Conclusion

 In this article, I've demonstrated how we can build component editors, by building a TDataSource component editor that can connect to any TDataSet component on the same form or data module. I hope to have shown that Property and Component Editors make powerful additions to the C++Builder IDE, and they can increase the RAD experience by automating or supporting tasks at design-time.

The full source for this article’s example program can be downloaded from http://www.bcbjournal.org.

 

 

Refer a friend and receive 3 free months of the Journal!  If your referral results in a new 12-month subscription, we'll extend your subscription by three additional months. 

 

Contact sales@bcbjournal.org for more information.

 



C++Builder Developer's Journal

www.bcbjournal.org

 

Copyright © 2004, EnCoded Communications Group.  All Rights Reserved.

 

C++Builder Developer’s Journal is an independently produced publication of EnCoded Communications Group. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of EnCoded Communications Group is prohibited.