2012-05-31 110 views
8

我有一個ListView有3列,並且想要編輯第三列,又名Subitem [1]。如果我將ListView.ReadOnly設置爲True,它允許我編輯所選項目的標題。是否有一種簡單的方法來爲子項目做同樣的事情?我想遠離在編輯上添加無邊框控件。就地編輯TListView中的子項目

回答

14

可以編輯列表視圖中的一個子項(在報告模式下) ,自定義消息並處理ListView的事件。

試試這個樣本

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

這樣做的絕大部分技巧,但它確實有一個副作用的bug,它在改變焦點時將數據帶到新的單元格。 –

+0

有沒有簡單的方法來做到這一點?我想只是就地編輯,所以1.我添加一個文本框到列表和退出分配TEdit。 'ListViewEditor:= TEdit.Create(自拍); ListViewEditor.Parent:= ListView1; ListViewEditor.OnExit:= ListViewEditorExit; ListViewEditor.Visible:= False;'這似乎不起作用 –

+0

@siddharthtaunk答案中的代碼按預期工作,並且AFAICS最低。我不明白你想達到什麼目的。沒有捷徑,你不能遺漏代碼,並希望它能起作用。 –

7

我在CodeCentral上編寫了示例代碼,演示瞭如何執行此操作。

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

更新:

這裏有一個更新的版本,現在應該編譯:使用TEDIT

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

漂亮的代碼,但它是一個有點過時。不會在德爾福2010年編譯。 – Bill

+0

嗯,我最初發布它,最後更新它,在2005年後:)它仍然相關,只需要一些小調整。 –

+0

感謝您的更新! – Bill

3

我把RRUZ的代碼,並決定將它的一個獨立單元,以支持多種編輯欄的派生TListView的對象。它還允許您使用箭頭,輸入和選項卡在可編輯項目之間移動。

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:增加了mousewheel滾動檢測退出編輯。
EDIT2:允許使用箭頭鍵在編輯框中移動光標

0

review queue

對於那些有興趣,我創建了基於 RRUZ的回答一個TListView的擴展

https://github.com/BakasuraRCE/TEditableListView

的代碼如下:

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. 

樓主的,Bakasura,答案已被刪除:

Screenshot of original answer