1

I try to make a ComboBox with full search autocomplete in C++Builder.

I found a solution in Delphi, but when I try to rewrite it in C++ I get some access error:

How to make a combo box with fulltext search autocomplete support?

Here's my code:

My header file:

class PACKAGE TSmartComboBox : public TComboBox
{
private:
    TStringList* FStoredItems;
    bool dofilter;
    int storeditemindex;
    void FilterItems();
    void __fastcall StoredItemsChange(TObject* Sender);
    void CNCommand(TWMCommand AMessage);
protected:
    DYNAMIC void __fastcall KeyPress(System::WideChar &Key);
    DYNAMIC void __fastcall CloseUp(void);
    DYNAMIC void __fastcall Click(void);
public:
    __fastcall TSmartComboBox(TComponent* Owner);
    __fastcall ~TSmartComboBox();
    void InitSmartCombo();

__published:
    void __fastcall SetStoredItems(TStringList* Value);
    __property TStringList* StoredItems =
    {read = FStoredItems, write = SetStoredItems};
};

And cpp file:

static inline void ValidCtrCheck(TSmartComboBox *)
{
    new TSmartComboBox(NULL);
}
//---------------------------------------------------------------------------
__fastcall TSmartComboBox::TSmartComboBox(TComponent* Owner)
    : TComboBox(Owner)
{
    FStoredItems = new TStringList();
    dofilter = false;
}

__fastcall TSmartComboBox::~TSmartComboBox()
{
    delete FStoredItems;
}

void _fastcall TSmartComboBox::SetStoredItems(TStringList* Value)
{
    if(FStoredItems)
    {
        FStoredItems->Assign(Value);
    } else {
        FStoredItems = Value;
    }
}

void __fastcall TSmartComboBox::KeyPress(System::WideChar &Key)
{
    if(dofilter && (Key >= 13 & Key <= 27))
    {
        if(Items->Count != 0 && DroppedDown)
        {
            SendMessage(Handle, CB_SHOWDROPDOWN, 1, 0);
        }
    }
}

void __fastcall TSmartComboBox::CloseUp(void)
{
    AnsiString x;

    if(dofilter)
    {
        if(Items->Count == 1 && ItemIndex == 0)
        {
            Text = Items->operator [](ItemIndex);
        } else if((Text == "" && ItemIndex != -1 && Text != Items->operator [](ItemIndex)) || (Text == "" && ItemIndex == 0))
        {
            storeditemindex = ItemIndex;
            x = Text;
            ItemIndex = Items->IndexOf(Text);
            if(ItemIndex == -1)
            {
                Text = x;
            }
        } else
        {
            storeditemindex = -1;
        }
    }
}

void __fastcall TSmartComboBox::Click(void)
{
    if(dofilter)
    {
        if(storeditemindex != -1)
        {
            ItemIndex = storeditemindex;
        }
        storeditemindex = -1;
    }
}

void TSmartComboBox::InitSmartCombo()
{
    FStoredItems->OnChange = NULL;
    StoredItems->Assign(Items);
    AutoComplete = false;
    FStoredItems->OnChange = StoredItemsChange;
    dofilter = true;
    storeditemindex = -1;
}

void __fastcall TSmartComboBox::StoredItemsChange(TObject* Sender)
{
    if(FStoredItems)
    {
        FilterItems();
    }
}

void TSmartComboBox::FilterItems()
{
    int i;
    TSelection Selection;

    SendMessage(Handle, CB_GETEDITSEL, WPARAM(Selection.StartPos), LPARAM(Selection.EndPos));

    Items->BeginUpdate();

    if (Text != "")
    {
        Items->Clear();

        for (i = 0; i < FStoredItems->Count - 1; i++)
        {
            if(Pos(UpperCase(Text), UpperCase(FStoredItems->Strings[i])) > 0)
            {
                Items->Add(FStoredItems->Strings[i]);
            }
        }
    } else
    {
        Items->Assign(FStoredItems);
    }

    Items->EndUpdate();

    //SendMessage(Handle, CB_SETEDITSEL, 0, MakeLParam(Selection.StartPos, Selection.EndPos));
}

void TSmartComboBox::CNCommand(TWMCommand AMessage)
{
    if(AMessage.NotifyCode == CBN_EDITUPDATE && dofilter)
    {
        FilterItems();
    }
}

//---------------------------------------------------------------------------
namespace Comboboxsmart
{
    void __fastcall PACKAGE Register()
    {
         TComponentClass classes[1] = {__classid(TSmartComboBox)};
         RegisterComponents(L"polpress", classes, 0);
    }
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
ppitu
  • 41
  • 3
  • "*I get some access error*" - can you be more specific? What does the error actually say? Which line does it happen on exactly? – Remy Lebeau Sep 30 '21 at 16:39

1 Answers1

1

I see a number of issues with your translation:

  • CNCommand() is not calling the inherited handler. And it is declared wrong anyway, as it needs to take the TWMCommand by reference instead of by value. Also, you are missing a MESSAGE_MAP to even invoke CNCommand() in the first place.

  • SetStoredItems() should not be trying to take ownership of the passed TStringList* at all. Not that it matters, since FStoredItems should never be NULL to begin with.

  • In KeyPress(), you are not calling the inherited handler. Also, you need to use && instead of & when testing 2 separate conditions. In C++, && is LOGICAL AND, whereas & is BITWISE AND. However, you are not actually checking the Key parameter in the same manner that the original Delphi code is.

  • in fact, a lot of your comparisons are doing the opposite of what the original Delphi code does.

  • In CloseUp(), you are not calling the inherited handler. Also, you should use (Unicode)String instead of AnsiString. Also, use (*Items)[...] or Items->Strings[...] instead of using Items->operator[](...) explicitly.

  • In Click(), you are not calling the inherited handler.

  • In FilterItems(), your parameter values for CB_GETEDITSEL are wrong. They need to be passed by pointer, not by value. Also, your for loop is skipping the last string in FStoredItems. Also, you are missing a try..finally block that is in the original Delphi code.

  • I would suggest changing the StoredItems property to use TStrings* instead of TStringList*. That will make it more flexible for accepting non-TStringList lists. And SetStoredItems() should not be declared __published. For that matter, does it really make sense to expose the StoredItems property to the Object Inspector at design-time? StoredItems is public in the original Delphi code, not __published.

With that said, try this instead:

class PACKAGE TSmartComboBox : public TComboBox
{
    typedef TComboBox inherited;

private:
    TStrings* FStoredItems;
    bool dofilter;
    int storeditemindex;
    void __fastcall FilterItems();
    void __fastcall StoredItemsChange(TObject* Sender);
    void __fastcall SetStoredItems(TStrings* Value);
    void __fastcall CNCommand(TWMCommand& AMessage);
protected:
    DYNAMIC void __fastcall KeyPress(WideChar &Key);
    DYNAMIC void __fastcall CloseUp();
    DYNAMIC void __fastcall Click();
public:
    __fastcall TSmartComboBox(TComponent* Owner);
    __fastcall ~TSmartComboBox();
    void __fastcall InitSmartCombo();

    BEGIN_MESSAGE_MAP
        VCL_MESSAGE_HANDLER(CN_COMMAND, TWMCommand, CNCommand)
    END_MESSAGE_MAP(TComboBox)
    
__published:
    __property TStrings* StoredItems = {read = FStoredItems, write = SetStoredItems};
};
static inline void ValidCtrCheck(TSmartComboBox *)
{
    new TSmartComboBox(NULL);
}
//---------------------------------------------------------------------------
__fastcall TSmartComboBox::TSmartComboBox(TComponent* Owner)
    : TComboBox(Owner)
{
    FStoredItems = new TStringList;
    dofilter = false;
}

__fastcall TSmartComboBox::~TSmartComboBox()
{
    delete FStoredItems;
}

void _fastcall TSmartComboBox::SetStoredItems(TStrings* Value)
{
    FStoredItems->Assign(Value);
}

void __fastcall TSmartComboBox::KeyPress(WideChar &Key)
{
    inherited::KeyPress(Key);
    if (dofilter && (Key != 13 && Key != 27))
    {
        if (Items->Count != 0 && !DroppedDown)
        {
            SendMessage(Handle, CB_SHOWDROPDOWN, 1, 0);
        }
    }
}

void __fastcall TSmartComboBox::CloseUp()
{
    if (dofilter)
    {
        if(Items->Count == 1 && ItemIndex == 0)
        {
            Text = Items->Strings[ItemIndex];
        }
        else if ((Text != _D("") && ItemIndex != -1 && Text != Items->Strings[ItemIndex]) || (Text == _D("") && ItemIndex == 0))
        {
            storeditemindex = ItemIndex;
            String x = Text;
            ItemIndex = Items->IndexOf(Text);
            if(ItemIndex == -1)
            {
                Text = x;
            }
        }
        else
        {
            storeditemindex = -1;
        }
    }
    inherited::CloseUp();
}

void __fastcall TSmartComboBox::Click()
{
    if (dofilter)
    {
        if (storeditemindex != -1)
        {
            ItemIndex = storeditemindex;
            storeditemindex = -1;
        }
    }
    inherited::Click();
}

void __fastcall TSmartComboBox::InitSmartCombo()
{
    static_cast<TStringList*>(FStoredItems)->OnChange = NULL;
    FStoredItems->Assign(Items);
    AutoComplete = false;
    static_cast<TStringList*>(FStoredItems)->OnChange = &StoredItemsChange;
    dofilter = true;
    storeditemindex = -1;
}

void __fastcall TSmartComboBox::StoredItemsChange(TObject* Sender)
{
    FilterItems();
}

void TSmartComboBox::FilterItems()
{
    DWORD StartPos, EndPos;

    SendMessage(Handle, CB_GETEDITSEL, reinterpret_cast<WPARAM>(&StartPos), reinterpret_cast<LPARAM>(&EndPos));

    Items->BeginUpdate();
    try
    {
        if (Text != _D(""))
        {
            Items->Clear();

            for (int i = 0; i < FStoredItems->Count; ++i)
            {
                if (UpperCase(Text).Pos(UpperCase(FStoredItems->Strings[i])) > 0)
                {
                    Items->Add(FStoredItems->Strings[i]);
                }
            }
        }
        else
        {
            Items->Assign(FStoredItems);
        }
    }
    __finally
    {
        Items->EndUpdate();
    }

    SendMessage(Handle, CB_SETEDITSEL, 0, MAKELPARAM(StartPos, EndPos));
}

void TSmartComboBox::CNCommand(TWMCommand AMessage)
{
    TComboBox::Dispatch(&AMessage);
    if (AMessage.NotifyCode == CBN_EDITUPDATE && dofilter)
    {
        FilterItems();
    }
}

//---------------------------------------------------------------------------
namespace Comboboxsmart
{
    void __fastcall PACKAGE Register()
    {
         TComponentClass classes[1] = {__classid(TSmartComboBox)};
         RegisterComponents(L"polpress", classes, 0);
    }
}

You appear to be basing your C++ code on this Delphi code, but you have not incorporated any of the additional fixes provided by this Delphi code. Just saying...

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770