2012-05-31 33 views
8

Tôi có một ListView với 3 cột và muốn chỉnh sửa cột thứ ba, aka Subitem [1]. Nếu tôi đặt ListView.ReadOnly thành True, nó cho phép tôi chỉnh sửa chú thích của mục đã chọn. Có cách nào dễ dàng để làm điều tương tự cho tiểu đơn vị không? Tôi muốn tránh xa việc thêm một kiểm soát không biên giới trên đầu trang mà chỉnh sửa.Chỉnh sửa tại chỗ của một phụ đề trong TListView

Trả lời

14

Bạn có thể Chỉnh sửa subitem của listview (trong chế độ báo cáo) sử dụng một TEdit , một thông điệp tùy chỉnh và xử lý sự kiện OnClick của ListView.

Hãy thử mẫu này

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; 
+0

Điều này thực hiện hầu hết các thủ thuật, nhưng nó có một lỗi con đẻ mang dữ liệu đến một ô mới khi thay đổi tiêu điểm. –

+0

Có cách nào dễ dàng để thực hiện việc này không? Tôi muốn chỉnh sửa tại chỗ như vậy 1. Tôi thêm một hộp văn bản vào Danh sách và trên lối ra Gán TEdit. 'ListViewEditor: = TEdit.Create (Tự); ListViewEditor.Parent: = ListView1; ListViewEditor.OnExit: = ListViewEditorExit; ListViewEditor.Visible: = False; 'Thsi doesnt dường như làm việc –

+0

@siddharthtaunk Mã trong câu trả lời hoạt động như dự định và là AFAICS tối thiểu. Tôi không hiểu những gì bạn cố gắng đạt được. Không có phím tắt, bạn không thể bỏ mã và hy vọng nó sẽ hoạt động. –

7

Tôi đã viết mã mẫu trên CodeCentral cho biết cách thực hiện việc này.

How to use the Build-in Editor of TListView to Edit SubItems

Cập nhật:

Dưới đây là một phiên bản cập nhật cần biên dịch bây giờ:

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

đẹp mã nhưng nó là một chút lỗi thời. Sẽ không biên dịch trong Delphi 2010. – Bill

+0

Vâng, ban đầu tôi đã đăng nó, và cập nhật nó lần cuối, vào năm 2005 afterall :) Nó vẫn còn có liên quan, chỉ cần một số chỉnh sửa nhỏ. –

+0

Cảm ơn bạn đã cập nhật! – Bill

3

tôi đã đang RRUZ và quyết định để thực hiện một đơn vị khép kín của nó, với một đối tượng TListView bắt nguồn hỗ trợ nhiều cột có thể chỉnh sửa. Nó cũng cho phép bạn di chuyển giữa các mục có thể chỉnh sửa bằng cách sử dụng mũi tên, nhập và 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: Phát hiện thêm cho di chuyển con lăn để thoát chỉnh sửa.
EDIT2: Cho phép di chuyển con trỏ trong hộp soạn thảo với các phím mũi tên

0

Từ review queue:

Đối với những người quan tâm, tôi đã tạo ra một phần mở rộng TListView có trụ sở tại câu trả lời RRUZ của

https://github.com/BakasuraRCE/TEditableListView

mã này là như sau:

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. 

Những poster ban đầu của, Bakasura, câu trả lời đã được xóa:

Screenshot of original answer

Các vấn đề liên quan