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