Article View: borland.public.cppbuilder.nativeapi
Article #3719Sizing ListView Column when HScrollPos > 0
From: JD
Date: Sat, 29 Apr 2006 10:34
Date: Sat, 29 Apr 2006 10:34
544 lines
22512 bytes
22512 bytes
Sizing a ListView Column when the Horizontal ScrollPos is
greater than zero exposes a bug with the underlying MS control.
This bug can be readily observed in MS Windows Explorer:
1) Launch Windows Explorer.
2) Size the columns until the Horizontal ScrollBar appears
and has a repectable scrolling range.
3) Scroll to the maximum Horizontal position.
4) Reduce the size of any column and observe.
What you should notice is that instead of the size decreasing
on a pixel per pixel basis as you move the mouse, the size is
decreased exponentially with every pixel move and I haven't
been able to find a solution to the problem.
The sample code has been trimed dowm to the minimum that
demonstrates the problem and it's ready to be cut-n-pasted.
I've isolated the place in the code where the fix needs to be
inserted and moved it to the bottom of the sample where it's
clearly noted in the HeaderMouseMove method. Please keep in
mind that this is just a test-bed so don't be too critical
about other parts of the code.
#include "GradientListView.h"
//--------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)
{
new TGradientListView( this );
}
//--------------------------------------------------------------
//--------------------------------------------------------------
#ifndef GradientListViewH
#define GradientListViewH
//--------------------------------------------------------------
#include <Classes.hpp>
#include <ComCtrls.hpp>
//--------------------------------------------------------------
#ifdef STRICT
#define HEADERWNDPROC WNDPROC
#else
#define HEADERWNDPROC FARPROC
#endif
//--------------------------------------------------------------
typedef BOOL WINAPI (*DLLGRADIENTFILL)( HDC, PTRIVERTEX, ULONG, PVOID, ULONG, ULONG );
//--------------------------------------------------------------
enum THeaderState
{
glvNormal,
glvPressed,
glvHot,
HeaderStateCount
};
//--------------------------------------------------------------
class TGradientListView : public TListView
{
typedef TListView inherited;
protected:
private:
HINSTANCE hMSImg32;
DLLGRADIENTFILL GradientFill;
Graphics::TBitmap *HeaderBmp[ HeaderStateCount ];
TColor FHeaderStartColor, FHeaderEndColor;
void __fastcall GradientFillObject( HDC hDC, TColor StartColor, TColor EndColor, int Width, int Height, unsigned long Mode, int Factor );
void __fastcall AssignRGBToV( TColor Color, TRIVERTEX *V );
int FClickedColumn;
int FHeaderColumn;
bool FMouseInHeader;
bool FHeaderMouseDown;
bool FSizingColumn;
int FSplitterAreaWidth;
TPoint FHeaderClickPoint;
TCursor HeaderCursor;
HWND hHeader;
LONG HeaderWndProcPtr;
HEADERWNDPROC OldHeaderWndProc;
void __fastcall SubclassHeaderWndProc();
void __fastcall HeaderBoundsChanged();
void __fastcall HeaderWndProc( TMessage &Message );
void __fastcall HeaderMouseDown( TMouseButton Button, TShiftState Shift, int X, int Y );
void __fastcall HeaderMouseMove( TShiftState Shift, short X, short Y );
void __fastcall HeaderMouseEnter( TPoint P );
void __fastcall HeaderMouseLeave();
void __fastcall HeaderMouseUp( TMouseButton Button, TShiftState Shift, int X, int Y );
int __fastcall HeaderColumn( TPoint P );
void __fastcall PaintHeaderColumn( int AColumn, THeaderState State );
void __fastcall PaintHeader();
THeaderState __fastcall HeaderColumnState( int Column, TRect ARect );
MESSAGE void __fastcall CMRecreateWnd( TMessage &Message );
MESSAGE void __fastcall WMBoundsChange( TMessage &Message );
public:
BEGIN_MESSAGE_MAP
VCL_MESSAGE_HANDLER( CM_RECREATEWND, TMessage, CMRecreateWnd )
VCL_MESSAGE_HANDLER( WM_WINDOWPOSCHANGED, TMessage, WMBoundsChange )
END_MESSAGE_MAP( inherited )
public:
__fastcall TGradientListView( TComponent* Owner );
__fastcall ~TGradientListView();
__published:
};
//--------------------------------------------------------------
#endif
//--------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "GradientListView.h"
#include "shlwapi.h"
//--------------------------------------------------------------
#pragma package(smart_init)
//--------------------------------------------------------------
__fastcall TGradientListView::TGradientListView(TComponent* Owner) : TListView( Owner )
{
Parent = dynamic_cast<TWinControl*>( Owner );
Align = alClient;
ShowColumnHeaders = true;
ViewStyle = vsReport;
TListColumn *pColumn;
for( int x = 0; x < 3; ++x )
{
pColumn = Columns->Add();
pColumn->Caption = "Column " + IntToStr(x);
pColumn->Width = (Width - 20) / 3;
}
TListItem *pItem;
for( int x = 0; x < 3; ++x )
{
pItem = Items->Add();
pItem->Caption = "Col " + IntToStr(0) + " ,Row " + IntToStr(x);
for( int y = 0; y < Columns->Count; ++y )
{
pItem->SubItems->Add("Col " + IntToStr(y+1) + " ,Row " + IntToStr(x));
}
}
HeaderWndProcPtr = reinterpret_cast<LONG>( MakeObjectInstance(HeaderWndProc) );
SubclassHeaderWndProc();
hMSImg32 = ::LoadLibrary( "msimg32.dll" );
if( hMSImg32 ) GradientFill = reinterpret_cast<DLLGRADIENTFILL>( ::GetProcAddress(hMSImg32, "GradientFill") );
for( int x = 0; x < HeaderStateCount; ++x )
{
HeaderBmp[ x ] = NULL;
}
FHeaderStartColor = clWhite;
FHeaderEndColor = clBlack;
FSplitterAreaWidth = 8;
FClickedColumn = -1;
FHeaderColumn = -1;
FMouseInHeader = false;
FHeaderMouseDown = false;
FHeaderClickPoint = Point( -1, -1 );
HeaderCursor = crArrow;
}
//--------------------------------------------------------------
__fastcall TGradientListView::~TGradientListView()
{
if( hMSImg32 ) ::FreeLibrary( hMSImg32 );
for( int x = 0; x < HeaderStateCount; ++x )
{
if( HeaderBmp[x] )
{
delete HeaderBmp[x];
}
}
if( hHeader ) ::SetWindowLong( hHeader, GWL_WNDPROC, reinterpret_cast<LONG>(OldHeaderWndProc) );
FreeObjectInstance( reinterpret_cast<void*>(HeaderWndProcPtr) );
}
//--------------------------------------------------------------
MESSAGE void __fastcall TGradientListView::WMBoundsChange( TMessage &Message )
{
inherited::Dispatch( &Message );
HeaderBoundsChanged();
}
//--------------------------------------------------------------
void __fastcall TGradientListView::HeaderBoundsChanged()
{
for( int x = 0; x < HeaderStateCount; ++x )
{
if( !HeaderBmp[x] )
{
HeaderBmp[x] = new Graphics::TBitmap();
HeaderBmp[x]->PixelFormat = pf32bit;
HeaderBmp[x]->Canvas->Brush->Style = bsClear; // Reported to speed-up bitmap operations
}
}
int w = 0; for( int x = 0; x < Columns->Count; ++x ) w += Columns->Items[x]->Width;
if( w < ClientWidth ) w = ClientWidth;
if( (HeaderBmp[0]->Width < w) || (HeaderBmp[0]->Width > (w - 10)) )
{
TRect R;
::GetWindowRect( ListView_GetHeader(Handle), &R );
for( int x = 0; x < HeaderStateCount; ++x )
{
HeaderBmp[x]->Width = w;
HeaderBmp[x]->Height = R.Bottom - R.Top;
switch( x )
{
case glvHot: GradientFillObject( HeaderBmp[x]->Canvas->Handle, FHeaderStartColor, FHeaderEndColor, HeaderBmp[x]->Width, HeaderBmp[x]->Height, GRADIENT_FILL_RECT_V, 4 ); break;
case glvNormal: GradientFillObject( HeaderBmp[x]->Canvas->Handle, FHeaderStartColor, FHeaderEndColor, HeaderBmp[x]->Width, HeaderBmp[x]->Height, GRADIENT_FILL_RECT_V, 2 ); break;
case glvPressed: GradientFillObject( HeaderBmp[x]->Canvas->Handle, FHeaderEndColor, FHeaderStartColor, HeaderBmp[x]->Width, HeaderBmp[x]->Height, GRADIENT_FILL_RECT_V, 1 ); break;
}
}
}
}
//--------------------------------------------------------------
void __fastcall TGradientListView::GradientFillObject( HDC hDC, TColor StartColor, TColor EndColor, int Width, int Height, unsigned long Mode, int Factor )
{
void *pMesh;
int NumberOfVertices, FFactor;
TRIVERTEX Vertices[3];
GRADIENT_RECT GradientRect;
GRADIENT_TRIANGLE GradientTriangle;
memset( &Vertices, 0, sizeof(TRIVERTEX) * 3 );
AssignRGBToV( StartColor, &Vertices[0] );
AssignRGBToV( EndColor, &Vertices[1] );
if( Mode == GRADIENT_FILL_TRIANGLE )
{
Vertices[ 2 ] = Vertices[ 1 ];
Vertices[ 1 ].y = (Height - 1) * Factor * 2;
Vertices[ 2 ].x = (Width - 1) * Factor * 2;
GradientTriangle.Vertex1 = 0;
GradientTriangle.Vertex2 = 1;
GradientTriangle.Vertex3 = 2;
pMesh = &GradientTriangle;
NumberOfVertices = 3;
}
else
{
Vertices[ 1 ].x = (Width - 1) * Factor;
Vertices[ 1 ].y = (Height - 1) * Factor;
GradientRect.UpperLeft = 0;
GradientRect.LowerRight = 1;
pMesh = &GradientRect;
NumberOfVertices = 2;
}
GradientFill( hDC, (TRIVERTEX*)&Vertices, NumberOfVertices, pMesh, 1, Mode );
}
//--------------------------------------------------------------
void __fastcall TGradientListView::AssignRGBToV( TColor Color, TRIVERTEX *V )
{
int C = ColorToRGB( Color );
V->Red = ( GetRValue(C) << 8 );
V->Green = ( GetGValue(C) << 8 );
V->Blue = ( GetBValue(C) << 8 );
}
//--------------------------------------------------------------
MESSAGE void __fastcall TGradientListView::CMRecreateWnd( TMessage &Message )
{
inherited::Dispatch( &Message );
SubclassHeaderWndProc();
}
//--------------------------------------------------------------
void __fastcall TGradientListView::SubclassHeaderWndProc()
{
hHeader = ListView_GetHeader( Handle );
if( hHeader ) OldHeaderWndProc = reinterpret_cast<HEADERWNDPROC>( ::SetWindowLong(hHeader, GWL_WNDPROC, HeaderWndProcPtr) );
}
//--------------------------------------------------------------
void __fastcall TGradientListView::HeaderWndProc( TMessage &Message )
{
switch( Message.Msg )
{
case WM_PAINT: if( ::GetUpdateRect(hHeader, NULL, false) )
{
PaintHeader();
Message.Result = 0;
}
break;
case WM_ERASEBKGND: Message.Result = -1; break;
case WM_LBUTTONDOWN: HeaderMouseDown( mbLeft, KeysToShiftState(Message.WParam), Message.LParamLo, Message.LParamHi ); Message.Result = 0; break;
case WM_MOUSEMOVE: HeaderMouseMove( KeysToShiftState(Message.WParam), LOWORD(Message.LParam), HIWORD(Message.LParam) ); Message.Result = 0; break;
case WM_MOUSELEAVE: HeaderMouseLeave(); break;
case WM_LBUTTONUP: HeaderMouseUp( mbLeft, KeysToShiftState(Message.WParam), LOWORD(Message.LParam), HIWORD(Message.LParam) ); Message.Result = 0; break;
case WM_SETCURSOR: ::SetCursor( CopyCursor(Screen->Cursors[HeaderCursor]) ); Message.Result = 0; break;
case WM_DESTROY: Message.Result = ::CallWindowProc(OldHeaderWndProc, hHeader, Message.Msg, Message.WParam, Message.LParam );
::SetWindowLong( hHeader, GWL_WNDPROC, reinterpret_cast<LONG>(OldHeaderWndProc) );
hHeader = NULL;
OldHeaderWndProc = NULL;
break;
default: Message.Result = ::CallWindowProc( OldHeaderWndProc, hHeader, Message.Msg, Message.WParam, Message.LParam );
}
}
//--------------------------------------------------------------
void __fastcall TGradientListView::HeaderMouseDown( TMouseButton Button, TShiftState Shift, int X, int Y )
{
FClickedColumn = -1;
FSizingColumn = false;
if( Button == mbLeft )
{
::SetCapture( hHeader );
if( FHeaderColumn > -1 )
{
if( HeaderCursor == crHSplit ) FSizingColumn = true;
else PaintHeaderColumn( FHeaderColumn, glvPressed );
}
FClickedColumn = FHeaderColumn;
FHeaderClickPoint = Point( X, Y );
FHeaderMouseDown = true;
}
}
//--------------------------------------------------------------
void __fastcall TGradientListView::HeaderMouseEnter( TPoint P )
{
FHeaderColumn = HeaderColumn( P );
if( FHeaderColumn > -1 )
{
if( FHeaderMouseDown && FHeaderColumn == FClickedColumn && HeaderCursor == crArrow ) PaintHeaderColumn( FHeaderColumn, glvPressed );
else PaintHeaderColumn( FHeaderColumn, glvHot );
}
FMouseInHeader = true;
TRACKMOUSEEVENT tme = { 0 };
tme.cbSize = sizeof( TRACKMOUSEEVENT );
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = hHeader;
TrackMouseEvent( &tme );
}
//--------------------------------------------------------------
void __fastcall TGradientListView::HeaderMouseLeave()
{
if( FHeaderColumn > -1 ) PaintHeaderColumn( FHeaderColumn, glvNormal );
FMouseInHeader = false;
FHeaderColumn = -1;
}
//--------------------------------------------------------------
void __fastcall TGradientListView::HeaderMouseUp( TMouseButton Button, TShiftState Shift, int X, int Y )
{
if( Button == mbLeft )
{
::ReleaseCapture();
if( FHeaderColumn > -1 )
{
int AColumn = HeaderColumn( Point(X,Y) );
if( AColumn != FHeaderColumn ) PaintHeaderColumn( FHeaderColumn, glvNormal );
else PaintHeaderColumn( FHeaderColumn, glvHot );
if( FHeaderColumn > -1 && FHeaderColumn == FClickedColumn && !FSizingColumn )
{
ShowMessage("Column Header Click " + IntToStr(FHeaderColumn) );
}
FHeaderColumn = AColumn;
}
}
FHeaderMouseDown = false;
FClickedColumn = -1;
FSizingColumn = false;
FHeaderClickPoint = Point( -1, -1 );
}
//--------------------------------------------------------------
int __fastcall TGradientListView::HeaderColumn( TPoint P )
{
TRect R;
HeaderCursor = crArrow;
for( int AColumn = 0; AColumn < Columns->Count; ++AColumn )
{
::SendMessage( hHeader, HDM_GETITEMRECT, (WPARAM)AColumn, (LPARAM)&R );
if( PtInRect(R,P) )
{
if( AColumn > 0 && Columns->Items[AColumn - 1]->Width == 0 )
{
R.right = R.left + FSplitterAreaWidth;
if( PtInRect(R,P) )
{
HeaderCursor = crHSplit;
return AColumn - 1;
}
}
else
{
R.left = R.right - FSplitterAreaWidth;
if( PtInRect(R,P) ) HeaderCursor = crHSplit;
}
return AColumn;
}
}
if( Columns->Items[Columns->Count - 1]->Width == 0 )
{
R.right = R.left + FSplitterAreaWidth;
if( PtInRect(R,P) )
{
HeaderCursor = crHSplit;
return Columns->Count - 1;
}
}
return -1;
}
//--------------------------------------------------------------
THeaderState __fastcall TGradientListView::HeaderColumnState( int AColumn, TRect ARect )
{
TPoint P;
::GetCursorPos( &P );
::ScreenToClient( hHeader, &P );
if( HeaderCursor == crHSplit )
if( AColumn == FHeaderColumn ) return glvHot;
else return glvNormal;
else if( FHeaderMouseDown )
if( FClickedColumn == AColumn )
if( PtInRect(ARect,P) ) return glvPressed;
else return glvNormal;
else return glvNormal;
else if( PtInRect(ARect,P) ) return glvHot;
else return glvNormal;
}
//--------------------------------------------------------------
void __fastcall TGradientListView::PaintHeaderColumn( int AColumn, THeaderState State )
{
TRect R;
HDC hDC = ::GetDC( hHeader );
TCanvas *pCanvas = new TCanvas;
pCanvas->Handle = hDC;
if( AColumn < 0 )
{
::SendMessage( hHeader, HDM_GETITEMRECT, (WPARAM)(Columns->Count - 1), (LPARAM)&R );
R.left = R.right;
R.Right = ClientWidth;
}
else ::SendMessage( hHeader, HDM_GETITEMRECT, (WPARAM)AColumn, (LPARAM)&R );
pCanvas->CopyRect( R, HeaderBmp[State]->Canvas, R );
--R.bottom;
switch( State )
{
case glvHot: Frame3D( pCanvas, R, FHeaderStartColor, FHeaderEndColor, 1 ); break;
case glvNormal: Frame3D( pCanvas, R, FHeaderStartColor, FHeaderEndColor, 1 ); break;
case glvPressed: Frame3D( pCanvas, R, FHeaderEndColor, FHeaderStartColor, 1 ); break;
}
if( AColumn > -1 )
{
R.left += 5;
int PreviouseMode = ::SetBkMode( pCanvas->Handle, TRANSPARENT );
::DrawText( pCanvas->Handle, Columns->Items[AColumn]->Caption.c_str(), -1, &R, DT_SINGLELINE | DT_VCENTER | DT_LEFT );
::SetBkMode( pCanvas->Handle, PreviouseMode );
}
::ReleaseDC( hHeader, hDC );
pCanvas->Handle = NULL;
delete pCanvas;
}
//--------------------------------------------------------------
void __fastcall TGradientListView::PaintHeader()
{
TRect R, R1;
PAINTSTRUCT ps = { 0 };
::BeginPaint( hHeader, &ps );
::ValidateRect( hHeader, &ps.rcPaint );
for( int AColumn = 0; AColumn < Columns->Count; ++AColumn )
{
::SendMessage( hHeader, HDM_GETITEMRECT, (WPARAM)AColumn, (LPARAM)&R );
::IntersectRect( &R1, &ps.rcPaint, &R );
if( !::IsRectEmpty(&R1) ) PaintHeaderColumn( AColumn, HeaderColumnState(AColumn, R) );
}
R.left = R.right;
R.right = ClientWidth;
IntersectRect( &R1, &ps.rcPaint, &R );
if( !IsRectEmpty(&R1) ) PaintHeaderColumn( -1, glvNormal );
::EndPaint( hHeader, &ps );
}
//--------------------------------------------------------------
void __fastcall TGradientListView::HeaderMouseMove( TShiftState Shift, short X, short Y )
{
if( !FMouseInHeader ) HeaderMouseEnter( Point(X,Y) );
else if( !FSizingColumn )
{
int AColumn = HeaderColumn( Point(X,Y) );
if( AColumn != FHeaderColumn )
{
if( FHeaderColumn > -1 ) PaintHeaderColumn( FHeaderColumn, glvNormal );
if( AColumn > -1 )
{
if( AColumn == FClickedColumn ) PaintHeaderColumn( AColumn, glvPressed );
else if( FHeaderMouseDown ) PaintHeaderColumn( AColumn, glvNormal );
else PaintHeaderColumn( AColumn, glvHot );
}
}
FHeaderColumn = AColumn;
}
else
{
if( FHeaderClickPoint.x < X )
{
Columns->Items[FClickedColumn]->Width = Columns->Items[FClickedColumn]->Width + (X - FHeaderClickPoint.x);
FHeaderClickPoint = Point( X, Y );
}
else if( Columns->Items[FClickedColumn]->Width > 0 )
{
if( Columns->Items[FClickedColumn]->Width > (FHeaderClickPoint.x - X) )
{
Columns->Items[FClickedColumn]->Width = Columns->Items[FClickedColumn]->Width - (FHeaderClickPoint.x - X);
//*******************************
//
// Start of problem block
//
//*******************************
SCROLLINFO si = { 0 };
si.cbSize = sizeof( SCROLLINFO );
si.fMask = SIF_POS;
::GetScrollInfo(Handle, SB_HORZ, &si);
if( si.nPos > 0 )
{
// solution goes here
}
else
{
FHeaderClickPoint = Point( X, Y );
}
//*******************************
//
// end of problem block
//
//*******************************
}
else
{
FHeaderClickPoint.x -= Columns->Items[FClickedColumn]->Width;
Columns->Items[FClickedColumn]->Width = 0;
}
}
HeaderBoundsChanged();
}
}
//--------------------------------------------------------------
~ JD
Technical Details
Message-ID:
<4453413e$1@newsgroups.borland.com>Path:
arc1-ca.pugleaf.net!g2news2.google.com!news3.google.com!news4.google.com!border1.nntp.dca.giganews.com!nntp.giganews.com!newsfeed00.sul.t-online.de!newsfeed01.sul.t-online.de!t-online.de!newsgroups.borland.com!not-for-mailShow Full Headers
Complete Article Headers
X-Google-Thread: 10cc67,6756aa08d2757c32,start X-Google-Attributes: gid10cc67,public X-Google-Language: ENGLISH,ASCII-7-bit From: "JD" <nospam@nospam.com> Sender: "JD" <nospam@nospam.com> Reply-To: "JD" <nospam@nospam.com> Subject: Sizing ListView Column when HScrollPos > 0 Newsgroups: borland.public.cppbuilder.nativeapi X-User-Info: 64.12.116.201 X-Forwarded-For: 172.163.255.26 NNTP-Posting-Host: 207.105.83.66 Message-ID: <4453413e$1@newsgroups.borland.com> Date: 29 Apr 2006 03:34:38 -0700 X-Trace: newsgroups.borland.com 1146306878 207.105.83.66 (29 Apr 2006 03:34:38 -0700) Lines: 545 Path: arc1-ca.pugleaf.net!g2news2.google.com!news3.google.com!news4.google.com!border1.nntp.dca.giganews.com!nntp.giganews.com!newsfeed00.sul.t-online.de!newsfeed01.sul.t-online.de!t-online.de!newsgroups.borland.com!not-for-mail