I have a ListView with 3 columns and would like to edit the third column, aka Subitem[1]. If I set ListView.ReadOnly to True, it allows me to edit the caption of the selected it
I took RRUZ's code and decided to make a self-contained unit of it, with a derived TListView object that supports multiple editable columns. It also allows you to move between editable items using the arrows, enter and tab.
unit EditableListView;
interface
uses
Messages,
Classes, StdCtrls, ComCtrls, System.Types,
Generics.Collections;
Const
ELV_EDIT = WM_USER + 16;
type
TEditableListView = class(TListView)
private
FEditable: TList<integer>;
FEditor: TEdit;
FItem: TListItem;
FEditColumn: integer;
procedure EditListView(var AMessage: TMessage); message ELV_EDIT;
procedure EditExit(Sender: TObject);
procedure EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure DoEdit;
procedure CleanupEditable;
function GetEditable(const I: integer): boolean;
procedure SetEditable(const I: integer; const Value: boolean);
protected
procedure Click; override;
function DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Editable[const I: integer]: boolean read GetEditable write SetEditable;
end;
implementation
uses
Windows, SysUtils, CommCtrl, Controls;
{ TEditableListView }
constructor TEditableListView.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FEditable := TList<integer>.Create;
FEditor := TEdit.Create(self);
FEditor.Parent := self;
FEditor.OnExit := EditExit;
FEditor.OnKeyDown := EditKeyDown;
FEditor.Visible := false;
ViewStyle := vsReport; // Default to vsReport instead of vsIcon
end;
destructor TEditableListView.Destroy;
begin
FEditable.Free;
inherited Destroy;
end;
procedure TEditableListView.DoEdit;
begin
if Assigned(FItem) Then
begin
// assign the value of the TEdit to the Subitem
if FEditColumn = 0 then
FItem.Caption := FEditor.Text
else if FEditColumn > 0 then
FItem.SubItems[FEditColumn - 1] := FEditor.Text;
end;
end;
function TEditableListView.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean;
begin
DoEdit;
FEditor.Visible := false;
SetFocus;
Result := inherited DoMouseWheel(Shift, WheelDelta, MousePos);
end;
procedure TEditableListView.CleanupEditable;
var
I: integer;
begin
for I := FEditable.Count - 1 downto 0 do
begin
if not Assigned(Columns.FindItemID(FEditable[I])) then
FEditable.Delete(I);
end;
end;
procedure TEditableListView.Click;
var
LPoint: TPoint;
LVHitTestInfo: TLVHitTestInfo;
begin
LPoint := ScreenToClient(Mouse.CursorPos);
FillChar(LVHitTestInfo, SizeOf(LVHitTestInfo), 0);
LVHitTestInfo.pt := LPoint;
// Check if the click was made in the column to edit
if (perform(LVM_SUBITEMHITTEST, 0, LPARAM(@LVHitTestInfo)) <> -1) Then
PostMessage(self.Handle, ELV_EDIT, LVHitTestInfo.iItem, LVHitTestInfo.iSubItem)
else
FEditor.Visible := false; //hide the TEdit
inherited Click;
end;
procedure TEditableListView.EditExit(Sender: TObject);
begin
DoEdit;
end;
procedure TEditableListView.EditKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
lNextRow, lNextCol: integer;
begin
if Key in [VK_RETURN, VK_TAB, VK_LEFT, VK_RIGHT, VK_UP, VK_DOWN] then
begin
DoEdit;
lNextRow := FItem.Index;
lNextCol := FEditColumn;
case Key of
VK_RETURN,
VK_DOWN:
lNextRow := lNextRow + 1;
VK_UP:
lNextRow := lNextRow - 1;
VK_TAB,
VK_RIGHT:
lNextCol := lNextCol + 1;
VK_LEFT:
lNextCol := lNextCol - 1;
end;
if not ( (Key = VK_RIGHT) and (FEditor.SelStart+FEditor.SelLength < Length(FEditor.Text)) )
and not ( (Key = VK_LEFT) and (FEditor.SelStart+FEditor.SelLength > 0) ) then
begin
Key := 0;
if (lNextRow >= 0) and (lNextRow < Items.Count)
and (lNextCol >= 0) and (lNextCol < Columns.Count) then
PostMessage(self.Handle, ELV_EDIT, lNextRow, lNextCol);
end;
end;
end;
procedure TEditableListView.EditListView(var AMessage: TMessage);
var
LRect: TRect;
begin
if Editable[AMessage.LParam] then
begin
LRect.Top := AMessage.LParam;
LRect.Left:= LVIR_BOUNDS;
Perform(LVM_GETSUBITEMRECT, AMessage.wparam, LPARAM(@LRect));
//get the current Item to edit
FItem := Items[AMessage.wparam];
FEditColumn := AMessage.LParam;
//set the text of the Edit
if FEditColumn = 0 then
FEditor.Text := FItem.Caption
else if FEditColumn > 0 then
FEditor.Text := FItem.Subitems[FEditColumn-1]
else
FEditor.Text := '';
//set the bounds of the TEdit
FEditor.BoundsRect := LRect;
//Show the TEdit
FEditor.Visible := true;
FEditor.SetFocus;
FEditor.SelectAll;
end
else
FEditor.Visible := false;
end;
function TEditableListView.GetEditable(const I: integer): boolean;
begin
if (I > 0) and (I < Columns.Count) then
Result := FEditable.IndexOf(Columns[I].ID) >= 0
else
Result := false;
CleanupEditable;
end;
procedure TEditableListView.SetEditable(const I: integer; const Value: boolean);
var
Lix: integer;
begin
if (I > 0) and (I < Columns.Count) then
begin
Lix := FEditable.IndexOf(Columns[I].ID);
if Value and (Lix < 0)then
FEditable.Add(Columns[I].ID)
else if not Value and (Lix >= 0) then
FEditable.Delete(Lix);
end;
CleanupEditable;
end;
end.
EDIT1: Added detection for mousewheel scroll to exit editing.
EDIT2: Allow for moving the cursor within the edit box with the arrow keys
You can Edit a subitem of the listview (in report mode) using a TEdit, a custom message and handling the OnClick
event of the ListView.
Try this sample
Const
USER_EDITLISTVIEW = WM_USER + 666;
type
TForm1 = class(TForm)
ListView1: TListView;
procedure FormCreate(Sender: TObject);
procedure ListView1Click(Sender: TObject);
private
ListViewEditor: TEdit;
LItem: TListitem;
procedure UserEditListView( Var Message: TMessage ); message USER_EDITLISTVIEW;
procedure ListViewEditorExit(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
CommCtrl;
const
EDIT_COLUMN = 2; //Index of the column to Edit
procedure TForm1.FormCreate(Sender: TObject);
Var
I : Integer;
Item : TListItem;
begin
for I := 0 to 9 do
begin
Item:=ListView1.Items.Add;
Item.Caption:=Format('%d.%d',[i,1]);
Item.SubItems.Add(Format('%d.%d',[i,2]));
Item.SubItems.Add(Format('%d.%d',[i,3]));
end;
//create the TEdit and assign the OnExit event
ListViewEditor:=TEdit.Create(Self);
ListViewEditor.Parent:=ListView1;
ListViewEditor.OnExit:=ListViewEditorExit;
ListViewEditor.Visible:=False;
end;
procedure TForm1.ListView1Click(Sender: TObject);
var
LPoint: TPoint;
LVHitTestInfo: TLVHitTestInfo;
begin
LPoint:= listview1.ScreenToClient(Mouse.CursorPos);
ZeroMemory( @LVHitTestInfo, SizeOf(LVHitTestInfo));
LVHitTestInfo.pt := LPoint;
//Check if the click was made in the column to edit
If (ListView1.perform( LVM_SUBITEMHITTEST, 0, LPARAM(@LVHitTestInfo))<>-1) and ( LVHitTestInfo.iSubItem = EDIT_COLUMN ) Then
PostMessage( self.Handle, USER_EDITLISTVIEW, LVHitTestInfo.iItem, 0 )
else
ListViewEditor.Visible:=False; //hide the TEdit
end;
procedure TForm1.ListViewEditorExit(Sender: TObject);
begin
If Assigned(LItem) Then
Begin
//assign the vslue of the TEdit to the Subitem
LItem.SubItems[ EDIT_COLUMN-1 ] := ListViewEditor.Text;
LItem := nil;
End;
end;
procedure TForm1.UserEditListView(var Message: TMessage);
var
LRect: TRect;
begin
LRect.Top := EDIT_COLUMN;
LRect.Left:= LVIR_BOUNDS;
listview1.Perform( LVM_GETSUBITEMRECT, Message.wparam, LPARAM(@LRect) );
MapWindowPoints( listview1.Handle, ListViewEditor.Parent.Handle, LRect, 2 );
//get the current Item to edit
LItem := listview1.Items[ Message.wparam ];
//set the text of the Edit
ListViewEditor.Text := LItem.Subitems[ EDIT_COLUMN-1];
//set the bounds of the TEdit
ListViewEditor.BoundsRect := LRect;
//Show the TEdit
ListViewEditor.Visible:=True;
end;
From the review queue:
For those interested, I've created a TListView extension based in RRUZ's answer
https://github.com/BakasuraRCE/TEditableListView
The code is as follows:
unit UnitEditableListView;
interface
uses
Winapi.Windows,
Winapi.Messages,
Winapi.CommCtrl,
System.Classes,
Vcl.ComCtrls,
Vcl.StdCtrls;
type
///
/// Based on: https://stackoverflow.com/a/10836109
///
TListView = class(Vcl.ComCtrls.TListView)
strict private
FListViewEditor: TEdit;
FEditorItemIndex, FEditorSubItemIndex: Integer;
FCursorPos: TPoint;
// Create native item
function CreateItem(Index: Integer; ListItem: TListItem): TLVItem;
// Free TEdit
procedure FreeEditorItemInstance;
// Invalidate cursor position
procedure ResetCursorPos;
{
TEdit Events
}
procedure ListViewEditorExit(Sender: TObject);
procedure ListViewEditorKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure ListViewEditorKeyPress(Sender: TObject; var Key: Char);
{
Override Events
}
procedure Click; override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
{
Windows Events
}
{ TODO -cenhancement : Scroll edit control with listview }
procedure WMMouseWheel(var Message: TWMMouseWheel); message WM_MOUSEWHEEL;
procedure WMHScroll(var Message: TWMHScroll); message WM_HSCROLL;
procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
///
/// Start edition on local position
///
procedure EditCaptionAt(Point: TPoint);
end;
implementation
uses
Vcl.Controls;
{ TListView }
procedure TListView.Click;
begin
inherited;
// Get current point
FCursorPos := ScreenToClient(Mouse.CursorPos);
FreeEditorItemInstance;
end;
constructor TListView.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
// Create the TEdit and assign the OnExit event
FListViewEditor := TEdit.Create(AOwner);
with FListViewEditor do
begin
Parent := Self;
OnKeyDown := ListViewEditorKeyDown;
OnKeyPress := ListViewEditorKeyPress;
OnExit := ListViewEditorExit;
Visible := False;
end;
end;
destructor TListView.Destroy;
begin
// Free TEdit
FListViewEditor.Free;
inherited;
end;
procedure TListView.EditCaptionAt(Point: TPoint);
var
Rect: TRect;
CursorPos: TPoint;
HitTestInfo: TLVHitTestInfo;
CurrentItem: TListItem;
begin
// Set position to handle
HitTestInfo.pt := Point;
// Get item select
if ListView_SubItemHitTest(Handle, @HitTestInfo) = -1 then
Exit;
with HitTestInfo do
begin
FEditorItemIndex := iItem;
FEditorSubItemIndex := iSubItem;
end;
// Nothing?
if (FEditorItemIndex < 0) or (FEditorItemIndex >= Items.Count) then
Exit;
if FEditorSubItemIndex < 0 then
Exit;
CurrentItem := Items[ItemIndex];
if not CanEdit(CurrentItem) then
Exit;
// Get bounds
ListView_GetSubItemRect(Handle, FEditorItemIndex, FEditorSubItemIndex, LVIR_LABEL, @Rect);
// set the text of the Edit
if FEditorSubItemIndex = 0 then
FListViewEditor.Text := CurrentItem.Caption
else
begin
FListViewEditor.Text := CurrentItem.SubItems[FEditorSubItemIndex - 1];
end;
// Set the bounds of the TEdit
FListViewEditor.BoundsRect := Rect;
// Show the TEdit
FListViewEditor.Visible := True;
// Set focus
FListViewEditor.SetFocus;
end;
procedure TListView.ResetCursorPos;
begin
// Free cursos pos
FCursorPos := Point(-1, -1);
end;
procedure TListView.FreeEditorItemInstance;
begin
FEditorItemIndex := -1;
FEditorSubItemIndex := -1;
FListViewEditor.Visible := False; // Hide the TEdit
end;
procedure TListView.KeyDown(var Key: Word; Shift: TShiftState);
begin
inherited KeyDown(Key, Shift);
// F2 key start edit
if (Key = VK_F2) then
EditCaptionAt(FCursorPos);
end;
///
/// Create a LVItem
///
function TListView.CreateItem(Index: Integer; ListItem: TListItem): TLVItem;
begin
with Result do
begin
mask := LVIF_PARAM or LVIF_IMAGE or LVIF_GROUPID;
iItem := index;
iSubItem := 0;
iImage := I_IMAGECALLBACK;
iGroupId := -1;
pszText := PChar(ListItem.Caption);
{$IFDEF CLR}
lParam := ListItem.GetHashCode;
{$ELSE}
lParam := Winapi.Windows.lParam(ListItem);
{$ENDIF}
end;
end;
procedure TListView.ListViewEditorExit(Sender: TObject);
begin
// I have an instance?
if FEditorItemIndex = -1 then
Exit;
// Assign the value of the TEdit to the Subitem
if FEditorSubItemIndex = 0 then
Items[FEditorItemIndex].Caption := FListViewEditor.Text
else
Items[FEditorItemIndex].SubItems[FEditorSubItemIndex - 1] := FListViewEditor.Text;
// Raise OnEdited event
Edit(CreateItem(FEditorItemIndex, Items[FEditorItemIndex]));
// Free instanse
FreeEditorItemInstance;
end;
procedure TListView.ListViewEditorKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
// ESCAPE key exit of editor
if Key = VK_ESCAPE then
FreeEditorItemInstance;
end;
procedure TListView.ListViewEditorKeyPress(Sender: TObject; var Key: Char);
begin
// Update item on press ENTER
if (Key = #$0A) or (Key = #$0D) then
FListViewEditor.OnExit(Sender);
end;
procedure TListView.WMHScroll(var Message: TWMHScroll);
begin
inherited;
// Reset cursos pos
ResetCursorPos;
// Free instanse
FreeEditorItemInstance;
end;
procedure TListView.WMMouseWheel(var Message: TWMMouseWheel);
begin
inherited;
// Reset cursos pos
ResetCursorPos;
// Free instanse
FreeEditorItemInstance;
end;
procedure TListView.WMVScroll(var Message: TWMVScroll);
begin
inherited;
// Reset cursos pos
ResetCursorPos;
// Free instanse
FreeEditorItemInstance;
end;
end.
The original poster's, Bakasura, answer had been deleted:
I wrote sample code on CodeCentral that shows how to do this.
How to use the Build-in Editor of TListView to Edit SubItems
Update:
Here is an updated version that should compile now:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics,
Controls, Forms, Dialogs, ComCtrls;
type
TForm1 = class(TForm)
ListView1: TListView;
procedure ListView1Editing(Sender: TObject; Item: TListItem; var AllowEdit: Boolean);
procedure ListView1Edited(Sender: TObject; Item: TListItem; var S: string);
procedure ListView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure ListView1DrawItem(Sender: TCustomListView; Item: TListItem; Rect: TRect; State: TOwnerDrawState);
private
{ Private declarations }
ColumnToEdit: Integer;
OldListViewEditProc: Pointer;
hListViewEditWnd: HWND;
ListViewEditWndProcPtr: Pointer;
procedure ListViewEditWndProc(var Message: TMessage);
public
{ Public declarations }
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
uses
Commctrl;
{$R *.dfm}
type
TListViewCoord = record
Item: Integer;
Column: Integer;
end;
TLVGetColumnAt = function(Item: TListItem; const Pt: TPoint): Integer;
TLVGetColumnRect = function(Item: TListItem; ColumnIndex: Integer; var Rect: TRect): Boolean;
TLVGetIndexesAt = function(ListView: TCustomListView; const Pt: TPoint; var Coord: TListViewCoord): Boolean;
// TCustomListViewAccess provides access to the protected members of TCustomListView
TCustomListViewAccess = class(TCustomListView);
var
// these will be assigned according to the version of COMCTL32.DLL being used
GetColumnAt: TLVGetColumnAt = nil;
GetColumnRect: TLVGetColumnRect = nil;
GetIndexesAt: TLVGetIndexesAt = nil;
//---------------------------------------------------------------------------
// GetComCtl32Version
//
// Purpose: Helper function to determine the version of CommCtrl32.dll that is loaded.
//---------------------------------------------------------------------------
var
ComCtl32Version: DWORD = 0;
function GetComCtl32Version: DWORD;
type
DLLVERSIONINFO = packed record
cbSize: DWORD;
dwMajorVersion: DWORD;
dwMinorVersion: DWORD;
dwBuildNumber: DWORD;
dwPlatformID: DWORD;
end;
DLLGETVERSIONPROC = function(var dvi: DLLVERSIONINFO): Integer; stdcall;
var
hComCtrl32: HMODULE;
lpDllGetVersion: DLLGETVERSIONPROC;
dvi: DLLVERSIONINFO;
FileName: array[0..MAX_PATH] of Char;
dwHandle: DWORD;
dwSize: DWORD;
pData: Pointer;
pVersion: Pointer;
uiLen: UINT;
begin
if ComCtl32Version = 0 then
begin
hComCtrl32 := GetModuleHandle('comctl32.dll');
if hComCtrl32 <> 0 then
begin
@lpDllGetVersion := GetProcAddress(hComCtrl32, 'DllGetVersion');
if @lpDllGetVersion <> nil then
begin
ZeroMemory(@dvi, SizeOf(dvi));
dvi.cbSize := SizeOf(dvi);
if lpDllGetVersion(dvi) >= 0 then
ComCtl32Version := MAKELONG(Word(dvi.dwMinorVersion), Word(dvi.dwMajorVersion));
end;
if ComCtl32Version = 0 then
begin
ZeroMemory(@FileName[0], SizeOf(FileName));
if GetModuleFileName(hComCtrl32, FileName, MAX_PATH) <> 0 then
begin
dwHandle := 0;
dwSize := GetFileVersionInfoSize(FileName, dwHandle);
if dwSize <> 0 then
begin
GetMem(pData, dwSize);
try
if GetFileVersionInfo(FileName, dwHandle, dwSize, pData) then
begin
pVersion := nil;
uiLen := 0;
if VerQueryValue(pData, '\', pVersion, uiLen) then
begin
with PVSFixedFileInfo(pVersion)^ do
ComCtl32Version := MAKELONG(LOWORD(dwFileVersionMS), HIWORD(dwFileVersionMS));
end;
end;
finally
FreeMem(pData);
end;
end;
end;
end;
end;
end;
Result := ComCtl32Version;
end;
//---------------------------------------------------------------------------
// Manual_GetColumnAt
//
// Purpose: Returns the column index at the specified coordinates,
// relative to the specified item
//---------------------------------------------------------------------------
function Manual_GetColumnAt(Item: TListItem; const Pt: TPoint): Integer;
var
LV: TCustomListViewAccess;
R: TRect;
I: Integer;
begin
LV := TCustomListViewAccess(Item.ListView);
// determine the dimensions of the current column value, and
// see if the coordinates are inside of the column value
// get the dimensions of the entire item
R := Item.DisplayRect(drBounds);
// loop through all of the columns looking for the value that was clicked on
for I := 0 to LV.Columns.Count-1 do
begin
R.Right := (R.Left + LV.Column[I].Width);
if PtInRect(R, Pt) then
begin
Result := I;
Exit;
end;
R.Left := R.Right;
end;
Result := -1;
end;
//---------------------------------------------------------------------------
// Manual_GetColumnRect
//
// Purpose: Calculate the dimensions of the specified column,
// relative to the specified item
//---------------------------------------------------------------------------
function Manual_GetColumnRect(Item: TListItem; ColumnIndex: Integer; var Rect: TRect): Boolean;
var
LV: TCustomListViewAccess;
I: Integer;
begin
Result := False;
LV := TCustomListViewAccess(Item.ListView);
// make sure the index is in the valid range
if (ColumnIndex >= 0) and (ColumnIndex < LV.Columns.Count) then
begin
// get the dimensions of the entire item
Rect := Item.DisplayRect(drBounds);
// loop through the columns calculating the desired offsets
for I := 0 to ColumnIndex-1 do
Rect.Left := (Rect.Left + LV.Column[i].Width);
Rect.Right := (Rect.Left + LV.Column[ColumnIndex].Width);
Result := True;
end;
end;
//---------------------------------------------------------------------------
// Manual_GetIndexesAt
//
// Purpose: Returns the Item and Column indexes at the specified coordinates
//---------------------------------------------------------------------------
function Manual_GetIndexesAt(ListView: TCustomListView; const Pt: TPoint; var Coord: TListViewCoord): Boolean;
var
Item: TListItem;
begin
Result := False;
Item := ListView.GetItemAt(Pt.x, Pt.y);
if Item <> nil then
begin
Coord.Item := Item.Index;
Coord.Column := Manual_GetColumnAt(Item, Pt);
Result := True;
end;
end;
//---------------------------------------------------------------------------
// ComCtl_GetColumnAt
//
// Purpose: Returns the column index at the specified coordinates, relative to the specified item
//---------------------------------------------------------------------------
function ComCtl_GetColumnAt(Item: TListItem; const Pt: TPoint): Integer;
var
HitTest: LV_HITTESTINFO;
begin
Result := -1;
ZeroMemory(@HitTest, SizeOf(HitTest));
HitTest.pt := Pt;
if ListView_SubItemHitTest(Item.ListView.Handle, @HitTest) > -1 then
begin
if HitTest.iItem = Item.Index then
Result := HitTest.iSubItem;
end;
end;
//---------------------------------------------------------------------------
// ComCtl_GetColumnRect
//
// Purpose: Calculate the dimensions of the specified column, relative to the specified item
//---------------------------------------------------------------------------
function ComCtl_GetColumnRect(Item: TListItem; ColumnIndex: Integer; var Rect: TRect): Boolean;
begin
Result := ListView_GetSubItemRect(Item.ListView.Handle, Item.Index, ColumnIndex, LVIR_BOUNDS, @Rect);
end;
//---------------------------------------------------------------------------
// ComCtl_GetIndexesAt
//
// Purpose: Returns the Item and Column indexes at the specified coordinates
//---------------------------------------------------------------------------
function ComCtl_GetIndexesAt(ListView: TCustomListView; const Pt: TPoint; var Coord: TListViewCoord): Boolean;
var
HitTest: LV_HITTESTINFO;
begin
Result := False;
ZeroMemory(@HitTest, SizeOf(HitTest));
HitTest.pt := Pt;
if ListView_SubItemHitTest(ListView.Handle, @HitTest) > -1 then
begin
Coord.Item := HitTest.iItem;
Coord.Column := HitTest.iSubItem;
Result := True;
end;
end;
//---------------------------------------------------------------------------
// TForm1 Constructor
//
// Purpose: Form constructor
//---------------------------------------------------------------------------
constructor TForm1.Create(Owner: TComponent);
begin
inherited Create(Owner);
// no editing yet
ColumnToEdit := -1;
OldListViewEditProc := nil;
hListViewEditWnd := 0;
ListViewEditWndProcPtr := MakeObjectInstance(ListViewEditWndProc);
if ListViewEditWndProcPtr = nil then
raise Exception.Create('Could not allocate memory for ListViewEditWndProc proxy');
if GetComCtl32Version >= DWORD(MAKELONG(70, 4)) then
begin
@GetColumnAt := @ComCtl_GetColumnAt;
@GetColumnRect := @ComCtl_GetColumnRect;
@GetIndexesAt := @ComCtl_GetIndexesAt;
end else
begin
@GetColumnAt := @Manual_GetColumnAt;
@GetColumnRect := @Manual_GetColumnRect;
@GetIndexesAt := @Manual_GetIndexesAt;
end;
end;
//---------------------------------------------------------------------------
// TForm1 Destructor
//
// Purpose: Form destructor
//---------------------------------------------------------------------------
destructor TForm1.Destroy;
begin
if ListViewEditWndProcPtr <> nil then
FreeObjectInstance(ListViewEditWndProcPtr);
inherited Destroy;
end;
//---------------------------------------------------------------------------
// ListViewEditWndProc
//
// Purpose: Custom Window Procedure for TListView's editor window
//---------------------------------------------------------------------------
procedure TForm1.ListViewEditWndProc(var Message: TMessage);
begin
if Message.Msg = WM_WINDOWPOSCHANGING then
begin
// this inline editor has a bad habit of re-positioning itself
// back on top of the Caption after every key typed in,
// so let's stop it from moving
with TWMWindowPosMsg(Message).WindowPos^ do flags := flags or SWP_NOMOVE;
Message.Result := 0;
end else
begin
// everything else
Message.Result := CallWindowProc(OldListViewEditProc, hListViewEditWnd,
Message.Msg, Message.WParam, Message.LParam);
end;
end;
//---------------------------------------------------------------------------
// ListView1DrawItem
//
// Purpose: Handler for the TListView::OnDrawItem event
//---------------------------------------------------------------------------
procedure TForm1.ListView1DrawItem(Sender: TCustomListView; Item: TListItem; Rect: TRect; State: TOwnerDrawState);
var
LV: TCustomListViewAccess;
R: TRect;
P: TPoint;
I: Integer;
S: String;
begin
LV := TCustomListViewAccess(Sender);
// erase the entire item to start fresh
R := Item.DisplayRect(drBounds);
LV.Canvas.Brush.Color := LV.Color;
LV.Canvas.FillRect(R);
// see if the mouse is currently held down, and if so update the marker as needed
if (GetKeyState(VK_LBUTTON) and $8000) <> 0 then
begin
// find the mouse cursor onscreen, convert the coordinates to client
// coordinates on the list view
GetCursorPos(P);
ColumnToEdit := GetColumnAt(Item, LV.ScreenToClient(P));
end;
// loop through all of the columns drawing each column
for I := 0 to LV.Columns.Count-1 do
begin
// determine the dimensions of the current column value
if not GetColumnRect(Item, I, R) then
Continue;
// mimic the default behavior by only drawing a value as highlighted if
// the entire item is selected, the particular column matches the marker,
// and the ListView is not already editing
if Item.Selected and (I = ColumnToEdit) and (not LV.IsEditing) then
begin
LV.Canvas.Brush.Color := clHighlight;
LV.Canvas.Font.Color := clHighlightText;
end else
begin
LV.Canvas.Brush.Color := LV.Color;
LV.Canvas.Font.Color := LV.Font.Color;
end;
LV.Canvas.FillRect(R);
// draw the column's text
if I = 0 then
S := Item.Caption
else
S := Item.SubItems[I-1];
LV.Canvas.TextRect(R, R.Left + 2, R.Top, S);
end;
end;
//---------------------------------------------------------------------------
// ListView1Edited
//
// Purpose: Handler for the TListView::OnEdited event
//---------------------------------------------------------------------------
procedure TForm1.ListView1Edited(Sender: TObject; Item: TListItem; var S: string);
begin
// ignore the Caption, let it do its default handling
if ColumnToEdit <= 0 then Exit;
// restore the previous window procedure for the inline editor
if hListViewEditWnd <> 0 then
begin
SetWindowLongPtr(hListViewEditWnd, GWL_WNDPROC, LONG_PTR(OldListViewEditProc));
hListViewEditWnd := 0;
end;
// assign the new text to the subitem being edited
Item.SubItems[ColumnToEdit-1] := S;
// prevent the default behavior from updating the Caption as well
S := Item.Caption;
end;
//---------------------------------------------------------------------------
// ListView1Editing
//
// Purpose: Handler for the TListView::OnEditing event
//---------------------------------------------------------------------------
procedure TForm1.ListView1Editing(Sender: TObject; Item: TListItem; var AllowEdit: Boolean);
var
Wnd: HWND;
R: TRect;
begin
// ignore the Caption, let it do its default handling
if ColumnToEdit <= 0 then Exit;
// get the inline editor's handle
Wnd := ListView_GetEditControl(ListView1.Handle);
if Wnd = 0 then Exit;
// determine the dimensions of the subitem being edited
if not GetColumnRect(Item, ColumnToEdit, R) then Exit;
// move the inline editor over the subitem
MoveWindow(Wnd, R.Left, R.Top - 2, R.Right-R.Left, (R.Bottom-R.Top) + 4, TRUE);
// update the inline editor's text with the subitem's text rather than the Caption
SetWindowText(Wnd, PChar(Item.SubItems[ColumnToEdit-1]));
// subclass the inline editor so we can catch its movements
hListViewEditWnd := Wnd;
OldListViewEditProc := Pointer(GetWindowLongPtr(Wnd, GWL_WNDPROC));
SetWindowLongPtr(Wnd, GWL_WNDPROC, LONG_PTR(ListViewEditWndProcPtr));
end;
//---------------------------------------------------------------------------
// ListView1MouseDown
//
// Purpose: Handler for the TListView::OnMouseDown event
//---------------------------------------------------------------------------
procedure TForm1.ListView1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
Coord: TListViewCoord;
begin
if GetIndexesAt(ListView1, Point(X, Y), Coord) then
begin
if Coord.Column <> ColumnToEdit then
begin
// update the marker
ColumnToEdit := Coord.Column;
// cancel the editing so that the listview won't go into
// its edit mode immediately upon clicking the new item
ListView1.Items[Coord.Item].CancelEdit;
// update the display with a new highlight selection
ListView1.Invalidate;
end;
end else
ColumnToEdit := -1;
end;
end.