I want to color one row in gray and the other in white.
I have the following code but there is white space of vertical lines of columns in Windows 7.
How do I color all rows?
procedure TForm2.Update_ListBoxCustomDrawItem(Sender: TCustomListView;
Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
begin
if Item.Index mod 2=0
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;
end;
Set OwnerDraw to true and add
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 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 ListView1.Columns.Count - 1 do
begin
inc(x2, ListView1.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[ListView1.Columns[i].Alignment] or
DT_VCENTER or DT_END_ELLIPSIS);
x1 := x2;
end;
end;
In the above example, the first column is left-aligned and the two other are centered.
If you only have one column, that is, no subitems:
procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
Rect: TRect; State: TOwnerDrawState);
var
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);
r := Rect;
Sender.Canvas.Brush.Style := bsClear;
S := Item.Caption;
DrawText(Sender.Canvas.Handle,
S,
length(S),
r,
DT_SINGLELINE or DT_ALIGN[ListView1.Columns[0].Alignment] or DT_VCENTER or DT_END_ELLIPSIS);
end;
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;
Sorry, can't comment on how to turn off hottracking on Delphi ListView?, I start a new question.
Following the advice of above link, but when I click on the text of caption, the list shows as follows:
I have set brush.color in drawItem and drawSubItem of each row:
Sender.Canvas.Brush.Color := mycolor
But If I click on the text of the caption, then in the caption region, only the text part have the colored background.
Code: (Running environment: XE6, Win8.1)
TMyTaskListView = class(TListView)
protected
procedure CreateWnd; override;
end;
procedure TMyTaskListView.CreateWnd;
begin
inherited;
SetWindowTheme(WindowHandle, nil, nil);
end;
var
ListView1: TMyTaskListView;
Form.createForm:
ListView1.Columns.Add.Caption := 'TaskNo';
ListView1.Columns.Add.Caption := 'Task1';
ListView1.Columns.Add.Caption := 'Task2';
ListView1.Columns.Add.Caption := 'Task3';
ListView1.Columns.Add.Caption := 'Task Status';
ListView1.Items.Count := 5;
procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
begin
Item.Caption := IntToStr(Item.Index);
Item.SubItems.Add('done');
Item.SubItems.Add('error');
Item.SubItems.Add('error');
Item.SubItems.Add('error');
end;
procedure TForm1.ListView1CustomDrawItem(Sender: TCustomListView;
Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
begin
if lstTaskItemCurClickBackgroundIndex = Item.Index then
Sender.Canvas.Brush.Color := $cc;
end;
procedure TForm1.ListView1CustomDrawSubItem(Sender: TCustomListView;
Item: TListItem; SubItem: Integer; State: TCustomDrawState;
var DefaultDraw: Boolean);
begin
if lstTaskItemCurClickBackgroundIndex = Item.Index then
Sender.Canvas.Brush.Color := $cc;
end;
procedure TForm1.ListView1Click(Sender: TObject);
var
ARect: TRect;
Idx: Integer;
SubItemIndex: Integer;
pt: TPoint;
item : TLIstItem;
hittestinfo: TLVHitTestInfo;
Rect: TRect;
begin
Idx := -1;
pt:= ListView1.ScreenToClient( mouse.cursorpos );
item := ListView1.GetItemAt( pt.x, pt.y );
If assigned( item ) then
Idx := item.Index
else
begin
FillChar( hittestinfo, sizeof( hittestinfo ), 0 );
hittestinfo.pt := pt;
If -1 <>ListView1.perform( LVM_SUBITEMHITTEST, 0, lparam(#hittestinfo))
Then
Begin
Idx := hittestinfo.iItem;
SubItemIndex := hittestinfo.iSubItem;
End
Else
if (ListView1.Items.Count > 0) then
Idx := 0;
end;
if Idx >= 0 then
begin
Rect := ListView1.Items[Idx].DisplayRect(drBounds);
if lstTaskItemPrevClickBackgroudColorRect <> Rect then
begin
InvalidateRect(ListView1.Handle, lstTaskItemPrevClickBackgroudColorRect, True);
InvalidateRect(ListView1.Handle, Rect, True);
end;
lstTaskItemPrevClickBackgroudColorRect := Rect;
lstTaskItemCurClickBackgroundIndex := Idx;
end;
end;
I want to color one row in gray and the other in white.
I have the following code but there is white space of vertical lines of columns in Windows 7.
How do I color all rows?
procedure TForm2.Update_ListBoxCustomDrawItem(Sender: TCustomListView;
Item: TListItem; State: TCustomDrawState; var DefaultDraw: Boolean);
begin
if Item.Index mod 2=0
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;
end;
Set OwnerDraw to true and add
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 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 ListView1.Columns.Count - 1 do
begin
inc(x2, ListView1.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[ListView1.Columns[i].Alignment] or
DT_VCENTER or DT_END_ELLIPSIS);
x1 := x2;
end;
end;
In the above example, the first column is left-aligned and the two other are centered.
If you only have one column, that is, no subitems:
procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem;
Rect: TRect; State: TOwnerDrawState);
var
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);
r := Rect;
Sender.Canvas.Brush.Style := bsClear;
S := Item.Caption;
DrawText(Sender.Canvas.Handle,
S,
length(S),
r,
DT_SINGLELINE or DT_ALIGN[ListView1.Columns[0].Alignment] or DT_VCENTER or DT_END_ELLIPSIS);
end;
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.