Interesting issue in Delphi's TDBComboBox

Core business of our company is creating Client-Server database applications and we decided to migrate all of our projects from a bit obsolete Delphi XE to Delphi 10.2. To be frank, whole migration took a very significant piece of time because we had to replace discontinued FIB Plus components set from Devrace with modern Devart IBDAC suite, especially to assure working with new versions of Firebird database server which FIBPlus does not handle any longer.

Alongside with checking and testing behavior of migrated projects in general, we found several issues when behavior of the same component was different between XE and 10.2 version but with a collaboration of suppliers of those components, we sorted out all this cases successfuly.

The last - and probably most difficult task - was to wait whether we miss some bugs or behavior changes and if and when our users will report such strange difficulties and issues. What a surprise - one quite ugly and unpleasant problem had been found and reported to us recently - it relates to common TDBComboBox component.

Let's have a typical TDBComboBox connected through DataSource and DataField to database table. As you can see on the screenshot of Object Inspector and in DFM file too, nothing strange or ambiguous is set. Note that Style property is set to csDropDown because we need to use values from attached Items list but we must also allow user to enter his custom values.

Delphi Bug Delphi Bug


object DBComboBox21: TDBComboBox
          Left = 24
          Top = 360
          Width = 145
          Height = 21
          DataField = 'NAZEV1'
          DataSource = FaktDataModule1.DataSource1
          Items.Strings = (
            'item1'
            'item2'
            'item3')
          TabOrder = 54
        end

If you navigate through the dataset using First, Last, Next or Previous method, field values are displayed correctly regardless of the fact if actual value is contained in Items property or was entered manually and differs from Items. This approach worked like a charm under Delphi XE and I expected the same in Delphi 10.2.

Alas, our customers warned us and complained that "some field values are gone while navigating among records, appear again if user navigates back, some of them are displayed always correctly". At this point I realized that there was something wrong and started to examine and debug my code together with our major components supplier. Together, we found certain ambiguous and suspicious code in TDBComboBox.SetComboText in Vcl.DBCtrls unit.


procedure TDBComboBox.SetComboText(const Value: string);
var
  I: Integer;
  Redraw: Boolean;
begin
  if Value <> GetComboText then
  begin
    if Style <> csDropDown then
    begin
      Redraw := (Style <> csSimple) and HandleAllocated;
      if Redraw then SendMessage(Handle, WM_SETREDRAW, 0, 0);
      try
        if Value = '' then I := -1 else I := Items.IndexOf(Value);
        ItemIndex := I;
      finally
        if Redraw then
        begin
          SendMessage(Handle, WM_SETREDRAW, 1, 0);
          Invalidate;
        end;
      end;
      if I >= 0 then Exit;
    end;
    if Style in [csDropDown, csSimple] then
    begin
      Text := Value;
      if Value = '' then ItemIndex := -1 else ItemIndex := Items.IndexOf(Value);
    end;
  end;
end;

It's apparently clear that last line of code cannot provide proper working in the case if given value is not found in the Items list. This code implementation causes erroneous behavior described above. However, related code for SetComboText in DBCtrls unit is the same in version XE, 10.2 and even in 10.3 - the difference is only that in XE the identical code worked okay but in 10.2 does not.

How about solution? Is quite simple, a minor change of the last part of the method will enough. Copy your Vcl.DBCtrls.pas and Vcl.DBGrids.pas from Source directory to Lib directory (keep on mind your platform, I'm talking about Lib\Win32 subdirectory, and make following changes:


procedure TDBComboBox.SetComboText(const Value: string);
var
  I: Integer;
  Redraw: Boolean;
begin
  if Value <> GetComboText then
  begin
    if Style <> csDropDown then
    begin
      Redraw := (Style <> csSimple) and HandleAllocated;
      if Redraw then SendMessage(Handle, WM_SETREDRAW, 0, 0);
      try
        if Value = '' then I := -1 else I := Items.IndexOf(Value);
        ItemIndex := I;
      finally
        if Redraw then
        begin
          SendMessage(Handle, WM_SETREDRAW, 1, 0);
          Invalidate;
        end;
      end;
      if I >= 0 then Exit;
    end;
    {if Style in [csDropDown, csSimple] then
    begin
      Text := Value;
      if Value = '' then ItemIndex := -1 else ItemIndex := Items.IndexOf(Value);
    end;}
    if Style in [csDropDown, csSimple] then
    begin
      Text := Value;
      if Value = '' then ItemIndex := -1 else
      begin
        I:=Items.IndexOf(Value);
        if I>=0 then
          ItemIndex := I;
      end;
    end;

  end;
end;

Save the changes and recompile your project. After that, you should see that appropriate values in DBComboBoxes are displayed correctly.

About the Author

Ing. Karel Janecek, MBA, MSc.
Torry.net

Unit changes:
Ing. Tomas Rosinsky
www.rosinsky.cz/delphi