In-place editing of a subitem in a TListView

前端 未结 4 1613
名媛妹妹
名媛妹妹 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<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: Added detection for mousewheel scroll to exit editing.
    EDIT2: Allow for moving the cursor within the edit box with the arrow keys

    0 讨论(0)
  • 2021-02-03 13:25

    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;
    
    0 讨论(0)
  • 2021-02-03 13:27

    From the review queue:

    For those interested, I've created a TListView extension based in RRUZ's answer

    https://github.com/BakasuraRCE/TEditableListView

    The code is as follows:

    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.
    

    The original poster's, Bakasura, answer had been deleted:

    0 讨论(0)
  • 2021-02-03 13:32

    I wrote sample code on CodeCentral that shows how to do this.

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

    Update:

    Here is an updated version that should compile now:

    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 讨论(0)
提交回复
热议问题