Ivan
Ivan

Reputation: 85

Delphi - TListView in virtual mode issue

After setting ListView in virtual mode ListView1.Selected.Top always returns 0. I'm using that property on double click on list view to show edit box at that position.

How can I resolve this?

Example of .pas and .dfm files are here. I want to open edit box on position where it is double clicked.

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls,Generics.Collections,Generics.Defaults;

type
  TLVData = record
    Column0: string;
    Column1: string;
    Column2: string;
  end;

type
  TForm1 = class(TForm)
    ListView1: TListView;
    Edit1: TEdit;
    procedure ListView1DblClick(Sender: TObject);
    procedure NewEntry(i: integer);
    procedure FormShow(Sender: TObject);
    procedure ListView1Data(Sender: TObject; Item: TListItem);
  private
    { Private declarations }
  public
    { Public declarations }
  end;


var
  Form1: TForm1;
  LVDataList : TList<TLVData>;

implementation

{$R *.dfm}

procedure TForm1.NewEntry(i: integer);
var
  LVData:TLVData;
begin
  if not Assigned(LVDataList) then LVDataList := TList<TLVData>.Create;
  LVData.Column0 := 'Column0:' + IntToStr(i);
  LVData.Column1 := 'Column1:' + IntToStr(i);
  LVData.Column2 := 'Column2:' + IntToStr(i);

  LVDataList.Add(LVData);
end;

procedure TForm1.FormShow(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to 9 do
    NewEntry(i);
  ListView1.Items.Count := LVDataList.Count;
end;

procedure TForm1.ListView1Data(Sender: TObject; Item: TListItem);
var i: integer;
begin
  if not Assigned(Item) then Exit;

  Item.Caption := LVDataList.Items[Item.Index].Column0;
  Item.SubItems.Add(LVDataList.Items[Item.Index].Column0);
  Item.SubItems.Add(LVDataList.Items[Item.Index].Column1);
  Item.SubItems.Add(LVDataList.Items[Item.Index].Column2);
end;

procedure TForm1.ListView1DblClick(Sender: TObject);
begin
  Edit1.Text:=ListView1.Selected.SubItems[0];
  Edit1.Top:=ListView1.Top+ListView1.Selected.Top-2;
  Edit1.Width:=100;
  Edit1.Show;
  Edit1.SetFocus;
end;

end.

And .dfm:

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 412
  ClientWidth = 784
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  OnShow = FormShow
  PixelsPerInch = 96
  TextHeight = 13
  object ListView1: TListView
    Left = 0
    Top = 0
    Width = 784
    Height = 412
    Align = alClient
    Columns = <
      item
        AutoSize = True
        Caption = 'Column0'
        MinWidth = 100
      end
      item
        AutoSize = True
        Caption = 'Column1'
        MinWidth = 100
      end
      item
        AutoSize = True
        Caption = 'Column2'
        MinWidth = 100
      end>
    GridLines = True
    HideSelection = False
    MultiSelect = True
    OwnerData = True
    ReadOnly = True
    RowSelect = True
    TabOrder = 0
    ViewStyle = vsReport
    OnData = ListView1Data
    OnDblClick = ListView1DblClick
  end
  object Edit1: TEdit
    Left = 360
    Top = 168
    Width = 121
    Height = 21
    TabOrder = 1
    Text = 'Edit1'
    Visible = False
  end
end

Upvotes: 1

Views: 1023

Answers (1)

fpiette
fpiette

Reputation: 12292

I can reproduce your problem. I found a workaround: use the display rectangle of the selected item:

procedure TForm1.ListView1DblClick(Sender: TObject);
var
  Rect : TRect;
begin
  Rect        := ListView1.Selected.DisplayRect(drBounds);
  Edit1.Text  := ListView1.Selected.SubItems[0];
  Edit1.Top   := ListView1.Top + Rect.Top - 1;
  Edit1.Width :=100;
  Edit1.Show;
  Edit1.SetFocus;
end;

If you want to get the subitem, then you have to iterate thru the columns to find on which one the user clicked. We need to mouse position so we install an OnMouseDown event handler to save the mouse coordinates and use that to find the column.

  private
    FMouseDown : TPoint;

procedure TForm1.ListView1MouseDown(Sender: TObject; Button: TMouseButton;
    Shift: TShiftState; X, Y: Integer);
begin
    FMouseDown.X := X;
    FMouseDown.Y := Y;
end;

procedure TForm1.ListView1DblClick(Sender: TObject);
var
  Rect   : TRect;
  I      : Integer;
  X1, X2 : Integer;
  Col    : Integer;
begin
  if not Assigned(ListView1.Selected) then
      Exit;
  Rect := ListView1.Selected.DisplayRect(drBounds);
  X1   := 0;
  X2   := 0;
  Col  := -1;
  for I := 0 to ListView1.Columns.Count - 1 do begin
      X2 := X2 + ListView1.Columns[0].Width;
      if (FMouseDown.X >= X1) and (FMouseDown.X < X2) then begin
          Col := I;
          break;
      end;
      X1 := X2;
  end;
  if Col < 0 then
      Exit;

  Edit1.Text  := ListView1.Selected.SubItems[0];
  Edit1.Top   := ListView1.Top + Rect.Top - 1;
  Edit1.Left  := X1;
  Edit1.Width := X2 - X1; // Same width as column
  Edit1.Show;
  Edit1.SetFocus;
end;

Upvotes: 1

Related Questions