Search code examples
c++c++buildervcltcombobox

C++ Builder derived TComboBox to have Items by default


A VCL component newbie here, so pardon me if this is a stupid question...

I'm trying to make a TComboBox component with default items in it upon dropped onto the form, i.e. a TMonthComboBox that will have the list of months in its Items list, when dropped on the form.

I've found that trying to access the Items property during Construction will result in a "Control '' has no parent window" error if I try to drop such combobox on the form.

here is (part of) the constructor:

__fastcall TMonthCombo::TMonthCombo(TComponent *Owner):TComboBox(Owner)
{
    this->Style = csDropDownList; // this is okay
    this->Items->Add("January"); // This is causing problem
}

I figured that the problem is stemming from the fact that the Items property is not available yet at this point of the construction.

Is there anyway to make sure the component is ready to accept values into its Items property, within the component source code itself (i.e. not adding the list items in the Properties Editor at design time)?

Before anyone tells me to "Just add the items in your Application code at run time", I have to explain that this ComboBox will be used quite frequently in many places, and Month selection is just a simple example I used to explain the problem, the actual values I want to put in the ComboBox is much more varied and most of the time, dynamic in nature. It also have to response to the user selection in varied ways.

I have tried the run-time way, but it's getting very tedious. That's why I'm making it into a component, so that it will handle itself without me having to repeatedly input multiple versions of codes just to populate the ComboBoxes.

Thanks for any help.

Edit: After tried manlio's solution, the ComboBox has an odd look in run-time:enter image description here

The ComboBox has double image at run time. What have I done wrong?

__fastcall TYearCombo::TYearCombo(TComponent* Owner) : TComboBox(Owner), init_items(true)
{

}
//---------------------------------------------------------------------------
void __fastcall TYearCombo::CreateWnd()
{
    unsigned short yr, mn, dy;

    this->Width = 90;
    this->Style = csDropDownList;
    this->DropDownCount = 11;
    TDate d = Today();
    d.DecodeDate(&yr, &mn, &dy);
    year = yr;

    if (init_items)
    {
        init_items = false;
        TComboBox::CreateWnd();
        Items->BeginUpdate();
        for(int i=year-5; i<=year+5; i++)
        {
            Items->Add(IntToStr(i));
        }
        this->ItemIndex = 5;
        Items->EndUpdate();
    }
}
//---------------------------------------------------------------------------

Solution

  • You can try this:

    1. Override the CreateWnd virtual method and add a init_items private data member:

      class TMonthCombo : public TComboBox
      {
      // ...
      
      protected:
        virtual void __fastcall CreateWnd();
      
      private:
        bool init_items;
      
      // ...
      };
      
    2. Set the init_items flag:

      TMonthCombo::TMonthCombo(TComponent *Owner) : TComboBox(Owner),
                                                    init_items(true)
      {
        // ...
      }
      
    3. Inside CreateWnd you can add new items:

      void __fastcall TMonthCombo::CreateWnd()
      {
        TComboBox::CreateWnd();
      
        if (init_items)
        {
          init_items = false;
          Items->BeginUpdate();
          Items->Add("January");
          // ...
          Items->EndUpdate();
        }
      }
      

    Further notes:

    • "Control has no parent" in Create ComboBox (TComboBox requires an allocated HWND in order to store strings in its Items property).
    • simply cast the constructor's Owner parameter to TWinControl and assign the result to the component's Parent property isn't a solution:

      TMonthCombo::TMonthCombo(TComponent *Owner) : TComBoBox(Owner)
      {
        Parent = static_cast<TWinControl *>(Owner);
        // ...
      }
      

      the assignment solves the "Control has no parent window error" but creates another problem: the form is always the component's parent (you cannot add the form to a different container).