Drawing a Progress Bar using TStringGrid's OnDrawCell event [duplicate] - delphi

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Grid progressbar or animation
Add graphical bar to a StringGrid col
Using Delphi 2010, I have a TStringGrid with 5 columns
ID, Start, End, Duration, and a column to draw a progress bar in each cell.
column 5 width (example: 60) is set by the Bar width spin edit field in options dialog.
Given that duration is (end - start) * 1440 (example: 0.39 minutes), I need to draw the progress bar as a percentage of the total bar width. (i.e. 39/60 = 65%) therefore the bar should be painted 65% accross the cell. It also needs to show the percentage centered in the bar. (navy blue bar & white text).
can anyone help me to paint this progress bar ?
procedure Tphasedata.grdMainDrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
LStrCell: string;
LRect: TRect;
begin
with (Sender as TStringGrid) do
begin
// Don't change color for first Column, first row
if (ACol = 0) or (ARow = 0) then
Canvas.Brush.Color := clBtnFace
else
begin
case ACol of
0: Canvas.Font.Color := clBlack;
1: Canvas.Font.Color := clBlue;
2: Canvas.Font.Color := clBlue;
3: Canvas.Font.Color := clRed;
end;
// Draw the Band
if ARow mod 2 = 0 then
Canvas.Brush.Color := $00E1FFF9
else
Canvas.Brush.Color := $00FFEBDF;
Canvas.TextRect(Rect, Rect.Left + 2, Rect.Top + 2, cells[acol, arow]);
Canvas.FrameRect(Rect);
//center the duration text
if ACol = 3 then
begin
LStrCell := Cells[ACol, ARow]; // grab cell text
Canvas.FillRect(Rect); // clear the cell
LRect := Rect;
LRect.Top := LRect.Top + 3; // adjust top to center vertical
// draw text
DrawText(Canvas.Handle, PChar(LStrCell), Length(LStrCell), LRect, DT_CENTER);
end;
i ACol = 4 then
begin
// draw progress bar here
end;
end;
end;

var
percent:Double;
procedure DrawTheText(const hDC: HDC; const Font: TFont; var Text: string; aRect:TRect);
var
lRect:Trect;
begin
with TBitmap.Create do
try
Width := aRect.Right - aRect.Left;
Height := aRect.Bottom - aRect.Top;
LRect :=Rect(0,0,width,height);
Canvas.Font.Assign(Font);
Canvas.Brush.Color := clBlack;
Canvas.FillRect(Lrect);
Canvas.Font.Color := clWhite;
Canvas.TextRect(Lrect,Text,[tfCenter ,tfVerticalCenter,tfSingleLine]);
BitBlt(hDC, aRect.Left, aRect.Top, Width, Height, Canvas.Handle, 0, 0, SRCINVERT);
finally
Free;
end;
end;
procedure TForm3.DrawGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
LRect:Trect;
s:String;
c:TCanvas;
begin
//.....yout code
percent := 0.5;//Random(2) / 60;
//.... case of wished Colum
c := DrawGrid1.Canvas;
LRect := Rect;
LRect.Right := Round(LRect.Left + (LRect.Right - LRect.Left)*percent);
inflaterect(LRect,-1,-1);
c.Brush.Color := clNavy;
c.Brush.Style := bsSolid;
c.Pen.Color := clBlack;
C.FillRect(LRect);
s := FormatFloat('0.00 %' , percent * 100 );
DrawTheText(c.Handle,DrawGrid1.font,s,rect);
end;

Related

Delphi TPopupMenu design modifications

Can we achieve the below look and feel with TPopupMenu VCl component
Can someone guide us in achieving the design?
I have tried setting OwnerDraw to True and wrote the OnDrawItem for menu items, But that is not successfull.
procedure TForm.tCopyDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; Selected: Boolean);
var
s: string;
begin
// change font
ACanvas.Font.Name := 'Noto Sans';
ACanvas.Font.Size := 12;
//ACanvas.Font.Style := [fsBold];
ACanvas.Font.Color := $00757575;
// change background
ACanvas.Brush.Color := clWindow;
ACanvas.Rectangle(ARect);
// write caption/text
s := (Sender as TMenuItem).Caption;
//ACanvas.TextOut(ARect.Left + 2, ARect.Top + 2 , s);
ACanvas.TextOut(-2, -2, s);
end;
after compiling this I got the look and feel like below.
I have to eliminate that black border and align the items vertically.
UPDATE
I have managed to write some code to get the UI as shown in the image but only the Vertical line separator in-between icons and text is missing.
My code is as below:
procedure TForm1.pmiProjectCopyDrawItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; Selected: Boolean);
var
bt: Tbitmap;
begin
bt := Tbitmap.Create;
with TMenuItem(Sender) do
begin
with ACanvas do
begin
Brush.Color := clWhite;
FillRect(ARect);
pen.Color := $00E5DFD7;
if Selected then
begin
Font.Color := $006C4E1F;
end
else
begin
Font.Color := $00757575;
end;
Font.Size := 8;
Font.Name := 'Noto Sans';
if Caption = '-' then
begin
MoveTo(ARect.left + 25, ARect.Top + 3);
LineTo(ARect.Width, ARect.Top + 3);
end
else
begin
ImageList1.GetBitmap(ImageIndex, bt);
Draw(ARect.left + 3, ARect.Top + 3, bt);
ARect.left := ARect.left + 25;
DrawText(ACanvas.Handle, PChar(Caption), Length(Caption), ARect,
DT_SINGLELINE or DT_VCENTER);
DrawText(ACanvas.Handle, PChar(ShortCutToText(shortcut)),
Length(ShortCutToText(shortcut)), ARect, DT_SINGLELINE or DT_RIGHT);
end;
end;
end;
end;
when i compile this code my output is as below :
Only thing left is I want to get a vertical line as shown in below image:
I have managed to write some code to get the UI as shown in the image but only the Vertical line separator in-between icons and text is missing.
My code is as below:
procedure TForm1.pmiProjectCopyDrawItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; Selected: Boolean);
var
bt: Tbitmap;
begin
bt := Tbitmap.Create;
with TMenuItem(Sender) do
begin
with ACanvas do
begin
Brush.Color := clWhite;
FillRect(ARect);
pen.Color := $00E5DFD7;
if Selected then
begin
Font.Color := $006C4E1F;
end
else
begin
Font.Color := $00757575;
end;
Font.Size := 8;
Font.Name := 'Noto Sans';
if Caption = '-' then
begin
MoveTo(ARect.left + 25, ARect.Top + 3);
LineTo(ARect.Width, ARect.Top + 3);
end
else
begin
ImageList1.GetBitmap(ImageIndex, bt);
Draw(ARect.left + 3, ARect.Top + 3, bt);
ARect.left := ARect.left + 25;
DrawText(ACanvas.Handle, PChar(Caption), Length(Caption), ARect,
DT_SINGLELINE or DT_VCENTER);
DrawText(ACanvas.Handle, PChar(ShortCutToText(shortcut)),
Length(ShortCutToText(shortcut)), ARect, DT_SINGLELINE or DT_RIGHT);
end;
end;
end;
end;
when i compile this code my output is as below :
Only thing left is I want to get a vertical line as shown in below image:
I have to eliminate that black border and align the items vertically.
This is written in C++. I've assumed that the MenuItem string is known.
The DoGetMenuString function is not accessible.
void __fastcall TForm1::Undo1DrawItem(TObject *Sender, TCanvas *ACanvas,
TRect &ARect, bool Selected)
{
// The assumptions are that the Canvas colors etc and the Rect sizes
// are already set by the program
// The text has two spaces at the front and four spaces at the end
const AnsiString ItemStr(" Undo Ctrl+Z ");
// calculate the position to draw the text
static int textpos = (ARect.Height() - ACanvas->TextHeight(ItemStr)) / 2;
// choose the color for the text
if( Selected)
ACanvas->Font->Color = clCream;
else
ACanvas->Font->Color = clAqua;
// Fill the whole rectangle
ACanvas->FillRect(ARect);
// write text to Canvas
ACanvas->TextOut(
ARect.Left,
textpos,
ItemStr);
}

Howto create same style of tMenuItem with AdvancedDrawItem?

I would like to add a Line with a specific color for each MenuItem of popup menu in Tokyo VCL app. The Style is "Amethyst Kamri".
I'm invoked the AdvancedDrawItem event of each MenuItem as below. However, the hilighted box is flat and has not the same 3d shape as the non-ownerdraw look.
The flat background (in Orange):
While I would like to get it:
Howto implement it better? Delphi 10.2, VCL.
procedure TForm1.mnuColorAdvancedDrawItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; State: TOwnerDrawState);
var
MenuItem : tMenuItem;
LStyles : TCustomStyleServices;
LDetails : TThemedElementDetails;
begin
MenuItem := (Sender as TMenuItem);
LStyles := StyleServices;
ACanvas.Brush.Style := bsClear;
ACanvas.Font.Color := LStyles.GetStyleFontColor(sfPopupMenuItemTextNormal);
//check the state
if odSelected in State then
begin
ACanvas.Brush.Color := LStyles.GetSystemColor(clHighlight);
ACanvas.Font.Color := LStyles.GetSystemColor(clHighlightText);
end;
ACanvas.FillRect(ARect);
ARect.Left := ARect.Left + 2;
//draw the text
ACanvas.TextOut(ARect.Left + 2, ARect.Top, MenuItem.Caption);
end;
Thanks
Reron
I more or less find a solution. The problem was using Canvas FillRect.
Assume three PopUp menu items, Red, Green and Blue. The line color for each of them is stored in each Tag field. Each Menu-line is composed from three elements: A Check mark, a Color line and the Caption.
All three items have a common event ColorAdvancedDrawItem.
All Owner draw methods are based on Styles and not on direct Canvas drawing, except the new lines. See code:
procedure TForm1.ColorAdvancedDrawItem(Sender: TObject; ACanvas: TCanvas;
ARect: TRect; State: TOwnerDrawState);
const
CheckBoxWidth = 20;
LineLen = 25;
var
MenuItem : tMenuItem;
LStyles : TCustomStyleServices;
LDetails : TThemedElementDetails;
CheckBoxRect, LineRect, TextRect: TRect;
Y: integer;
begin
MenuItem := (Sender as TMenuItem);
LStyles := StyleServices;
// Draw Check box
if MenuItem.Checked then
begin
LDetails := StyleServices.GetElementDetails(tmPopupCheckNormal);
CheckBoxRect := ARect;
CheckBoxRect.Width := CheckBoxWidth;
LStyles.DrawElement(ACanvas.Handle, LDetails, CheckBoxRect);
end;
// Draw text
// Check the state
if odSelected in State then
LDetails := StyleServices.GetElementDetails(tmPopupItemHot)
else
LDetails := StyleServices.GetElementDetails(tmPopupItemNormal);
TextRect := ARect;
TextRect.Left := CheckBoxWidth + LineLen;
LStyles.DrawText(ACanvas.Handle, LDetails, MenuItem.Caption, TextRect, [tfLeft, tfSingleLine, tfVerticalCenter]);
// Draw Line
ACanvas.Pen.Color := tColor(MenuItem.Tag);
ACanvas.Pen.Width := 2;
LineRect := ARect;
LineRect.Left := CheckBoxWidth;
LineRect.Width:= LineLen;
Y := LineRect.Top + (LineRect.Height div 2);
ACanvas.MoveTo(LineRect.Left+2, Y);
ACanvas.LineTo(LineRect.Left + LineRect.Width - 2, Y);
end;
The results looks like:

TListview is not correctly painted when OnDrawItem event is used

I'm using the OnDrawItem event in the TlistView component to draw the content using custom colors, but when scroll the listview some artifacts appears.
This is the code used.
procedure TForm35.FormCreate(Sender: TObject);
var
i, j : integer;
Item : TListItem;
s : string;
begin
for i:= 0 to 99 do
begin
Item:=ListView1.Items.Add;
for j:= 0 to ListView1.Columns.Count-1 do
begin
s:= Format('Row %d Column %d',[i+1, j+1]);
if j=0 then
Item.Caption :=s
else
Item.SubItems.Add(s);
end;
end;
end;
procedure TForm35.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
Rect: TRect; State: TOwnerDrawState);
var
x, y, i: integer;
begin
if odSelected in State then
begin
TListView(Sender).Canvas.Brush.Color := clYellow;
TListView(Sender).Canvas.Font.Color := clBlack;
end
else
begin
TListView(Sender).Canvas.Brush.Color := clLtGray;
TListView(Sender).Canvas.Font.Color := clGreen;
end;
TListView(Sender).Canvas.FillRect(Rect);
x := 5;
y := (Rect.Bottom - Rect.Top - TListView(Sender).Canvas.TextHeight('Hg')) div 2 + Rect.Top;
TListView(Sender).Canvas.TextOut(x, y, Item.Caption);
for i := 0 to Item.SubItems.Count - 1 do
begin
inc(x, TListView(Sender).Columns[i].Width);
TListView(Sender).Canvas.TextOut(x, y, Item.SubItems[i]);
end;
end;
I tested this code in Delphi 2007 and XE3, but I'm getting the same results. How i can prevent this issue?
Ok. Change X := 5 to X := Rect.Left;
And another solution (may be more accuracy):
uses
Graphics;
//... Form or something else declarations ...
implementation
procedure TForm35.ListView1DrawItem(Sender: TCustomListView; Item: TListItem; Rect: TRect; State: TOwnerDrawState);
var
s: string;
ts: TTextStyle; // Text style (used for drawing)
begin
// inherited;
// Clear target rectangle
// Set Canvas'es Font, Pen, Brush according for Item state
// Get into s variable text value of the Cell.
ts.Alignment := taLeftJustify; // Horz left alignment
ts.Layout := tlCenter; // Vert center alignment
ts.EndEllipsis := True; // End ellipsis (...) If line of text is too long too fit between left and right boundaries
// Other fields see in the Graphics.TTextStyle = packed record
ListView1.Canvas.TextRect(
Rect,
Rect.Left, // Not sure, but there are a small chance of this values equal to zero instead of Rect...
Rect.Top,
s,
ts)
end;
end.
And to prevent some flicking...
...
var
b: TBitmap;
begin
b := TBitmap.Create;
try
b.Widht := Rect.Right - Rect.Left;
b.Height := Rect.Bottom - Rect.Top;
//...
// Draw content on the b.Canvas
//...
ListView.Canvas.Draw(Rect.Left, Rect.Top, b);
finally
b.Free;
end;
end;

Add graphical bar to a StringGrid col

Using Delphi 2010 and a TStringGrid component, I currently display five filds from a database query.
Here is a simplied example of what i am doing
//set up the grid
procedure TGriddata.FormCreate(Sender: TObject);
begin
grdMain.Rows[0].commatext:='"One","Two","Three","Four","Five"';
grdMain.ColWidths[0]:= 50;
grdMain.ColWidths[1]:= 175;
grdMain.ColWidths[2]:= 175;
grdMain.ColWidths[3]:= 100;
grdMain.ColWidths[4]:= 300;
end;
//display the data in the grid
//note, I am not showing my creation, execution, or destroy of the query
procedure TGriddata.load;
begin
...
grdMain.Cells[0,row]:= FieldByName('one').AsString;
grdMain.Cells[1,row]:= FieldByName('two').AsString;
grdMain.Cells[2,row]:= FieldByName('three').AsString;
grdMain.Cells[3,row]:= FieldByName('four').AsString;
//draw progress bar here
...
end;
One of the columns ("Five") needs to display a navy blue horizontal bar in the col. It should also diplay some text centered in the bar. I have no expereince using the custom drawing. What properties do i set to only custom draw the one column and use the default drawing for the other columns?
Add the text to the cells like you normally would. But you have to draw those bars in the OnDrawCell event. Leave DefaultDrawing as is (True by default), and erase the already drawn cell text in those columns by filling it in advance:
procedure TForm1.grdMainDrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
Progress: Single;
R: TRect;
Txt: String;
begin
with TStringGrid(Sender) do
if (ACol = 4) and (ARow >= FixedRows) then
begin
Progress := StrToFloatDef(Cells[ACol, ARow], 0) / 100;
Canvas.FillRect(Rect);
R := Rect;
R.Right := R.Left + Trunc((R.Right - R.Left) * Progress);
Canvas.Brush.Color := clNavy;
Canvas.Rectangle(R);
Txt := Cells[ACol, ARow] + '%';
Canvas.Brush.Style := bsClear;
IntersectClipRect(Canvas.Handle, R.Left, R.Top, R.Right, R.Bottom);
Canvas.Font.Color := clHighlightText;
DrawText(Canvas.Handle, PChar(Txt), -1, Rect, DT_SINGLELINE or
DT_CENTER or DT_VCENTER or DT_END_ELLIPSIS or DT_NOPREFIX);
SelectClipRgn(Canvas.Handle, 0);
ExcludeClipRect(Canvas.Handle, R.Left, R.Top, R.Right, R.Bottom);
Canvas.Font.Color := clWindowText;
DrawText(Canvas.Handle, PChar(Txt), -1, Rect, DT_SINGLELINE or
DT_CENTER or DT_VCENTER or DT_END_ELLIPSIS or DT_NOPREFIX);
SelectClipRgn(Canvas.Handle, 0);
end;
end;
For more options, you might consider this DrawStatus routine.
Here you can view a sample (Draw percentage in a cell in a Grid), to draw a bar in a cell of a TStringGrid.
The explanation is in spanish, but you can download the code, that is very simple.
Also you can use authomatic translation on right of page.
procedure TFormDrawCell.DBGrid1DrawColumnCell(Sender: TObject;
const Rect: TRect; DataCol: Integer; Column: TColumn;
State: TGridDrawState);
const
STR_EMPTY = '';
CHAR_PERCENT = '%';
SPACE_TO_CENTER_CELLTEXT = 0;
var
fValue: Integer;
ActualPenColor, ActualBrushColor: TColor;
EmptyDS: Boolean;
DrawRect: TRect;
fWidth1, fLeft2: Integer;
StrValue: string;
begin
if not (Column.FieldName = 'Precent') then
Exit;
if not (cbdraw.Checked) then
Exit;
EmptyDS := ((TDBGrid(Sender).DataSource.DataSet.EoF) and
(TDBGrid(Sender).DataSource.DataSet.Bof));
if (Column.Field.IsNull) then begin
fValue := -1;
StrValue := STR_EMPTY;
end
else begin
fValue := Column.Field.AsInteger;
StrValue := IntToStr(fValue) + CHAR_PERCENT;
end;
DrawRect := Rect;
InflateRect(DrawRect, -1, -1);
fWidth1 := (((DrawRect.Right - DrawRect.Left) * fValue) DIV 100);
ActualPenColor := TDBGrid(Sender).Canvas.Pen.Color;
ActualBrushColor := TDBGrid(Sender).Canvas.Brush.Color;
TDBGrid(Sender).Canvas.Pen.Color := clHighlight;
TDBGrid(Sender).Canvas.Brush.Color := clWhite;
TDBGrid(Sender).Canvas.Rectangle(DrawRect);
if (fValue > 0) then begin
TDBGrid(Sender).Canvas.Pen.Color := clSkyBlue;
TDBGrid(Sender).Canvas.Brush.Color := clSkyBlue;
DrawRect.Right := DrawRect.Left + fWidth1;
InflateRect(DrawRect, -1, -1);
TDBGrid(Sender).Canvas.Rectangle(DrawRect);
end;
if not (EmptyDS) then begin
DrawRect := Rect;
InflateRect(DrawRect, -2, -2);
TDBGrid(Sender).Canvas.Brush.Style := bsClear;
fLeft2 := DrawRect.Left + (DrawRect.Right - DrawRect.Left) shr 1 -
(TDBGrid(Sender).Canvas.TextWidth(StrValue) shr 1);
TDBGrid(Sender).Canvas.TextRect(DrawRect, fLeft2,
DrawRect.Top + SPACE_TO_CENTER_CELLTEXT, StrValue);
end;
TDBGrid(Sender).Canvas.Pen.Color := ActualPenColor;
TDBGrid(Sender).Canvas.Brush.Color := ActualBrushColor;
end;
Regards.

Delphi: draw selections and mouse-hover effects on ListViewDrawItem

I have a code from here: Delphi: Canvas.FillRect in List View to paint rows (I use OwnerDraw).
Therefore I need to draw selections and mouse-hover effects. And to draw a normal column resizing effect...
I have a code to move items up and down:
procedure TForm1.ListViewDragDrop(Sender, Source: TObject; X,
Y: Integer);
var
DragItem, DropItem, CurrentItem, NextItem: TListItem;
begin
if Sender = Source then
with TListView(Sender) do
begin
DropItem := GetItemAt(X, Y);
CurrentItem := Selected;
while CurrentItem <> nil do
begin
NextItem := GetNextItem(CurrentItem, SdAll, [IsSelected]);
if DropItem = nil then DragItem := Items.Add
else
DragItem := Items.Insert(DropItem.Index);
DragItem.Assign(CurrentItem);
CurrentItem.Free;
CurrentItem := NextItem;
end;
end;
end;
procedure TForm1.ListViewDragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
begin
Accept := Sender = Update_ListBox;
end;
how to draw selections and mouse-hover effects on ListViewDrawItem + column resizing effect?
Thanks!!!
If you want to draw selections and in other ways respond to the current state of the control, you need to write additional code for this purpose, because we are owner-drawing the control. This means that we say to Windows, "hey, don't draw anything in the client area, I'll do that". Therefore, Windows draws nothing, not even selections, focus rectangles, and mouse-hover effects.
Fortunately, it is rather easy to implement this behaviour manually. Indeed, in the OnCustomDraw event handler, you are given a State parameter, which you can read. This is a set, and some of the possible elements include odSelected, odHotLight, and odFocused.
Building upon our previous code, adding only a few new lines, we arrive at
procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
Rect: TRect; State: TOwnerDrawState);
var
i: Integer;
x1, x2: integer;
r: TRect;
S: string;
const
DT_ALIGN: array[TAlignment] of integer = (DT_LEFT, DT_RIGHT, DT_CENTER);
begin
if SameText(Item.SubItems[1], 'done') then
begin
Sender.Canvas.Font.Color := clWhite;
Sender.Canvas.Brush.Color := clGreen;
end
else
if Odd(Item.Index) then
begin
Sender.Canvas.Font.Color := clBlack;
Sender.Canvas.Brush.Color := $F6F6F6;
end
else
begin
Sender.Canvas.Font.Color := clBlack;
Sender.Canvas.Brush.Color := clWhite;
end;
if odSelected in State then // NEW!
begin // NEW!
Sender.Canvas.Font.Color := clWhite; // NEW!
Sender.Canvas.Brush.Color := clNavy; // NEW!
end; // NEW!
Sender.Canvas.Brush.Style := bsSolid;
Sender.Canvas.FillRect(Rect);
x1 := 0;
x2 := 0;
r := Rect;
Sender.Canvas.Brush.Style := bsClear;
Sender.Canvas.Draw(3, r.Top + (r.Bottom - r.Top - bm.Height) div 2, bm);
for i := 0 to ListView1.Columns.Count - 1 do
begin
inc(x2, ListView1.Columns[i].Width);
r.Left := x1;
r.Right := x2;
if i = 0 then
begin
S := Item.Caption;
r.Left := bm.Width + 6;
end
else
S := Item.SubItems[i - 1];
DrawText(Sender.Canvas.Handle,
S,
length(S),
r,
DT_SINGLELINE or DT_ALIGN[ListView1.Columns[i].Alignment] or
DT_VCENTER or DT_END_ELLIPSIS);
x1 := x2;
end;
if odFocused in State then // NEW!
DrawFocusRect(Sender.Canvas.Handle, Rect); // NEW!
end;
Notice that the 'horse' line is selected and has keyboard focus.

Resources