Search code examples
databasedelphicombobox

Delphi DBCombobox with CSOwnerDraw Style, only show own Items not Datafield String


I have DBCombobox with CsOwnerDrawFixed Style that connected to Tstringfield.

sometimes database stringfield content doesn't exist in dbcombobox.Items (because of User mistake in separate process) and dbcombobox control show nothing. How can I show this data as text on dbcombobox?

procedure TForm1.DBComboBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
begin
   with DBComboBox1.Canvas do
    begin
     if odselected in State then
      Brush.Color:= $00D68759
     else
      Brush.Color:= $00FEECDD;
      FillRect(rect);
      TextOut(30, Rect.Top, DBComboBox1.Items[Index]);
      Pen.Color   := clwhite;
      Brush.Style:= bsClear;
      TextOut(Rect.Left + 4, Rect.Top, UpCase(DBComboBox1.Items[Index][1]));
      Rectangle(Rect.Left + 1, Rect.Top + 1, Rect.Left +20, Rect.Top + DBComboBox1.ItemHeight-2);
    end;
end;

enter image description here


Solution

  • I do this in two steps. after creating a Combobox with this capabality, I inherited a DBCombobox from manipulated combobox.

    This is a simple Combobox test.

    unit Unit1;
    
    interface
    uses
     Windows, Messages, SysUtils, Variants, Classes, Graphics,   Controls, Forms, Dialogs, StdCtrls, DBCtrls;
    
    type
     TManipulatecombobox= class(stdctrls.TCombobox)
     private
      Ftext: string;
      FOnDrawItem: TDrawItemEvent;
      procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;
      procedure Settext(Avalue: string);
     protected
      procedure DrawItem(Index: Integer; Rect: TRect; State: TOwnerDrawState); reintroduce; virtual;
     property OnDrawItem: TDrawItemEvent read FOnDrawItem write FOnDrawItem;
    public
     property text: string read Ftext write Settext;
    end;
    
    TForm1 = class(TForm)
     Edit1: TEdit;
     DBComboBox1: TDBComboBox;
     procedure FormCreate(Sender: TObject);
     procedure Edit1Change(Sender: TObject);
    private
     Combo: TManipulatecombobox;
    end;
    
    var
     Form1: TForm1;
    
    implementation
    {$R *.dfm}
    
    procedure TManipulatecombobox.CNDrawItem(var Message:  TWMDrawItem);
    var
      State: TOwnerDrawState;
    begin
      with Message.DrawItemStruct^ do
      begin
        State := TOwnerDrawState(LongRec(itemState).Lo);
        if itemState and ODS_COMBOBOXEDIT <> 0 then
         Include(State, odComboBoxEdit);
        if itemState and ODS_DEFAULT <> 0 then
         Include(State, odDefault);
       Canvas.Handle := hDC;
       Canvas.Font := Font;
       Canvas.Brush := Brush;
       if (Integer(itemID) >= 0) and (odSelected in State) then
       begin
        Canvas.Brush.Color := clHighlight;
        Canvas.Font.Color := clHighlightText
      end;
       if Integer(itemID) >= 0 then
        DrawItem(itemID, rcItem, State) else
    
       if (Integer(itemID) = -1) and (odComboBoxEdit in State)  then
        DrawItem(itemID, rcItem, State)
       else
        Canvas.FillRect(rcItem);
    
      if odFocused in State then DrawFocusRect(hDC, rcItem);
       Canvas.Handle := 0;
      end;
    end;
    
    procedure TManipulatecombobox.DrawItem(Index: Integer;  Rect: TRect;
     State: TOwnerDrawState);
    begin
     TControlCanvas(Canvas).UpdateTextFlags;
     if Assigned(FOnDrawItem) then FOnDrawItem(Self, Index, Rect, State)
     else
     begin
      Canvas.FillRect(Rect);
       if Index = -1 then
        Canvas.TextOut(Rect.Left + 2, Rect.Top, text);
       if Index >= 0 then
        Canvas.TextOut(Rect.Left + 2, Rect.Top, Items[Index]);
     end;
    end;
    
    procedure TManipulatecombobox.Settext(Avalue: string);
    begin
     if Avalue <> Ftext then
     begin
      Ftext:= Avalue;
      //I'm not sure this is a good way.
      Invalidate;
     end;
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
     combo:= TManipulatecombobox.Create(self);
     with combo do
     begin
      parent:= self;
      SetBounds(20, 50, 120, 30);
      Items.Add('item1');
      items.Add('item2');
      Style:= csOwnerDrawVariable;
     end;
    end;
    
    procedure TForm1.Edit1Change(Sender: TObject);
    begin
     Combo.ItemIndex:= -1;
     Combo.Settext(TEdit(sender).Text);
    end;
    
    end.
    

    And finally, I copy all DBCombobox class from VCL code and change two functions.

    TManipulateDBcombobox= class(TManipulatecombobox)
    private
      FDataLink: TFieldDataLink;
      FPaintControl: TPaintControl;
      procedure DataChange(Sender: TObject);
      procedure EditingChange(Sender: TObject);
      function GetComboText: string;
      function GetDataField: string;
      function GetDataSource: TDataSource;
      function GetField: TField;
      function GetReadOnly: Boolean;
      procedure SetComboText(const Value: string);
      .
      .
    end;
    
    implementation
    .
    .
    procedure TManipulateDBcombobox.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 [csownerdrawfixed, csownerdrawvariable] then //?
      text:= value;
     end;
    end;
    
    function TManipulateDBcombobox.GetComboText: string;
    begin
     if Style in [csDropDown, csSimple] then Result := Text else
     if ItemIndex < 0 then Result := text else Result := Items[ItemIndex];
    end;