Проблема Delphi — TListView в виртуальном режиме

#listview #delphi

Вопрос:

После настройки ListView в виртуальном режиме ListView1.Выбран.Top всегда возвращает 0. Я использую это свойство при двойном щелчке по списку, чтобы отобразить поле редактирования в этой позиции.

Как я могу это решить?

Пример файлов .pas и .dfm приведен здесь. Я хочу открыть окно редактирования в том месте, где он дважды щелкнут.

 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.
 

И .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
 

Комментарии:

1. Что вы делаете с этой информацией? Знание этого может помочь людям придумать альтернативу.

2. Я показываю окно редактирования в этой позиции.

3. Не могли бы вы привести минимальный, но полный, воспроизводимый пример, с которым мы могли бы поиграть? Отредактируйте свой вопрос, чтобы опубликовать файлы .pas и .dfm для этого примера.

4. Добавлен простой пример.

5. При использовании виртуального режима TListView необходимо использовать временное TListItem значение для удовлетворения таких свойств Selected , как Items[] , и т.д. Я нахожу, что это, как правило , вызывает тонкие/нежелательные побочные эффекты, поэтому я обычно просто игнорирую такие свойства в виртуальном режиме и сразу обращаюсь к API Win32, чтобы получить необходимую мне информацию, например ListView_GetNextItem(LVNI_SELECTED) , и ListView_GetItemRect() т. Д

Ответ №1:

Я могу воспроизвести вашу проблему. Я нашел обходной путь: используйте прямоугольник отображения выбранного элемента:

 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;
 

Если вы хотите получить подпункт, вам нужно выполнить итерацию по столбцам, чтобы найти, на какой из них нажал пользователь. Нам нужно положение мыши, поэтому мы устанавливаем обработчик событий OnMouseDown, чтобы сохранить координаты мыши и использовать их для поиска столбца.

   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;
 

Комментарии:

1. Дополнительный вопрос: Как получить левую позицию столбца? Сетка находится в режиме выбора строк. Есть ли лучший способ рассчитать его по ширине каждого столбца?

2. Отредактировал мой ответ. Кстати, в следующий раз, когда вы спросите, вам следует лучше сформулировать свой вопрос.