In-place editing of a subitem in a TListView

前端 未结 4 1631
名媛妹妹
名媛妹妹 2021-02-03 13:04

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

4条回答
  •  一整个雨季
    2021-02-03 13:13

    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

提交回复
热议问题