I have a ListView with 3 columns and would like to edit the third column, aka Subitem[1]. If I set ListView.ReadOnly to False, it allows me to edit the caption of the selected item. Is there an easy way to do the same thing for the subitem? I would like to stay away from adding a borderless control on top that does the editing.
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;
ListViewEditor:=TEdit.Create(Self); ListViewEditor.Parent:=ListView1; ListViewEditor.OnExit:=ListViewEditorExit; ListViewEditor.Visible:=False;
Thsi doesnt seem to work –
Markus LVHitTestInfo.iSubItem = EDIT_COLUMN
to (LVHitTestInfo.iSubItem = EDIT_COLUMN) or (LVHitTestInfo.iSubItem = EDIT_NEXT_COLUMN)
But how DO i add it in UserEditListView
. . I mean I how do I add a TComboBox to COloum 4 here –
Markus LItem := listview1.Items[ Message.wparam ];
The LItems
is show only 2 although I have added another column. what do I have to change in the handler? Although or the calling function? –
Markus 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.
Manual_...()
functions should not be getting used at all, it should be using the ComCtl_...()
functions instead. Is there a problem with GetComCtl32Version()
returning the correct version number for comctl32.dll
? Regardless, in order for TListView.GetItemAt()
to support hit tests on sub-columns, you have to set the TListView.RowSelect
property to true. –
Accusal DllGetVersion()
reports the right values, but the GetComCtl32Version()
comparison evaluates to True only when Runtime Themes are disabled, because MAKELONG(4, 70)
is 4587524 and MAKELONG(5, 82)
is 5373957, but MAKELONG(6, 16)
is 1048582! Swap the parameters when calling MAKELONG()
(and fix a minor bug in GetComCtl32Version()
when DllGetVersion()
fails) and everything will work correctly. I have updated my answer. –
Accusal 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
From the review queue:
For those interested, I've created a TListView extension based in RRUZ's answer
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:
© 2022 - 2024 — McMap. All rights reserved.