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

Progress Bar in ListView

5 views
Skip to first unread message

Jerry Cohen

unread,
Apr 5, 2001, 3:43:47 AM4/5/01
to
Is it possible to draw a progress bar (or any control for that matter) in a
subitem of a listview?

Thanks, Jerry


Uwe Molzahn

unread,
Apr 6, 2001, 5:26:00 AM4/6/01
to
Jerry

Take a look at the Delphi Pool at http://www.lmc-mediaagentur.de/dpool.htm

Under Delphi -> Tips & Tricks -> VCL -> TListView -> 0006 you will find what
you're looking for.

HTH
Uwe


Peter Below (TeamB)

unread,
Apr 6, 2001, 9:21:01 AM4/6/01
to
In article <3acc21f1_1@dnews>, Jerry Cohen wrote:
> Is it possible to draw a progress bar (or any control for that matter) in a
> subitem of a listview?
>
Well, you can in fact parent a live progressbar to a listview, you just
have to do it at run-time. The sample below has timer that randomly steps
the progress bars. The bar is added to the last column of the listview.

There are some gotchas here: the DisplayRect method of a listitem does
not return the correct position unless the control is visible, thus the
hack used at the top of the method to ensure this. And of course you will
have to adjust the width and left bound of the progress bars if the user
resizes columns. And you need to add new bars if the user can add items
and destroy bars if the user can delete item.

procedure TForm1.FormCreate(Sender: TObject);
var
pb: TProgressBar;
r: TRect;
i, k: Integer;
begin
Show;
Application.ProcessMessages;
for i:= 0 To listview1.items.count-1 do begin
r:= listview1.items[i].DisplayRect( drBounds );
// last column is to take progress bar
for k:= 1 to listview1.columns.Count -1 do
r.left := r.left + listview1.columns[k-1].Width;
r.right := r.Left +
listview1.columns[listview1.columns.Count -1].Width;
pb:= TProgressBar.Create( self );
pb.Parent := listview1;
pb.BoundsRect := r;
listview1.items[i].Data := pb;
end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
i: Integer;
pb: TProgressbar;
begin
i:= Random( listview1.items.count );
pb:= TProgressBar( listview1.Items[i].Data );
If assigned(pb) then
if pb.Position = pb.Max Then
pb.Position := 0
else
pb.StepBy( pb.Max div 10 );
end;

Peter Below (TeamB) 10011...@compuserve.com)
No e-mail responses, please, unless explicitly requested!
Note: I'm unable to visit the newsgroups every day at the moment,
so be patient if you don't get a reply immediately.

Jerry Cohen

unread,
Apr 6, 2001, 8:21:12 PM4/6/01
to
How does one know when column widths are changed? There is no event for
this. An OnCustomDraw event is triggered, but the new column widths haven't
been stored yet.

Thanks.

"Peter Below (TeamB)" <10011...@compuXXserve.com> wrote in message
news:VA.00006d4...@antispam.compuserve.com...

Peter Below (TeamB)

unread,
Apr 7, 2001, 1:52:21 PM4/7/01
to
In article <3ace5d38_1@dnews>, Jerry Cohen wrote:
> How does one know when column widths are changed? There is no event for
> this. An OnCustomDraw event is triggered, but the new column widths haven't
> been stored yet.

The event can be added with a bit of work. See the custom listview derivative
below. In my tests the OnBeginColumnResize event was not fired (Win95B,
ComCtl32 version 5.8) for some reason but the OnEndColumnResize event does
fire.

unit PBExListview;

interface

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

type
TLVColumnResizeEvent = Procedure( sender: TCustomListview;
columnindex: Integer ) of Object;
TPBExListview = class(TListview)
private
FBeginColumnResizeEvent: TLVColumnResizeEvent;
FEndColumnResizeEvent : TLVColumnResizeEvent;

Procedure DoBeginColumnResize( columnindex: Integer ); virtual;
Procedure DoEndColumnResize( columnindex: Integer ); virtual;
protected
Procedure WMNotify( Var msg: TWMNotify ); message WM_NOTIFY;
Function FindColumnIndex( pHeader: pNMHdr ): Integer;
published
property OnBeginColumnResize: TLVColumnResizeEvent
read FBeginColumnResizeEvent
write FBeginColumnResizeEvent;
property OnEndColumnResize: TLVColumnResizeEvent
read FEndColumnResizeEvent
write FEndColumnResizeEvent;
end;

procedure Register;

implementation
uses CommCtrl;

procedure Register;
begin
RegisterComponents('PBGoodies', [TPBExListview]);
end;

procedure TPBExListview.DoBeginColumnResize( columnindex: Integer );
begin
If Assigned( FBeginColumnResizeEvent ) Then
FBeginColumnResizeEvent( self, columnindex );
end;

procedure TPBExListview.DoEndColumnResize( columnindex: Integer );
begin
If Assigned( FEndColumnResizeEvent ) Then
FEndColumnResizeEvent( self, columnindex );
end;

function TPBExListview.FindColumnIndex(pHeader: pNMHdr): Integer;
var
hwndHeader: HWND;
iteminfo: THdItem;
itemindex: Integer;
buf: Array [0..128] of Char;
begin
Result := -1;
hwndHeader := pHeader^.hwndFrom;
itemindex := pHDNotify( pHeader )^.Item;
FillChar( iteminfo, sizeof( iteminfo ), 0);
iteminfo.Mask := HDI_TEXT;
iteminfo.pszText := buf;
iteminfo.cchTextMax := sizeof(buf)-1;
Header_GetItem( hwndHeader, itemindex, iteminfo );
If CompareStr( Columns[itemindex].Caption, iteminfo.pszText ) = 0 Then
Result := itemindex
Else Begin
For itemindex := 0 to Columns.count-1 Do
If CompareStr( Columns[itemindex].Caption, iteminfo.pszText ) = 0 Then
Begin
Result := itemindex;
Break;
End;
End;
end;

procedure TPBExListview.WMNotify(var msg: TWMNotify);
begin
inherited;
Case msg.NMHdr^.code Of
HDN_ENDTRACK: DoEndColumnResize( FindColumnIndex( msg.NMHdr ));
HDN_BEGINTRACK: DoBeginColumnResize( FindColumnIndex( msg.NMHdr ));
End;
end;

end.

Jerry Cohen

unread,
Apr 7, 2001, 5:27:15 PM4/7/01
to
Thank you Peter. I've noticed that sometimes when I am in the process of
resizing the column containing the ProgressBar, the ProgressBar updates
incorrectly until after I've let go of the mouse button (my
OnEndColumnResize event gets triggered so it's drawn correctly then). Is
there a way of "freezing" the ProgressBar updates until after the mouse
button is let go?

Jerry

"Peter Below (TeamB)" <10011...@compuXXserve.com> wrote in message

news:VA.00006d5...@antispam.compuserve.com...

Peter Below (TeamB)

unread,
Apr 8, 2001, 7:07:05 AM4/8/01
to
In article <3acf85f3$1_2@dnews>, Jerry Cohen wrote:
> Thank you Peter. I've noticed that sometimes when I am in the process of
> resizing the column containing the ProgressBar, the ProgressBar updates
> incorrectly until after I've let go of the mouse button (my
> OnEndColumnResize event gets triggered so it's drawn correctly then). Is
> there a way of "freezing" the ProgressBar updates until after the mouse
> button is let go?

You could check if the mouse button is down (GetKeyState( VK_LBUTTON ) < 0) in
the routine that updates the progress bar position. If it is down check where
the mouse is (GetCursorPos) and do not update the bar if the mouse is over the
listview.

Jerry Cohen

unread,
Apr 13, 2001, 1:29:16 AM4/13/01
to
What routine that update the progress bar position? My routine only gets
executed when the mouse is let up.

"Peter Below (TeamB)" <10011...@compuXXserve.com> wrote in message

news:VA.00006d6...@antispam.compuserve.com...

Peter Below (TeamB)

unread,
Apr 13, 2001, 6:18:29 AM4/13/01
to
In article <3ad68e6c_1@dnews>, Jerry Cohen wrote:
> What routine that update the progress bar position?
>

You tell me, you are updating it, not I <g>. If the bar is updated from
a timer for instance it might be updated while a column resize is in
progress.

Jerry Cohen

unread,
Apr 14, 2001, 2:23:16 AM4/14/01
to
Peter, I don't update it until the mouse is let go (this is the only event I
was able to capture). My code does not get called DURING a column resize.
The TListView itself tries to update it during a resize and does a terrible
job. So I was wondering if I could just freeze it or something until the
mouse is let go (but on second thought maybe this isn't such a good idea
becuase if the column before it is being updated it will need to be moved to
the right). What I would really like to do is make the progress bar update
correctly DURING a column resize (like it does in Napster).

"Peter Below (TeamB)" <10011...@compuXXserve.com> wrote in message

news:VA.00006db...@antispam.compuserve.com...

Peter Below (TeamB)

unread,
Apr 16, 2001, 4:33:16 PM4/16/01
to
In article <3ad7ec8d$1_1@dnews>, Jerry Cohen wrote:
> Peter, I don't update it until the mouse is let go (this is the only event I
> was able to capture). My code does not get called DURING a column resize.
> The TListView itself tries to update it during a resize and does a terrible
> job. So I was wondering if I could just freeze it or something until the
> mouse is let go

That should be doable. There is a special message, WM_SETREDRAW, that can be
send to a control to disable or enable the automatic redrawing. See win32.hlp.
Not that you have to manually invalidate the control to force a redraw after
enabling redraing again, this is not automatic.

(but on second thought maybe this isn't such a good idea
> becuase if the column before it is being updated it will need to be moved to
> the right). What I would really like to do is make the progress bar update
> correctly DURING a column resize (like it does in Napster).

Mmh, for that one would need to handle one additional notification from the
header control: HDN_TRACK. This one is send to the listview on each move of
the divider line the user is dragging. TCustomListview handles this
notification to update the column width, so you can just call the inherited
handler first and then access the Columns[i].Width to adjust the progress bar
width.

Jerry Cohen

unread,
Apr 17, 2001, 1:36:46 AM4/17/01
to
I've tried to capture the HDN_TRACK notification in a WMNotify event but it
doesn't work!? How do is this done? Thanks!!!

"Peter Below (TeamB)" <10011...@compuXXserve.com> wrote in message

news:VA.00006dc...@antispam.compuserve.com...

Peter Below (TeamB)

unread,
Apr 17, 2001, 2:24:01 PM4/17/01
to
In article <3adbd634$1_1@dnews>, Jerry Cohen wrote:
> I've tried to capture the HDN_TRACK notification in a WMNotify event but it
> doesn't work!? How do is this done? Thanks!!!

Using the example listview descendent i posted near the start of this thread
you would just extend the WMNotify method:

procedure TPBExListview.WMNotify(var msg: TWMNotify);
begin
inherited;
Case msg.NMHdr^.code Of
HDN_ENDTRACK: DoEndColumnResize( FindColumnIndex( msg.NMHdr ));
HDN_BEGINTRACK: DoBeginColumnResize( FindColumnIndex( msg.NMHdr ));

HDN_TRACK: DoUpdateColumn( FindColumnIndex( msg.NMHdr ));
End;
end;

The (new) DoUpdateColumn method would perhaps fire a new event to allow the
listviews user to react to the column sizing.

Jerry Cohen

unread,
Apr 17, 2001, 7:56:39 PM4/17/01
to
Nope, doesn't work, at least not on my system - Win 2k. The only one of the
three that ever gets called is endtrack.

"Peter Below (TeamB)" <10011...@compuXXserve.com> wrote in message

news:VA.00006dd...@antispam.compuserve.com...

Peter Below (TeamB)

unread,
Apr 18, 2001, 4:00:19 PM4/18/01
to
In article <3adcd800$1_2@dnews>, Jerry Cohen wrote:
> Nope, doesn't work, at least not on my system - Win 2k. The only one of the
> three that ever gets called is endtrack.
>

Pity. Sue Bill Gates, it's his listview <g>.

I found a MSKB article that is relevant to the problem, i think:

INFO: HDN_TRACK Notifications and Full Window Drag Style
ID: Q183258

Go read it on msdn.microsoft.com. You should be able to fix the problem by
doing this on the listview, e.g. in the forms OnCreate method.

var
wnd: HWND;


wnd:= GetWindow( listview1.handle, GW_CHILD );
SetWindowLong( wnd, GWL_STYLE,
GetWindowLong( wnd, GWL_STYLE ) and not HDS_FULLDRAG );

HDS_FULLDRAG should be declared in unit CommCtrl.

Oh, a better way to do this would be to override the CreateWnd method of the
listview derivative and change the style there after the inherited call.
The drawback is that you can no longer reorder the columns via drag&drop if
the HDS_FULLDRAG style is not set. I have modified the TPBExListview class
accordingly and it works in my system:

unit PBExListview;

interface

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

type
TLVColumnResizeEvent = Procedure( sender: TCustomListview;
columnindex: Integer ) of Object;
TPBExListview = class(TListview)
private
FBeginColumnResizeEvent: TLVColumnResizeEvent;
FEndColumnResizeEvent : TLVColumnResizeEvent;

FColumnResizeEvent : TLVColumnResizeEvent;

Procedure DoBeginColumnResize( columnindex: Integer ); virtual;
Procedure DoEndColumnResize( columnindex: Integer ); virtual;

Procedure DoColumnResize( columnindex: Integer ); virtual;


protected
Procedure WMNotify( Var msg: TWMNotify ); message WM_NOTIFY;
Function FindColumnIndex( pHeader: pNMHdr ): Integer;

Procedure CreateWnd; override;


published
property OnBeginColumnResize: TLVColumnResizeEvent
read FBeginColumnResizeEvent
write FBeginColumnResizeEvent;
property OnEndColumnResize: TLVColumnResizeEvent
read FEndColumnResizeEvent
write FEndColumnResizeEvent;

property OnColumnResize: TLVColumnResizeEvent
read FColumnResizeEvent
write FColumnResizeEvent;
end;

procedure Register;

implementation
uses CommCtrl;

procedure Register;
begin
RegisterComponents('PBGoodies', [TPBExListview]);
end;

procedure TPBExListview.DoBeginColumnResize( columnindex: Integer );
begin
If Assigned( FBeginColumnResizeEvent ) Then
FBeginColumnResizeEvent( self, columnindex );
end;

procedure TPBExListview.DoEndColumnResize( columnindex: Integer );
begin
If Assigned( FEndColumnResizeEvent ) Then
FEndColumnResizeEvent( self, columnindex );
end;

procedure TPBExListview.DoColumnResize( columnindex: Integer );
begin
If Assigned( FColumnResizeEvent ) Then
FColumnResizeEvent( self, columnindex );
end;

procedure TPBExListview.WMNotify(var msg: TWMNotify);


begin
inherited;
Case msg.NMHdr^.code Of
HDN_ENDTRACK: DoEndColumnResize( FindColumnIndex( msg.NMHdr ));
HDN_BEGINTRACK: DoBeginColumnResize( FindColumnIndex( msg.NMHdr ));

HDN_TRACK: DoColumnResize( FindColumnIndex( msg.NMHdr ));
End;
end;

Procedure TPBExListview.CreateWnd;
var
wnd: HWND;
begin
inherited;
wnd:= GetWindow( handle, GW_CHILD );
SetWindowLong( wnd, GWL_STYLE,
GetWindowLong( wnd, GWL_STYLE ) and not HDS_FULLDRAG );

end;


end.

Jerry Cohen

unread,
Apr 18, 2001, 7:08:34 PM4/18/01
to
Peter, this works, but I now have a new problem. Column sizes in a listview
aren't set until the hdn_endtrack. Do you know of a way to get column sizes
during an hdn_track?

Thanks, Jerry

"Peter Below (TeamB)" <10011...@compuXXserve.com> wrote in message

news:VA.00006de...@antispam.compuserve.com...

Jerry Cohen

unread,
Apr 18, 2001, 6:57:38 PM4/18/01
to
Peter, you're the greatest.

"Peter Below (TeamB)" <10011...@compuXXserve.com> wrote in message

news:VA.00006de...@antispam.compuserve.com...

Peter Below (TeamB)

unread,
Apr 19, 2001, 3:21:37 PM4/19/01
to
In article <3ade1e3c$1_1@dnews>, Jerry Cohen wrote:
> Peter, this works, but I now have a new problem. Column sizes in a listview
> aren't set until the hdn_endtrack. Do you know of a way to get column sizes
> during an hdn_track?
>
Jerry,

you are going to need asbestos underwear soon if you don't stop this wholesale
quoting of lengthy posts!

The HDN_TRACK notification comes with a record that contains information about
the dragged column. Or you can send directly query the header control about
the column size using the item index that comes with the message.
Header_GetItemRect would be the easiest way to do that.

In analogy to TPBExListview.FindColumnIndex one could add a method

function TPBExListview.FindColumnWidth(pHeader: pNMHdr): Integer;
var
hwndHeader: HWND;
itemindex: Integer;
rect: TRect;


begin
Result := -1;
hwndHeader := pHeader^.hwndFrom;
itemindex := pHDNotify( pHeader )^.Item;

Header_GetItemRect( hwndHeader, itemindex, @rect );
Result := rect.right - rect.left;
end;

Jerry Cohen

unread,
Apr 19, 2001, 8:34:34 PM4/19/01
to
Asbestos underwear?! Hmm... hey wait a second, I'm not that dumb, it could
give me cancer!

"Peter Below (TeamB)" <10011...@compuXXserve.com> wrote in message

news:VA.00006e0...@antispam.compuserve.com...

Jerry Cohen

unread,
Apr 19, 2001, 10:48:29 PM4/19/01
to
Peter did you try this code? On my system it does not return the width of a
column during an hdn_track (while the mouse is down the width is constant).
It returns the initial width of the column until the mouse is released, then
it returns the correct column width. In other words, it doesn't seem to be
working any differently than the columns.width property of the control.

"Peter Below (TeamB)" <10011...@compuXXserve.com> wrote in message

news:VA.00006e0...@antispam.compuserve.com...

Peter Below (TeamB)

unread,
Apr 20, 2001, 5:07:20 PM4/20/01
to
In article <3adfa348_2@dnews>, Jerry Cohen wrote:
> Peter did you try this code?
>

No, i leave the hard work to you <g>.

> In other words, it doesn't seem to be
> working any differently than the columns.width property of
> the control.

@#*%%!! OK, one last chance: check if the notification record that
comes with the HDN_TRACK notification contains a valid pointer to a
HD_ITEM record. If so this record may contain more up-to-date
information. The docs are not clear about whether the pointer is
supposed to be valid for this notification. Lets see, that would look
like this:

function TPBExListview.FindColumnWidth(pHeader: pNMHdr): Integer;
begin
Result := -1;
If Assigned( PHDNotify( pHeader )^.pItem ) AND
((PHDNotify( pHeader )^.pItem^.mask and HDI_WIDTH) <> 0 )
Then
Result := PHDNotify( pHeader )^.pItem^.cxy;
end;

Go test that. Oh, and you are *still* quoting full messages...

Jerry Cohen

unread,
Apr 20, 2001, 8:08:53 PM4/20/01
to
Peter you won't believe it, but it works. Not a bad week's worth of work.
The check is in the mail. :) (And sorry about the resending of prior
messages)

Peter Below (TeamB)

unread,
Apr 21, 2001, 8:42:04 AM4/21/01
to
In article <3ae0cf62$1_1@dnews>, Jerry Cohen wrote:
> Peter you won't believe it, but it works.
>

Will wonders never cease <g>. OK, for the benefit of all you lurkers
who've followed this thread with bated breath, here is the final
version of the TPBExListview control:

unit PBExListview;

interface

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

type
TLVColumnResizeEvent = Procedure( sender: TCustomListview;

columnindex: Integer;
columnwidth: Integer ) of Object;


TPBExListview = class(TListview)
private
FBeginColumnResizeEvent: TLVColumnResizeEvent;
FEndColumnResizeEvent : TLVColumnResizeEvent;
FColumnResizeEvent : TLVColumnResizeEvent;

protected
Procedure DoBeginColumnResize( columnindex, columnwidth: Integer );
virtual;
Procedure DoEndColumnResize( columnindex, columnwidth: Integer );
virtual;
Procedure DoColumnResize( columnindex, columnwidth: Integer );
virtual;


Procedure WMNotify( Var msg: TWMNotify ); message WM_NOTIFY;
Function FindColumnIndex( pHeader: pNMHdr ): Integer;

function FindColumnWidth(pHeader: pNMHdr): Integer;


Procedure CreateWnd; override;
published
property OnBeginColumnResize: TLVColumnResizeEvent
read FBeginColumnResizeEvent
write FBeginColumnResizeEvent;
property OnEndColumnResize: TLVColumnResizeEvent
read FEndColumnResizeEvent
write FEndColumnResizeEvent;
property OnColumnResize: TLVColumnResizeEvent
read FColumnResizeEvent
write FColumnResizeEvent;
end;

procedure Register;

implementation
uses CommCtrl;

procedure Register;
begin
RegisterComponents('PBGoodies', [TPBExListview]);
end;

procedure TPBExListview.DoBeginColumnResize( columnindex, columnwidth:

Integer );
begin
If Assigned( FBeginColumnResizeEvent ) Then

FBeginColumnResizeEvent( self, columnindex, columnwidth );
end;

procedure TPBExListview.DoEndColumnResize( columnindex, columnwidth:

Integer );
begin
If Assigned( FEndColumnResizeEvent ) Then

FEndColumnResizeEvent( self, columnindex, columnwidth );
end;

procedure TPBExListview.DoColumnResize( columnindex, columnwidth:

Integer );
begin
If Assigned( FColumnResizeEvent ) Then

FColumnResizeEvent( self, columnindex, columnwidth );
end;

DoEndColumnResize( FindColumnIndex( msg.NMHdr ),
FindColumnWidth( msg.NMHdr ));
HDN_BEGINTRACK:
DoBeginColumnResize( FindColumnIndex( msg.NMHdr ),
FindColumnWidth( msg.NMHdr ));
HDN_TRACK:
DoColumnResize( FindColumnIndex( msg.NMHdr ),
FindColumnWidth( msg.NMHdr ));
End;
end;

Procedure TPBExListview.CreateWnd;
var
wnd: HWND;
begin
inherited;
wnd:= GetWindow( handle, GW_CHILD );
SetWindowLong( wnd, GWL_STYLE,
GetWindowLong( wnd, GWL_STYLE ) and not HDS_FULLDRAG
);

end;

function TPBExListview.FindColumnWidth(pHeader: pNMHdr): Integer;


begin
Result := -1;
If Assigned( PHDNotify( pHeader )^.pItem ) AND
((PHDNotify( pHeader )^.pItem^.mask and HDI_WIDTH) <> 0 )
Then
Result := PHDNotify( pHeader )^.pItem^.cxy;

0 new messages