In-place editing of a subitem in a TListView

Aaron picture Aaron · May 31, 2012 · Viewed 16.2k times · Source

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 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.

Answer

RRUZ picture RRUZ · May 31, 2012

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;