Recent

Author Topic: [SOLVED]: Creating a combobox to display pen styles with BGRABitmap  (Read 1307 times)

EganSolo

  • Sr. Member
  • ****
  • Posts: 398
I've created a combo box to display pen styles with the standard TBitmap and I decided to replace it with TBGRABitmap because I believe the output will be far more pleasing to the eye.
I'm obviously missing something basic here. Please check the full code down below (and you can also run the attached project):

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.    Classes , SysUtils , Forms , Controls , Graphics , Dialogs , StdCtrls , Types,
  9.    BGRABitmapTypes, BGRABitmap;
  10.  
  11. type
  12.  
  13.    { TForm1 }
  14.  
  15.    TForm1 = class(TForm)
  16.       ComboBox1: TComboBox;
  17.       procedure ComboBox1DrawItem(Control: TWinControl; Index: Integer; ARect: TRect; State: TOwnerDrawState);
  18.       procedure FormCreate(Sender: TObject);
  19.    private
  20.       procedure DrawPenItem(const anIndex: integer; ARect: TRect);
  21.    public
  22.    end;
  23.  
  24. var
  25.    Form1: TForm1;
  26.  
  27. implementation
  28.  
  29. {$R *.lfm}
  30.  
  31. { TForm1 }
  32.  
  33. procedure TForm1.FormCreate(Sender: TObject);
  34. begin
  35.   with ComboBox1 do
  36.   begin
  37.     Items.Clear;
  38.     Style:= csOwnerDrawEditableVariable;
  39.     With Items do
  40.     begin
  41.       Add('Solid'       );
  42.       Add('Dash'        );
  43.       Add('DashDot'     );
  44.       Add('DashDotDot'  );
  45.       Add('Dot'         );
  46.       Add('Clear'       );
  47.     end;
  48.   end;
  49. end;
  50.  
  51. procedure TForm1.DrawPenItem(const anIndex: integer; ARect: TRect);
  52. var aBitmap    : TBGRABitmap;
  53.     aColor     : TColor     ;
  54.     aBGRAColor : TBGRAPixel ;
  55. begin
  56.   aColor := ComboBox1.Color;
  57.   aBGRAColor := ColorToBGRA(aColor);
  58.   aBitmap := TBGRABitmap.Create(aRect.Width div 2, aRect.Height, aBGRAColor);
  59.   case ComboBox1.Items[anIndex] of
  60.        'Solid'     : aBitmap.PenStyle := psSolid;
  61.        'Dash'      : aBitmap.PenStyle := psDash;
  62.        'DashDot'   : aBitmap.PenStyle := psDashDot;
  63.        'DashDotDot': aBitmap.PenStyle := psDashDotDot;
  64.        'Dot'       : aBitmap.PenStyle := psDot;
  65.        'Clear'     : aBitmap.PenStyle := psClear;
  66.   end;
  67.   aBitmap.DrawLineAntiAlias({x1             = } aRect.Left ,
  68.                             {y1             = } aRect.Top + aRect.Height div 2 ,
  69.                             {x2             = } aRect.Right div 2,
  70.                             {y2             = } aRect.Top + aRect.Height div 2 ,
  71.                             {aBGRAColor     = } BGRABlack     ,
  72.                             {aThickenss     = } 1
  73.                             );
  74.   aBitmap.FontHeight := 10;
  75.   aBitmap.FontAntialias := true;
  76.   abitmap.FontStyle := [];
  77.   aBitmap.TextOut(ARect.Right div 2 + 10, ARect.Top, ComboBox1.Items[anIndex],BGRABlack);
  78.   aBitmap.Draw(ComboBox1.Canvas,0,0,True);
  79.   aBitmap.Free;
  80. end;
  81.  
  82. procedure TForm1.ComboBox1DrawItem(Control: TWinControl; Index: Integer; ARect: TRect; State: TOwnerDrawState);
  83. begin
  84.  DrawPenItem(Index,aRect);
  85. end;
  86. end.
  87.  

In short: the TComboBox is fed six items. It's style is set to csOwnerDrawEditableVariable and the OnDrawItem event is handled. The bulk of the code is in the method DrawPenItem which
  • Sets the pen style of the BGRABitmap based on the item selected
  • Draws an anti alias line.
  • Draws the corresponding text with TextOut
  • Lastly, draws the content of the bitmap onto the canvas of the combo box. Since this is taking place inside the event handler of OnDrawItem, there is no further need to call aBitmap.draw from within the handler to the FormPaint event (I think?)

But when I run this code, I get this blank window (See attached png).

Any thoughts on where I'm going wrong gratefully appreciated!

« Last Edit: March 21, 2022, 08:05:24 am by EganSolo »

circular

  • Hero Member
  • *****
  • Posts: 4471
    • Personal webpage
Re: Creating a combobox to display pen styles with BGRABitmap
« Reply #1 on: March 20, 2022, 09:28:32 pm »
Hi,

I notice that the coordinates of DrawLineAntiAlias are not correct, because the bitmap starts at (0,0). You don't need to add Left or Top. Same thing for TextOut.

On the contrary, the coordinates of Draw(ComboBox1.Canvas...) need the Left and Top.
Conscience is the debugger of the mind

Handoko

  • Hero Member
  • *****
  • Posts: 5551
  • My goal: build my own game engine using Lazarus
Re: Creating a combobox to display pen styles with BGRABitmap
« Reply #2 on: March 21, 2022, 07:09:53 am »
That should be:

Code: Pascal  [Select][+][-]
  1. procedure TForm1.DrawPenItem(const anIndex: integer; ARect: TRect);
  2. var aBitmap    : TBGRABitmap;
  3.     aColor     : TColor     ;
  4.     aBGRAColor : TBGRAPixel ;
  5. begin
  6.  aColor := ComboBox1.Color;
  7.  aBGRAColor := ColorToBGRA(clWhite);
  8.  aBitmap := TBGRABitmap.Create(aRect.Width, aRect.Height, aBGRAColor);
  9.  case ComboBox1.Items[anIndex] of
  10.       'Solid'     : aBitmap.PenStyle := psSolid;
  11.       'Dash'      : aBitmap.PenStyle := psDash;
  12.       'DashDot'   : aBitmap.PenStyle := psDashDot;
  13.       'DashDotDot': aBitmap.PenStyle := psDashDotDot;
  14.       'Dot'       : aBitmap.PenStyle := psDot;
  15.       'Clear'     : aBitmap.PenStyle := psClear;
  16.  end;
  17.  aBitmap.DrawLineAntiAlias({x1             = } aRect.Left ,
  18.                            {y1             = } aRect.Top + aRect.Height div 2 ,
  19.                            {x2             = } aRect.Right div 2,
  20.                            {y2             = } aRect.Top + aRect.Height div 2 ,
  21.                            {aBGRAColor     = } BGRABlack     ,
  22.                            {aThickenss     = } 1
  23.                            );
  24.  aBitmap.FontHeight := 10;
  25.  aBitmap.FontAntialias := true;
  26.  abitmap.FontStyle := [];
  27.  aBitmap.TextOut(ARect.Right div 2 + 10, 5, ComboBox1.Items[anIndex],BGRABlack);
  28.  aBitmap.Draw(ComboBox1.Canvas, 0, anIndex*ARect.Height, True);
  29.  aBitmap.Free;
  30. end;

Tested on Linux GTK2, the result may be a bit different on other OSes.

EganSolo

  • Sr. Member
  • ****
  • Posts: 398
Re: Creating a combobox to display pen styles with BGRABitmap
« Reply #3 on: March 21, 2022, 08:04:47 am »
@Handoko: Thank you for posting a corrected method! Your code helped me understand how to use a TRect relative to the indices of the ComboxBox. I had not thought about that.
Having said that, just before you posted your code, I was about to post mine based on @Circular's comments. My solution is very similar to yours: I realized that I had to use BGRAWhite (or, as you did ColorToBGRA(clWhite)) to clear the background of the drop-down list. There are minor differences: 1. I switched to relative coordinates based on @Circular's comments and I've added a bit of code to highlight the selected or focused text. Other than that my code is very similar to yours.

Code: Pascal  [Select][+][-]
  1. procedure TForm1.DrawPenItem(const anIndex: integer; ARect: TRect; const aState : TOwnerDrawState);
  2. var aBitmap    : TBGRABitmap;
  3. begin
  4.   aBitmap := TBGRABitmap.Create(aRect.Width, aRect.Height,BGRAWhite);
  5.   case ComboBox1.Items[anIndex] of
  6.        'Solid'     : aBitmap.PenStyle := psSolid;
  7.        'Dash'      : aBitmap.PenStyle := psDash;
  8.        'DashDot'   : aBitmap.PenStyle := psDashDot;
  9.        'DashDotDot': aBitmap.PenStyle := psDashDotDot;
  10.        'Dot'       : aBitmap.PenStyle := psDot;
  11.        'Clear'     : aBitmap.PenStyle := psClear;
  12.   end;
  13.   aBitmap.DrawLineAntiAlias(1,aRect.Height div 2, aRect.width div 2, aRect.Height Div 2, BGRABlack,2);
  14.   aBitmap.FontHeight := 12;
  15.   aBitmap.FontAntialias := true;
  16.   If [odSelected, odFocused] * aState <> []
  17.   then begin
  18.     aBitmap.RectangleAntialias(aRect.Width div 2 + 5,1,aRect.Width,aRect.Height,BGRAPixelTransparent,0,VGABlue);
  19.     abitmap.FontStyle := [fsBold];
  20.     aBitmap.TextOut(aRect.Width div 2 + 10, 1 ,ComboBox1.Items[anIndex],BGRAWhite);
  21.   end
  22.   else aBitmap.TextOut(aRect.Width div 2 + 10 , 1 , ComboBox1.Items[anIndex],BGRABlack);
  23.   aBitmap.Draw(ComboBox1.Canvas,aRect);
  24.   aBitmap.Free;
  25. end;
  26.  

Key takeaway: This is now obvious, and presumably obvious to a lot of coders:
  • Set the dimensions of the bitmap relative to the canvas you want to draw on
  • Issue all the drawing commands with the origin being set to the top and left coordinate of the bitmap, not the target canvas
  • When ready to draw the bitmap back on the canvas, specify the coordinates where the bitmap must land and those coordinates are relative to the target canvas

I know that most of that is "duh" for most coders; I'm just slower. I wrote this in the hope that it might be helpful to others.

Cheers,

Egan

 

TinyPortal © 2005-2018