Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

how to make TListView header sections to become owner drawn?

795 views
Skip to first unread message

Iliya

unread,
Jan 30, 2008, 10:13:24 AM1/30/08
to
THeaderControl has a collection of THeaderSection items that have the style property which can be set to hsOwnerDraw but TListView's header doewn't seem to have one. How to make TListView header sections to become owner drawn?

Thanks

Peter Below (TeamB)

unread,
Jan 30, 2008, 12:41:56 PM1/30/08
to
Iliya wrote:

You have to go down to the API level to do this, since the header of a
listview is not a VCL THeaderControl but a native header common
control. The header control does not have support for owner-drawing,
though (at least in the last version I looked at), so you would have to
subclass the control to catch WM_ERASEBKGND and WM_PAINT messages. If
you are only looking for a way to draw a sort indicator there is an
easier way. See the following older message.

Showing sort indicators on a listviews header

> Ok but image display on the left of the Listview Colum caption
> I 'd like image on the rigth (see outlook express column sort)
>

The problem is that not all versions of the listview common control
support this. You have to drop to the API to make use of it. This is
somewhat ackward (the common controls seem to get more cumbersome to
use with each version). The listviews header line is an actual header
control. A header control can display either images from an imagelist
*or* a bitmap. Only the bitmap can be arranged to the right of the
caption text. The listview offers no direct method to set a bitmap for
a header, so you have to get the header controls handle and send
messages to it directly. The bitmap you use should be created on form
creation and destroyed on form destruction.

The following example shows the principle. There is a *major* snag
here, though. Since the VCL listview has no idea that you changed some
header properties it will happily wipe out what you did every time it
feels like resetting some of the header properties, e.g. when the user
resizes one of the columns. This can be dealt with if needs be, by
subclassing the header control to trap the HDM_SETITEM messages that
change the item properties. The TListview class already subclasses the
header but the method used is private and not virtual, so not
accessible.

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs,
ComCtrls, StdCtrls, ImgList;

type
TForm1 = class(TForm)
ListView1: TListView;
ImageList1: TImageList;
procedure ListView1ColumnClick(Sender: TObject; Column:
TListColumn);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
FUpArrow, FDownArrow: TBitmap;
procrrow, FDownArrow: TBitmap;
procedure SetColumnSortOrder(lv: TListview; Column: TListcolumn);
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

uses commctrl;
{$R *.DFM}

Procedure TForm1.SetColumnSortOrder( lv: TListview; Column: TListcolumn
);
var
hdr: HWND;
hdritem: THDItem;
Begin
hdr := Listview_GetHeader( lv.handle );
FillChar( hdritem, sizeof(hdritem), 0 );
hdritem.Mask := HDI_FORMAT;
Header_GetItem( hdr, column.index, hdritem );
hdritem.Mask := HDI_FORMAT or HDI_BITMAP;
If column.tag = 0 Then
hdritem.hbm := FUpArrow.Handle
Else
hdritem.hbm := FDownArrow.Handle;
hdritem.fmt := hdritem.fmt OR HDF_BITMAP_ON_RIGHT OR HDF_BITMAP;
Header_SetItem( hdr, column.index, hdritem );
End;


procedure TForm1.ListView1ColumnClick(Sender: TObject;
Column: TListColumn);
begin
Column.Tag := Ord( not Odd( Column.Tag ));
SetColumnSortOrder( Sender as TListview, Column );
end;

procedure TForm1.FormCreate(Sender: TObject);
procedure MakeBitmap( Var bmp: TBitmap; imageindex: Integer );
begin
bmp:= TBitmap.Create;
bmp.Width := imagelist1.width;
bmp.Height := imagelist1.height;
With bmp.Canvas Do Begin
Brush.COlor := clBtnface;
Brush.Style := bsSolid;
FillRect( Cliprect );
End;
imagelist1.Draw( bmp.canvas, 0, 0, imageindex );
end;
begin
MakeBitmap( FUpArrow, 1 );
MakeBitmap( FDownArrow, 0 );
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
FUpArrow.Free;
FDownArrow.Free;
end;

end.


--
Peter Below (TeamB)
Don't be a vampire (http://slash7.com/pages/vampires),
use the newsgroup archives :
http://www.tamaracka.com/search.htm
http://groups.google.com

Iliya

unread,
Jan 30, 2008, 3:31:06 PM1/30/08
to

"Peter Below (TeamB)" <no...@nomail.please> wrote in message news:xn0flt7r...@newsgroups.borland.com...

> You have to go down to the API level to do this, since the header of a
> listview is not a VCL THeaderControl but a native header common
> control. The header control does not have support for owner-drawing,
> though (at least in the last version I looked at), so you would have to
> subclass the control to catch WM_ERASEBKGND and WM_PAINT messages. If
> you are only looking for a way to draw a sort indicator there is an
> easier way. See the following older message.
>

Thank you for help but what you mean when you say doesn't have the support. The HDF_OWNERDRAW flag is that suppose to make the header item ownerdrawn or am I missing something? I also would very much like to look at example of subclassing a header control in Delphi that would be very helpfull as I have very vague comprehension on this.

Peter Below (TeamB)

unread,
Jan 31, 2008, 12:28:14 PM1/31/08
to
Iliya wrote:

>
> "Peter Below (TeamB)" <no...@nomail.please> wrote in message
> news:xn0flt7r...@newsgroups.borland.com...
>
> > You have to go down to the API level to do this, since the header
> > of a listview is not a VCL THeaderControl but a native header common
> > control. The header control does not have support for owner-drawing,
> > though (at least in the last version I looked at), so you would
> > have to subclass the control to catch WM_ERASEBKGND and WM_PAINT
> > messages. If you are only looking for a way to draw a sort
> > indicator there is an easier way. See the following older message.
> >
>
> Thank you for help but what you mean when you say doesn't have the
> support. The HDF_OWNERDRAW flag is that suppose to make the header
> item ownerdrawn or am I missing something?

No, I was missing something: I only looked at the header control
styles, not at item-specific flags. The THeaderControl VCL class does
in fact support this through the hsOwnerDraw style. But the TListview
class has no support for owner-drawing of its header. To retrofit this
you would have to solve two problems:

- find a way to set the HDF_OWNERDRAW flag for the header items of
interest.
- intercept the WM_DRAWITEM message the header control will then send
to its listview.

The second part is in fact the easier one: derive a new class from
TListview and add a message handler for this message. You do not need
to delve into API-style subclassing for this task. But the first task
is tricky, not so much because you cannot get at the header controls
window handle (that is easy: GetWindow(listview.handle, GW_CHILD) or
FindWindowEx(listview.handle, 0, 'SysHeader32', nil)) but because you
will get into a fight with the Listview over the control of the header
item styles.

> I also would very much
> like to look at example of subclassing a header control in Delphi
> that would be very helpfull as I have very vague comprehension on
> this.

Study the TCustomListview source code in comctrls.pas. Search for
HeaderWndProc and FHeaderInstance. The listview does indeed subclass
the header already, but you cannot make use of that since the
HeaderWndProc is not virtual. The tricky part here is *when* to do the
subclassing and when to undo it, since the listview will only have a
header control in vsReport style.

I would suggest that you simply forget the TListview and use a
TStringgrid or TDrawgrid instead. It is much easier to customize the
way it is drawn since full support for owner-drawing all cells is
available through the OnDrawCell event.

Iliya

unread,
Jan 31, 2008, 5:08:45 PM1/31/08
to

"Peter Below (TeamB)" <no...@nomail.please> wrote in message news:xn0flulz...@newsgroups.borland.com...

Thank you for information!

> - find a way to set the HDF_OWNERDRAW flag for the header items of
> interest.

var
hHeader: THandle;
HdItem: THdItem;
begin
hHeader:=ListView_GetHeader(ListView1.Handle);
Header_GetItem(hHeader, 1, hditem);
hditem.fmt := hditem.fmt or HDF_OWNERDRAW;
Header_SetItem(hHeader, 0, hditem);
end;

> - intercept the WM_DRAWITEM message the header control will then send
> to its listview.

procedure TForm1.FormCreate(Sender: TObject);
begin
WndMethod := ListView1.WindowProc; //where WndMethod is a private member of Form1
ListView1.WindowProc := CheckMesg;
end;

procedure TForm1.FormClose(Sender: TObject; var CanClose: Boolean);
begin
ListView1.WindowProc := WndMethod;
end;

procedure TForm1.CheckMesg(var aMesg: TMessage);
var
SaveIndex: Integer;
begin
case aMesg.Msg of
WM_DRAWITEM:
begin
with TWMDrawItem(aMesg).DrawItemStruct^ do
begin
SaveIndex := SaveDC(hDC);
FCanvas.Lock;
try
FCanvas.Handle := hDC;
FCanvas.Font := Font;
FCanvas.Brush.Color := clBtnFace;
FCanvas.Brush.Style := bsSolid;
DrawSection(itemID, rcItem, itemState and ODS_SELECTED <> 0);//DrawSection is actually my custom drawing routine.
finally
FCanvas.Handle := 0;
FCanvas.Unlock;
RestoreDC(hDC, SaveIndex);
end;
end;
aMesg.Result := 1;
end;
else
WndMethod(aMesg);
end;
end;

This is how far i've gone so far. Everything is fine except for one thing: when you resize the columns, the header item reverts back to non custom draw state. I believe it is because of the thing you've mentioned in your first post (HDM_SETITEM message). as you also mentioned in this post that in order to prevent the header from receiving this message I have to subclass the header window, now i think it's time to consider it. Again would you please shed some light on the thing called subclassing in Delphi?

Peter Below (TeamB)

unread,
Feb 1, 2008, 1:04:12 PM2/1/08
to
Iliya wrote:

> This is how far i've gone so far. Everything is fine except for one
> thing: when you resize the columns, the header item reverts back to
> non custom draw state. I believe it is because of the thing you've
> mentioned in your first post (HDM_SETITEM message). as you also
> mentioned in this post that in order to prevent the header from
> receiving this message I have to subclass the header window, now i
> think it's time to consider it. Again would you please shed some
> light on the thing called subclassing in Delphi?

Since the header is not a VCL control you have to use API-style
subclassing. I told you where to look for an example, didn't I?

0 new messages