Konkurs: Jak pobrać zakres wyświetlanych itemów w TListView

0

Witajcie,

W dniu dzisiejszym natknąłem się na ciekawy problem programistyczny. Udało się go rozwiązać w dość ... nietypowy sposób. W związku z powyższym postanowiłem podzielić się z wami problemem (w oczekiwaniu na wasze rozwiązania) za kilka dni wrzucę również moje rozwiązanie (chyba, że ktoś z was zrobi to tak jak ja).

Mamy TListView w trybie virtualnym

OwnerData = True

Dynamicznie dodajemy do niego itemy (np 10 mln - dlatego jest tryb virtual). Następnie potrzebujemy pobrać zakres wyświetlonych rekordów czyli jeśli forma w obecnym rozmiarze wyświetla 10 rekordów i przeskrolujemy np o 20 rekordów to zakres ma być od 30 do 40. Jak zwiększymy rozmiar to dalej mamy mieć aktualny zakres.

Ktoś mógłby napisać od ręki, że należy użyć zdarzenia OnDataHint gdzie jako parametr zdarzenia mamy StartIndexi EndIndex ALE tego użyć nie można bo przy kliku w dany item StartIndex i EndIndex są równe zaznaczonemu rekordowi. Ponadto musicie wziąć pod uwagę, że ktoś skrolując ListView może użyć scrolla z myszki lub strzałek na klawiaturze, które również w zdarzeniu OnDataHint wyświetlają zakresy odrysowywane, a nie rzeczywisty wyświetlany zakres.

W załączeniu zamieszczam przykładowy projekt do testów oraz screen, który zakłada rozwiązanie (w captionie formy) jakie mnie zadowala bez względu czy klikniemy w ListView, przeskrolujemy paskie z boku, scrolem z myszy, naciśniemy górę/dół na klawiaturze czy np pgup/pgdn lub home/end.

W każdym wypadku ma działać tak jak na moim screenie. Powodzenia

1

naprawdę nie mam pojęcia co wymyśliłeś ale całość się zawiera w odpytaniu dwóch właściwości ListView

procedure TForm1.btn1Click(Sender: TObject);
var
  f, c: Integer;
begin
  c := lv.VisibleRowCount;
  f := lv.TopItem.Index;
  Caption := Format('%d - %d', [f, f + c]);
end;

W załączniku exec

0

Widzisz dobrze, że mi przypomniałeś bo zapomniałem dopisać. Musi to być wywoływane na zdarzeniu zmiany rekordów. Twoje rozwiązanie jest bardzo fajne pytanie, pod która metodę (event) byś to podpiął aby reagowało na wszystkie możliwe zmiany zakresu/danych? Innymi słowy BEZ BUTTONA :D. (ale ja faktycznie wymyśliłem strasznego cudaka w porównaniu z Twoim rozwiązaniem :D)

1

tu jest wersja dla leniwych i jednostrzałowa

type
  TForm1 = class(TForm)
    lv: TListView;
    pnl1: TPanel;
    btn1: TButton;
    mmo1: TMemo;
    procedure lvData(Sender: TObject; Item: TListItem);
    procedure FormCreate(Sender: TObject);
  private
    FPrevListViewProc: TWndMethod;
    procedure ListViewWndProc(var Msg: TMessage);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  lv.Items.Count := 2000;
  FPrevListViewProc := lv.WindowProc;
  lv.WindowProc := ListViewWndProc;
end;

procedure TForm1.lvData(Sender: TObject; Item: TListItem);
begin
  Item.Caption := IntToStr(Item.Index);
end;

procedure TForm1.ListViewWndProc(var Msg: TMessage);
var
  f, c: Integer;
begin
  case Msg.Msg of
    WM_ERASEBKGND, WM_SIZE:
    begin
      c := lv.VisibleRowCount;
      f := lv.TopItem.Index;
      Caption := Format('%d - %d', [f, f + c]);
    end;    
  end;
  FPrevListViewProc(Msg);
end;

"po ludzku" należało by stworzyć komponent, który dziedziczył by po TListView i dodać mu zdarzenie np. OnCosSieStalo wywoływane w momencie dostania np. WM_ERASEBKGND. To zdarzenie bo jest ono wywoływane zawsze wtedy, kiedy trzeba przerysować komponent (np. zmienił się zakres pokazywanych itemów, nie ważne jak się zmienił) i wywoływane jest raz w przeciwieństwie do np. WM_DRAWITEM. Dodatkowo reagowanie na WM_SIZE obsługuje przypadek, kiedy LV jest zmniejszane - wtedy nie trzeba go malować od nowa

0

Bardzo fajnie faktycznie nie wpadłem na to aby przechwycić TWndMethod. Podpowiem, że ja użyłem funkcji z WinApi i zajęło to raptem 4 linijki kodu bez konieczności nadpisywania klasy itd. :)

Sprawdziłem jeszcze w praktyce Twoje rozwiązanie i faktycznie spełnia oczekiwania zadania ale ja go zastosować nie mogę u siebie gdyż dopiero w zdarzeniu OnData uzupełniam wszystkie niezbędne dane itema ale pomysł ciekawy.

0

Ponieważ ja potrzebowałem w zdarzeniu OnData pobrać odpowiednie informacje z TDictionary i odpowiednio ustawić Itema to rozwiązałem temat tak:

procedure TForm1.lvData(Sender: TObject; Item: TListItem);
var
  si: TScrollInfo;
begin
  Item.Caption := IntToStr(Item.Index); 
  si.cbSize := sizeof(si);
  si.fMask := SIF_ALL;
  if GetScrollInfo(lv.Handle, SB_VERT, si) then
    Caption := Format('%d - %d', [si.nPos, si.nPos + si.nPage]);
end;
0

Nie do końca jest dla mnie zrozumiały problem, ale rozwiązanie większości problemów z treeview / listview ma jedną nazwę: "VirtualTreeview".

1 użytkowników online, w tym zalogowanych: 0, gości: 1