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.
Related
I wrote some code to make the first row white, second grey, third white, and so on. To do that, I had to use OwnerDraw=true, but now the ListView doesn't respond as it used to when you hover over a row. How do I add that back?
This is what I have now:
procedure TAchievementTracker.lvAchievementsDrawItem(Sender: TSMView;
Item: TSMListItem; 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 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;
Sender.Canvas.Brush.Style := bsSolid;
Sender.Canvas.FillRect(Rect);
x1 := 0;
x2 := 0;
r := Rect;
Sender.Canvas.Brush.Style := bsClear;
for i := 0 to lvAchievements.Columns.Count - 1 do
begin
inc(x2, lvAchievements.Columns[i].Width);
r.Left := x1;
r.Right := x2;
if i = 0 then
S := Item.Caption
else
S := ' ' + Item.SubItems[i-1];
DrawText(Sender.Canvas.Handle,
S,
length(S),
r,
DT_SINGLELINE or DT_ALIGN[lvAchievements.Columns[i].Alignment] or
DT_VCENTER or DT_END_ELLIPSIS);
x1 := x2;
end;
end;
There's a simpler way to colour the lines of a list view control than to use full owner drawing. You can use the OnCustomDrawItem event even if OwnerDraw is False:
procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView;
Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
const
BgColors: array[Boolean] of TColor = (clWhite, clSilver);
FgColors: array[Boolean] of TColor = (clBlack, clBlack);
begin
Sender.Canvas.Brush.Color := BgColors[Odd(Item.Index)];
Sender.Canvas.Font.Color := FgColors[Odd(Item.Index)];
end;
This actually preserves the themed hover and selected effects:
The problem is that the standard themed effects typically look bad together with the custom colours.
So perhaps it is better to fully custom-draw it (OwnerDraw = True):
procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
Rect: TRect; State: TOwnerDrawState);
const
BgColors: array[Boolean] of TColor = (clWhite, clSilver);
FgColors: array[Boolean] of TColor = (clBlack, clBlack);
Alignments: array[TAlignment] of TTextFormats = (tfLeft, tfRight, tfCenter);
var
LV: TListView;
i, x1, x2: Integer;
R: TRect;
S: string;
begin
LV := Sender as TListView;
if [odSelected, odHotLight] * State <> [] then
begin
LV.Canvas.Brush.Color := clNavy;
LV.Canvas.Font.Color := clWhite;
end
else
begin
LV.Canvas.Brush.Color := BgColors[Odd(Item.Index)];
LV.Canvas.Font.Color := FgColors[Odd(Item.Index)];
end;
LV.Canvas.Brush.Style := bsSolid;
LV.Canvas.FillRect(Rect);
x1 := 0;
x2 := 0;
R := Rect;
LV.Canvas.Brush.Style := bsClear;
for i := 0 to LV.Columns.Count - 1 do
begin
Inc(x2, LV.Columns[i].Width);
R.Left := x1;
R.Right := x2;
if i = 0 then
S := Item.Caption
else
S := Item.SubItems[i - 1];
S := #32 + S;
LV.Canvas.TextRect(R, S, [tfSingleLine,
Alignments[LV.Columns[i].Alignment], tfVerticalCenter, tfEndEllipsis]);
x1 := x2;
end;
if odFocused in State then
begin
LV.Canvas.Brush.Style := bsSolid;
LV.Canvas.Brush.Color := clBlack;
LV.Canvas.Font.Color := clWhite;
Rect.Inflate(-1, -1);
DrawFocusRect(LV.Canvas.Handle, Rect);
end;
end;
Unfortunately, as you can see, this introduces new problems, such as an alignment issue, which I "solved" in a very sloppy way in this snippet. Also, it seems like this approach doesn't allow you to produce a hover ("hot") effect. The snippet above supports highlight and focus, but not hover.
OK, let's do it!
If you really, really, want the hot effect, there's always a way:
Set the list view control's Tag to -1, let
procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
Rect: TRect; State: TOwnerDrawState);
const
BgColors: array[Boolean] of TColor = (clWhite, clSilver);
FgColors: array[Boolean] of TColor = (clBlack, clBlack);
Alignments: array[TAlignment] of TTextFormats = (tfLeft, tfRight, tfCenter);
var
LV: TListView;
i, x1, x2: Integer;
R: TRect;
S: string;
begin
LV := Sender as TListView;
if ListView1.Tag = Item.Index then //
begin //
LV.Canvas.Brush.Color := clSkyBlue; // NEW
LV.Canvas.Font.Color := clBlack; //
end //
else if odSelected in State then
begin
LV.Canvas.Brush.Color := clNavy;
LV.Canvas.Font.Color := clWhite;
end
else
begin
LV.Canvas.Brush.Color := BgColors[Odd(Item.Index)];
LV.Canvas.Font.Color := FgColors[Odd(Item.Index)];
end;
LV.Canvas.Brush.Style := bsSolid;
LV.Canvas.FillRect(Rect);
x1 := 0;
x2 := 0;
R := Rect;
LV.Canvas.Brush.Style := bsClear;
for i := 0 to LV.Columns.Count - 1 do
begin
Inc(x2, LV.Columns[i].Width);
R.Left := x1;
R.Right := x2;
if i = 0 then
S := Item.Caption
else
S := Item.SubItems[i - 1];
S := #32 + S;
LV.Canvas.TextRect(R, S, [tfSingleLine,
Alignments[LV.Columns[i].Alignment], tfVerticalCenter, tfEndEllipsis]);
x1 := x2;
end;
if (odFocused in State) and not (odNoFocusRect in State) then
begin
LV.Canvas.Brush.Style := bsSolid;
LV.Canvas.Brush.Color := clBlack;
LV.Canvas.Font.Color := clWhite;
Rect.Inflate(-1, -1);
DrawFocusRect(LV.Canvas.Handle, Rect);
end;
end;
and add the following OnMouseMove handler:
procedure TForm1.ListView1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
LI: TListItem;
Idx: Integer;
begin
LI := ListView1.GetItemAt(X, Y);
if Assigned(LI) then
Idx := LI.Index
else
Idx := -1;
if Idx <> ListView1.Tag then
begin
ListView1.Tag := Idx;
ListView1.Invalidate; // maybe overkill
end;
end;
and the following OnMouseLeave handler:
procedure TForm1.ListView1MouseLeave(Sender: TObject);
begin
if ListView1.Tag <> -1 then
begin
ListView1.Tag := -1;
ListView1.Invalidate;
end;
end;
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);
}
Is there a way to draw a transparent text on form that has some controls? If I use TLabel control, it would always show behind controls on the form.
You cannot use a TLabel control, since it is not a windowed control, and therefore it will be hidden by every windowed child control of the form. You could use a TStaticText, which is indeed a windowed control (a STATIC control), but it will be a bit difficult to make it truly transparent, I'd suppose.
You can use layered windows for this:
Create a new VCL project, and add a bunch of windowed controls to it.
Create a new form in the project, named splash. Set BorderStyle to bsNone, and set the font name, size, and colour to whatever you desire (e.g., Segoe UI, 42, red).
Add a public method
procedure Tsplash.UpdateSplash(const Str: string);
var
R: TRect;
P: TPoint;
S: TPoint;
bm: TBitmap;
bf: TBlendFunction;
EXSTYLE: DWORD;
x, y: integer;
pixel: PRGBQuad;
TextRed,
TextGreen,
TextBlue: byte;
begin
EXSTYLE := GetWindowLong(Handle, GWL_EXSTYLE);
SetWindowLong(Handle, GWL_EXSTYLE, EXSTYLE or WS_EX_LAYERED);
R := ClientRect;
bm := TBitmap.Create;
try
bm.PixelFormat := pf32bit;
bm.SetSize(ClientWidth, ClientHeight);
bm.Canvas.Brush.Color := clBlack;
bm.Canvas.FillRect(ClientRect);
bm.Canvas.Font.Assign(Self.Font);
bm.Canvas.Font.Color := clWhite;
DrawText(bm.Canvas.Handle, PChar(Str), Length(Str), R,
DT_SINGLELINE or DT_VCENTER or DT_CENTER or DT_WORD_ELLIPSIS);
TextRed := GetRValue(Font.Color);
TextGreen := GetGValue(Font.Color);
TextBlue := GetBValue(Font.Color);
for y := 0 to bm.Height - 1 do
begin
pixel := bm.ScanLine[y];
x := 0;
while x < bm.Width do
begin
with pixel^ do
begin
rgbReserved := (rgbRed + rgbGreen + rgbBlue) div 3;
rgbBlue := TextBlue * rgbReserved div 255;
rgbGreen := TextGreen * rgbReserved div 255;
rgbRed := TextRed * rgbReserved div 255;
end;
inc(pixel);
inc(x);
end;
end;
P := Point(0, 0);
S := Point(bm.Width, bm.Height);
bf.BlendOp := AC_SRC_OVER;
bf.BlendFlags := 0;
bf.SourceConstantAlpha := 255;
bf.AlphaFormat := AC_SRC_ALPHA;
UpdateLayeredWindow(Handle, 0, nil, #S, bm.Canvas.Handle, #P, 0, #bf,
ULW_ALPHA)
finally
bm.Free;
end;
end;
To your main form, add private methods
procedure TForm1.CreateSplash;
var
p: TPoint;
begin
splash.Visible := true;
UpdateSplash;
end;
procedure TForm1.UpdateSplash;
var
p: TPoint;
begin
if not (Assigned(splash) and splash.Visible) then Exit;
p := ClientToScreen(Point(0, 0));
splash.SetBounds(p.X, p.Y, ClientWidth, ClientHeight);
splash.UpdateSplash('Sample Text');
end;
and call UpdateSplash every time the form is moved or resized:
procedure TForm1.WMMove(var Message: TWMMove);
begin
UpdateSplash;
end;
procedure TForm4.FormResize(Sender: TObject);
begin
UpdateSplash;
end;
Finally, you can do, just to try it out,
procedure TForm1.FormClick(Sender: TObject);
begin
if splash.Visible then
splash.Hide
else
CreateSplash;
end;
Compiled demo EXE
Enable Autosize of columns and enable OwnerDraw for a List View. Then add a code below from HERE:
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;
Actively resize the penultimate column if it has autosize. It bill be bugs:
How to prevent these bugs?
Thanks!
The bug is in the TListColumn.GetWidthin 'comctrls.pas'. The VCL is retrieving a wrong column width while resizing columns when 'AutoSize' on the columns is set, hence you're drawing item text all over the columns.
I looked at the VCL code for a few minutes and couldn't figure out what's wrong, but setting the value in a getter is suspicious enough.
Anyway, for a workaround, instead of
inc(x2, ListView1.Columns[i].Width);
use this:
inc(x2, ListView_GetColumnWidth(ListView1.Handle, ListView1.Columns[i].Index));
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.