How to draw TTreeView's styled selection rectangle during AdvancedCustomDrawItem? - delphi

I'm doing custom TTreeView drawing from scratch using OnAdvancedCustomDrawItem event, and I wonder how to render these selection and hot rectangles correctly in the background of my owner-draw items? They are Vista/7 styled so I cannot simply fill the background in some solid color.
I tried to draw my items at cdPostPaint stage, but if I leave DefaultDraw := True atcdPrePaint stage to draw selection background, the complete default drawing occurs, including text of items.
procedure TForm1.TreeView1AdvancedCustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage; var PaintImages,
DefaultDraw: Boolean);
begin
case Stage of
cdPreErase:
begin
DefaultDraw := True;
end;
cdPostErase:
begin
DefaultDraw := True;
end;
cdPrePaint:
begin
// I thought this will paint only the selected/hot backgrounds,
// however this will paint whole item, including text.
DefaultDraw := True;
end;
cdPostPaint:
begin
DefaultDraw := False;
// painting my owner-draw text
// .........
end;
end;
PaintImages := False;
end;

Here is my solution (tested).
Note the TreeView must have HotTrack := True to draw hot items normally.
There also must be additional drawing when themes not enabled.
uses
UxTheme,
Themes;
const
TreeExpanderSpacing = 6;
procedure TForm1.DrawExpander(ACanvas: TCanvas; ATextRect: TRect; AExpanded: Boolean;
AHot: Boolean);
var
ExpanderRect: TRect;
Graphics: IGPGraphics;
Points: array of TGPPoint;
Brush: IGPBrush;
Pen: IGPPen;
ThemeData: HTHEME;
ElementPart: Integer;
ElementState: Integer;
ExpanderSize: TSize;
UnthemedColor: TColor;
begin
if ThemeServices.ThemesEnabled then
begin
if AHot then
ElementPart := TVP_HOTGLYPH
else
ElementPart := TVP_GLYPH;
if AExpanded then
ElementState := GLPS_OPENED
else
ElementState := GLPS_CLOSED;
ThemeData := OpenThemeData(TreeView1.Handle, VSCLASS_TREEVIEW);
GetThemePartSize(ThemeData, ACanvas.Handle, ElementPart, ElementState, nil,
TS_TRUE, ExpanderSize);
ExpanderRect.Left := ATextRect.Left - TreeExpanderSpacing - ExpanderSize.cx;
ExpanderRect.Right := ExpanderRect.Left + ExpanderSize.cx;
ExpanderRect.Top := ATextRect.Top + (ATextRect.Bottom - ATextRect.Top - ExpanderSize.cy) div 2;
ExpanderRect.Bottom := ExpanderRect.Top + ExpanderSize.cy;
DrawThemeBackground(ThemeData, ACanvas.Handle, ElementPart, ElementState, ExpanderRect, nil);
CloseThemeData(ThemeData);
end
else
begin
// Drawing expander without themes enabled
Graphics := TGPGraphics.Create(ACanvas.Handle);
Graphics.SmoothingMode := SmoothingModeHighQuality;
ExpanderRect := ATextRect;
ExpanderRect.Right := ATextRect.Left - TDPIAware.GetScaledSize(TreeExpanderSpacing96dpi);
ExpanderRect.Left := ATextRect.Left - TDPIAware.GetScaledSize(TreeExpanderSpacing96dpi) -
TDPIAware.GetScaledSize(Max(TreeExpanderCollapsedWidth96dpi, TreeExpanderExpandedWidth96dpi));
if ASelected then
UnthemedColor := ColorToRGB(clHighlightText)
else
if AExpanded then
UnthemedColor := clBlack
else
UnthemedColor := clGray;
SetLength(Points, 3);
if AExpanded then
begin
Points[0] := TGPPoint.Create(ExpanderRect.Right, ExpanderRect.Top +
(ExpanderRect.Bottom - ExpanderRect.Top - TreeExpanderExpandedHeight96dpi) div 2);
Points[1] := TGPPoint.Create(ExpanderRect.Right, ExpanderRect.Top +
(ExpanderRect.Bottom - ExpanderRect.Top + TreeExpanderExpandedHeight96dpi) div 2);
Points[2] := TGPPoint.Create(ExpanderRect.Right - TreeExpanderExpandedWidth96dpi,
ExpanderRect.Top + (ExpanderRect.Bottom - ExpanderRect.Top +
TreeExpanderExpandedHeight96dpi) div 2);
Brush := TGPSolidBrush.Create(TGPColor.CreateFromColorRef(UnthemedColor));
Graphics.FillPolygon(Brush, Points);
end
else
begin
Points[0] := TGPPoint.Create(ExpanderRect.Right - TreeExpanderCollapsedWidth96dpi,
ExpanderRect.Top + (ExpanderRect.Bottom - ExpanderRect.Top -
TreeExpanderCollapsedHeight96dpi) div 2);
Points[1] := TGPPoint.Create(ExpanderRect.Right,
ExpanderRect.Top + (ExpanderRect.Bottom - ExpanderRect.Top) div 2);
Points[2] := TGPPoint.Create(ExpanderRect.Right - TreeExpanderCollapsedWidth96dpi,
ExpanderRect.Top + (ExpanderRect.Bottom - ExpanderRect.Top +
TreeExpanderCollapsedHeight96dpi) div 2);
Pen := TGPPen.Create(TGPColor.CreateFromColorRef(UnthemedColor));
Graphics.DrawPolygon(Pen, Points);
end;
end;
end;
procedure TForm1.TreeView1AdvancedCustomDrawItem(Sender: TCustomTreeView;
Node: TTreeNode; State: TCustomDrawState; Stage: TCustomDrawStage; var PaintImages,
DefaultDraw: Boolean);
var
NodeRect: TRect;
NodeTextRect: TRect;
Text: string;
ThemeData: HTHEME;
TreeItemState: Integer;
begin
if Stage = cdPrePaint then
begin
NodeRect := Node.DisplayRect(False);
NodeTextRect := Node.DisplayRect(True);
// Drawing background
if (cdsSelected in State) and Sender.Focused then
TreeItemState := TREIS_SELECTED
else
if (cdsSelected in State) and (cdsHot in State) then
TreeItemState := TREIS_HOTSELECTED
else
if cdsSelected in State then
TreeItemState := TREIS_SELECTEDNOTFOCUS
else
if cdsHot in State then
TreeItemState := TREIS_HOT
else
TreeItemState := TREEITEMStateFiller0;
if TreeItemState <> TREEITEMStateFiller0 then
begin
ThemeData := OpenThemeData(Sender.Handle, VSCLASS_TREEVIEW);
DrawThemeBackground(ThemeData, Sender.Canvas.Handle, TVP_TREEITEM, TreeItemState,
NodeRect, nil);
CloseThemeData(ThemeData);
end;
// Drawing expander
if Node.HasChildren then
DrawExpander(Sender.Canvas, NodeTextRect, Node.Expanded, cdsHot in State);
// Drawing main text
SetBkMode(Sender.Canvas.Handle, TRANSPARENT);
SetTextColor(Sender.Canvas.Handle, clBlue);
Text := Node.Text;
Sender.Canvas.TextRect(NodeTextRect, Text,
[tfVerticalCenter, tfSingleLine, tfEndEllipsis, tfLeft]);
// Some extended drawing...
end;
PaintImages := False;
DefaultDraw := False;
end;

Related

How can I restore the Highlight function in an owner-drawn ListView

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;

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:

How can I change the background and font color of a selected item in list view?

I tried to change the colors with the OnCustomDrawItem event but it has no effect.
procedure TForm1.RListCustomDrawItem(Sender: TCustomListView; Item: TListItem;
State: TCustomDrawState; var DefaultDraw: Boolean);
begin
if cdsSelected in State then begin
Sender.Canvas.Brush.Color:=clRed;
Sender.Canvas.Font.Color:=clYellow;
end;
end;
I use the default TListView component with 3 columns an ViewStyle set to vsReport.
The font color only will work as shown in your code.
If you want to change the Background color you will have to Draw the Item and the SubItems on your own and set DefaultDraw to false.
This could look like:
procedure TMyForm.ListView1CustomDrawItem(Sender: TCustomListView; Item: TListItem
; State: TCustomDrawState; var DefaultDraw: Boolean);
var
rt, r: TRect;
s: String;
i: Integer;
c:TCanvas;
// Fit the rect used for TextRect
Procedure PrepareTextRect;
begin
rt := r;
rt.Left := rt.Left + 5;
rt.Top := rt.Top + 1;
end;
begin
c := Sender.Canvas;
if (cdsSelected in State) then
begin
c.Brush.Color := clRed;
c.Font.Color := clYellow;
// will get the rect for Item + Subitems in ViewStyle = vsReport
r := Item.DisplayRect(drBounds);
c.FillRect(r);
// set width to get fitting rt for tfEndEllipsis
r.Right := r.Left + TListView(Sender).Columns[0].Width;
s := Item.Caption;
PrepareTextRect;
c.TextRect(rt, s, [tfSingleLine, tfEndEllipsis]);
if TListView(Sender).ViewStyle = vsReport then
begin // Paint the Subitems if ViewStyle = vsReport
for i := 0 to Item.SubItems.Count - 1 do
begin
r.Left := r.Left + TListView(Sender).Columns.Items[i].Width;
r.Right := r.Left + TListView(Sender).Columns.Items[i + 1].Width;
PrepareTextRect;
s := Item.SubItems[i];
c.TextRect(rt, s, [tfSingleLine, tfEndEllipsis]);
end;
end;
DefaultDraw := false;
end;
end;
If you set ViewStyle to vsList then you are up and running.

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